From 3c2a45a9d2062a38dee543b11ad72675c1dc79fd Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Thu, 27 Aug 2020 18:13:38 +0200 Subject: [PATCH 001/447] 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 Co-authored-by: Vasco Santos --- examples/chat/src/stream.js | 2 +- examples/libp2p-in-the-browser/package.json | 12 ++++++------ examples/pnet/README.md | 6 +----- examples/pnet/package.json | 20 -------------------- 4 files changed, 8 insertions(+), 32 deletions(-) delete mode 100644 examples/pnet/package.json diff --git a/examples/chat/src/stream.js b/examples/chat/src/stream.js index de888dc74a..6c44abcbf4 100644 --- a/examples/chat/src/stream.js +++ b/examples/chat/src/stream.js @@ -29,7 +29,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', '')) } } ) diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json index 91f58823b5..6c8b212361 100644 --- a/examples/libp2p-in-the-browser/package.json +++ b/examples/libp2p-in-the-browser/package.json @@ -16,12 +16,12 @@ "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-secio": "^0.13.1", + "libp2p-webrtc-star": "^0.20.0", + "libp2p-websockets": "^0.14.0" }, "devDependencies": { "@babel/cli": "^7.8.3", diff --git a/examples/pnet/README.md b/examples/pnet/README.md index b7515e9619..b14f8f4ab6 100644 --- a/examples/pnet/README.md +++ b/examples/pnet/README.md @@ -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. diff --git a/examples/pnet/package.json b/examples/pnet/package.json deleted file mode 100644 index 4736ac77b6..0000000000 --- a/examples/pnet/package.json +++ /dev/null @@ -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" - } -} From bd26bde876bf8ec76d08ad199fd42e089a1e82ab Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 31 Aug 2020 15:19:43 +0200 Subject: [PATCH 002/447] chore: remove libp2p-pubsub from package table (#743) --- README.md | 1 - package-list.json | 1 - 2 files changed, 2 deletions(-) diff --git a/README.md b/README.md index 9f94bc17ba..34bd8267cc 100644 --- a/README.md +++ b/README.md @@ -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** | diff --git a/package-list.json b/package-list.json index e9b9409ec8..84c648a971 100644 --- a/package-list.json +++ b/package-list.json @@ -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"], From 63ba2f8fa355566c3a5e14661ffa30df4a3aad69 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 8 Sep 2020 14:07:06 +0200 Subject: [PATCH 003/447] chore: update docs after secio deprecation announcement (#745) --- doc/API.md | 2 +- doc/CONFIGURATION.md | 58 +++++++++++++++++++++--------------------- doc/GETTING_STARTED.md | 12 ++++----- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/doc/API.md b/doc/API.md index a72a2fa1da..4b412dd97b 100644 --- a/doc/API.md +++ b/doc/API.md @@ -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) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 1ea9d7cb43..4c219481ee 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -98,7 +98,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). @@ -232,7 +232,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 +244,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 +258,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 +291,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 +300,7 @@ const node = await Libp2p.create({ WebRTCStar ], streamMuxer: [MPLEX], - connEncryption: [SECIO], + connEncryption: [NOISE], }, config: { peerDiscovery: { @@ -318,14 +318,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 +345,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 +375,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 +387,7 @@ const node = await Libp2p.create({ modules: { transport: [TCP], streamMuxer: [MPLEX], - connEncryption: [SECIO], + connEncryption: [NOISE], contentRouting: [ new DelegatedContentRouter(peerId) ], @@ -405,13 +405,13 @@ 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 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) @@ -438,14 +438,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', @@ -472,13 +472,13 @@ 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 node = await Libp2p.create({ modules: { transport: [TCP], streamMuxer: [MPLEX], - connEncryption: [SECIO] + connEncryption: [NOISE] }, dialer: { maxParallelDials: 100, @@ -495,13 +495,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 +526,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 +534,7 @@ const node = await Libp2p.create({ modules: { transport: [TCP], streamMuxer: [MPLEX], - connEncryption: [SECIO] + connEncryption: [NOISE] }, transportManager: { faultTolerance: FaultTolerance.NO_FATAL @@ -560,13 +560,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 +599,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 +607,7 @@ const node = await Libp2p.create({ modules: { transport: [TCP], streamMuxer: [MPLEX], - connEncryption: [SECIO] + connEncryption: [NOISE] }, datastore: new LevelStore('path/to/store'), peerStore: { @@ -625,7 +625,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 +633,7 @@ const node = await Libp2p.create({ modules: { transport: [WebRTCStar], streamMuxer: [MPLEX], - connEncryption: [SECIO] + connEncryption: [NOISE] }, config: { transport: { diff --git a/doc/GETTING_STARTED.md b/doc/GETTING_STARTED.md index 8b50efd7e0..0e280b2236 100644 --- a/doc/GETTING_STARTED.md +++ b/doc/GETTING_STARTED.md @@ -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,7 +197,7 @@ 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') @@ -211,7 +211,7 @@ const bootstrapMultiaddrs = [ const node = await Libp2p.create({ modules: { transport: [WebSockets], - connEncryption: [SECIO], + connEncryption: [NOISE], streamMuxer: [MPLEX], peerDiscovery: [Bootstrap] }, From 58b793d700a86e273e7cc8e58f9fd9eb7dc2723c Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 8 Sep 2020 14:47:25 +0200 Subject: [PATCH 004/447] chore: add libp2p examples repo to release checklist (#746) --- RELEASE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE.md b/RELEASE.md index c121b91aa6..6ad0393da2 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -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 From 0087218194579433bdf1a3409d46c2a2e9aa55c3 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 14 Sep 2020 13:23:28 +0200 Subject: [PATCH 005/447] chore: update getting started connect event (#752) --- doc/GETTING_STARTED.md | 6 +++--- examples/libp2p-in-the-browser/index.js | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/doc/GETTING_STARTED.md b/doc/GETTING_STARTED.md index 0e280b2236..e7398cc1d7 100644 --- a/doc/GETTING_STARTED.md +++ b/doc/GETTING_STARTED.md @@ -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() diff --git a/examples/libp2p-in-the-browser/index.js b/examples/libp2p-in-the-browser/index.js index 7403224907..dcd8b8da37 100644 --- a/examples/libp2p-in-the-browser/index.js +++ b/examples/libp2p-in-the-browser/index.js @@ -5,7 +5,7 @@ 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 @@ -23,11 +23,13 @@ document.addEventListener('DOMContentLoaded', async () => { transport: [Websockets, WebRTCStar], connEncryption: [NOISE, Secio], 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', From fb4b2734d3ce15572170fa01171a6b0516831908 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 15 Sep 2020 12:47:16 +0200 Subject: [PATCH 006/447] chore: update delegate deps (#753) --- package.json | 6 +++--- test/content-routing/content-routing.node.js | 9 +++++---- test/peer-routing/peer-routing.node.js | 9 +++++---- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index f19b862285..86becc1c52 100644 --- a/package.json +++ b/package.json @@ -93,14 +93,14 @@ "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", diff --git a/test/content-routing/content-routing.node.js b/test/content-routing/content-routing.node.js index adda6c1370..17850e9924 100644 --- a/test/content-routing/content-routing.node.js +++ b/test/content-routing/content-routing.node.js @@ -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') ]) diff --git a/test/peer-routing/peer-routing.node.js b/test/peer-routing/peer-routing.node.js index f29ff3f7c6..105ef6148f 100644 --- a/test/peer-routing/peer-routing.node.js +++ b/test/peer-routing/peer-routing.node.js @@ -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, { From bb59b518f142065d9f40852aa9089d73ac7a7cdc Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 16 Sep 2020 15:46:05 +0200 Subject: [PATCH 007/447] 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 --- doc/migrations/v0.28-v0.29.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/doc/migrations/v0.28-v0.29.md b/doc/migrations/v0.28-v0.29.md index 9eb2d6d71d..131b97ae8a 100644 --- a/doc/migrations/v0.28-v0.29.md +++ b/doc/migrations/v0.28-v0.29.md @@ -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. From 96df4b7dc466084d7d7a320f81122fecef232eb1 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 6 Oct 2020 14:59:43 +0200 Subject: [PATCH 008/447] chore: update aegir and jsdocs for eslint changes (#773) --- package.json | 2 +- src/address-manager/index.js | 17 +++--- src/circuit/circuit/hop.js | 8 ++- src/circuit/circuit/stop.js | 5 +- src/circuit/circuit/stream-handler.js | 7 ++- src/circuit/circuit/utils.js | 2 +- src/circuit/index.js | 4 +- src/circuit/listener.js | 18 +++--- src/connection-manager/index.js | 47 +++++++++----- src/connection-manager/latency-monitor.js | 61 ++++++++++--------- .../visibility-change-emitter.js | 49 ++++++++------- src/content-routing.js | 9 ++- src/dialer/dial-request.js | 3 +- src/dialer/index.js | 19 +++--- src/get-peer.js | 2 +- src/identify/index.js | 17 ++++-- src/index.js | 27 ++++++-- src/keychain/index.js | 16 ++--- src/keychain/util.js | 2 +- src/metrics/index.js | 24 +++++--- src/metrics/old-peers.js | 2 +- src/metrics/stats.js | 4 +- src/peer-routing.js | 4 +- src/peer-store/address-book.js | 44 ++++++++----- src/peer-store/book.js | 26 +++++--- src/peer-store/index.js | 9 ++- src/peer-store/key-book.js | 15 +++-- src/peer-store/metadata-book.js | 18 ++++-- src/peer-store/persistent/index.js | 32 +++++++--- src/peer-store/proto-book.js | 10 ++- src/ping/index.js | 5 +- src/pnet/crypto.js | 12 ++-- src/pnet/index.js | 6 +- src/pnet/key-generator.js | 3 +- src/pubsub-adapter.js | 2 + src/record/envelope/index.js | 39 +++++++----- src/record/peer-record/index.js | 17 +++--- src/registrar.js | 14 +++-- src/transport-manager.js | 20 ++++-- src/upgrader.js | 38 +++++++----- test/utils/creators/peer.js | 18 +++--- test/utils/mockConnection.js | 6 +- test/utils/mockMultiaddrConn.js | 5 +- 43 files changed, 425 insertions(+), 263 deletions(-) diff --git a/package.json b/package.json index 86becc1c52..c4a7f7dd7b 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "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", diff --git a/src/address-manager/index.js b/src/address-manager/index.js index e15279367b..0ba0df03c3 100644 --- a/src/address-manager/index.js +++ b/src/address-manager/index.js @@ -15,11 +15,11 @@ const multiaddr = require('multiaddr') */ class AddressManager { /** - * @constructor + * @class * @param {object} [options] - * @param {Array} [options.listen = []] list of multiaddrs string representation to listen. - * @param {Array} [options.announce = []] list of multiaddrs string representation to announce. - * @param {Array} [options.noAnnounce = []] list of multiaddrs string representation to not announce. + * @param {Array} [options.listen = []] - list of multiaddrs string representation to listen. + * @param {Array} [options.announce = []] - list of multiaddrs string representation to announce. + * @param {Array} [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} + * + * @returns {Array} */ getListenAddrs () { return Array.from(this.listen).map((a) => multiaddr(a)) @@ -37,7 +38,8 @@ class AddressManager { /** * Get peer announcing multiaddrs. - * @return {Array} + * + * @returns {Array} */ getAnnounceAddrs () { return Array.from(this.announce).map((a) => multiaddr(a)) @@ -45,7 +47,8 @@ class AddressManager { /** * Get peer noAnnouncing multiaddrs. - * @return {Array} + * + * @returns {Array} */ getNoAnnounceAddrs () { return Array.from(this.noAnnounce).map((a) => multiaddr(a)) diff --git a/src/circuit/circuit/hop.js b/src/circuit/circuit/hop.js index c8ac0fddb0..f497f33a32 100644 --- a/src/circuit/circuit/hop.js +++ b/src/circuit/circuit/hop.js @@ -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} */ module.exports.hop = async function hop ({ @@ -119,6 +118,11 @@ module.exports.hop = async function hop ({ /** * 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 ({ diff --git a/src/circuit/circuit/stop.js b/src/circuit/circuit/stop.js index ef4ca3d1dd..77eaa1fcc2 100644 --- a/src/circuit/circuit/stop.js +++ b/src/circuit/circuit/stop.js @@ -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 ({ diff --git a/src/circuit/circuit/stream-handler.js b/src/circuit/circuit/stream-handler.js index 5fd8b6389a..8b8ecf89bc 100644 --- a/src/circuit/circuit/stream-handler.js +++ b/src/circuit/circuit/stream-handler.js @@ -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() diff --git a/src/circuit/circuit/utils.js b/src/circuit/circuit/utils.js index 8e87851d21..be7ab35a73 100644 --- a/src/circuit/circuit/utils.js +++ b/src/circuit/circuit/utils.js @@ -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) { diff --git a/src/circuit/index.js b/src/circuit/index.js index baf10e370e..15746c907c 100644 --- a/src/circuit/index.js +++ b/src/circuit/index.js @@ -21,7 +21,7 @@ class Circuit { /** * Creates an instance of Circuit. * - * @constructor + * @class * @param {object} options * @param {Libp2p} options.libp2p * @param {Upgrader} options.upgrader @@ -152,7 +152,7 @@ class Circuit { * * @param {any} options * @param {Function} handler - * @return {listener} + * @returns {listener} */ createListener (options, handler) { if (typeof options === 'function') { diff --git a/src/circuit/listener.js b/src/circuit/listener.js index 055b4ef3d4..76870501dc 100644 --- a/src/circuit/listener.js +++ b/src/circuit/listener.js @@ -19,7 +19,7 @@ module.exports = (circuit) => { * 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 !== '') @@ -34,7 +34,7 @@ module.exports = (circuit) => { /** * TODO: Remove the peers from our topology * - * @return {void} + * @returns {void} */ listener.close = () => {} @@ -44,15 +44,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 = [] diff --git a/src/connection-manager/index.js b/src/connection-manager/index.js index d16878cb0b..1b1d807cb5 100644 --- a/src/connection-manager/index.js +++ b/src/connection-manager/index.js @@ -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} */ this._peerValues = new Map() /** * Map of connections per peer + * * @type {Map>} */ 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} */ @@ -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 () { diff --git a/src/connection-manager/latency-monitor.js b/src/connection-manager/latency-monitor.js index 8af45edc91..db299890b4 100644 --- a/src/connection-manager/latency-monitor.js +++ b/src/connection-manager/latency-monitor.js @@ -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 diff --git a/src/connection-manager/visibility-change-emitter.js b/src/connection-manager/visibility-change-emitter.js index 152945e8cc..baece0ec17 100644 --- a/src/connection-manager/visibility-change-emitter.js +++ b/src/connection-manager/visibility-change-emitter.js @@ -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') diff --git a/src/content-routing.js b/src/content-routing.js index dcf549e7b0..d7e160e79e 100644 --- a/src/content-routing.js +++ b/src/content-routing.js @@ -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} */ 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 diff --git a/src/dialer/dial-request.js b/src/dialer/dial-request.js index 2f97f997ac..dc427e1bbf 100644 --- a/src/dialer/dial-request.js +++ b/src/dialer/dial-request.js @@ -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} 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) { diff --git a/src/dialer/index.js b/src/dialer/index.js index bd43bd7183..fc02b357a1 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -20,12 +20,13 @@ 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. */ constructor ({ transportManager, @@ -62,9 +63,9 @@ 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} */ async connectToPeer (peer, options = {}) { @@ -101,8 +102,9 @@ 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 + * @param {PeerId|Multiaddr|string} peer - A PeerId or Multiaddr * @returns {DialTarget} */ _createDialTarget (peer) { @@ -137,10 +139,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) { diff --git a/src/get-peer.js b/src/get-peer.js index cb6cba4c19..bc36b04cc9 100644 --- a/src/get-peer.js +++ b/src/get-peer.js @@ -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 }} */ function getPeer (peer) { diff --git a/src/identify/index.js b/src/identify/index.js index a313ad0e3f..f42a8b6f94 100644 --- a/src/identify/index.js +++ b/src/identify/index.js @@ -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,10 +48,10 @@ class IdentifyService { } /** - * @constructor + * @class * @param {object} options * @param {Libp2p} options.libp2p - * @param {Map} options.protocols A reference to the protocols we support + * @param {Map} options.protocols - A reference to the protocols we support */ constructor ({ libp2p, protocols }) { /** @@ -86,6 +87,7 @@ class IdentifyService { /** * Send an Identify Push update to the list of connections + * * @param {Array} connections * @returns {Promise} */ @@ -119,6 +121,7 @@ class IdentifyService { /** * Calls `push` for all peers in the `peerStore` that are connected + * * @param {PeerStore} peerStore */ pushToPeerStore (peerStore) { @@ -209,7 +212,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} @@ -228,6 +231,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 @@ -265,6 +269,7 @@ class IdentifyService { /** * Reads the Identify Push message from the given `connection` + * * @private * @param {object} options * @param {*} options.stream @@ -311,7 +316,8 @@ class IdentifyService { /** * Get self signed peer record raw envelope. - * @return {Uint8Array} + * + * @returns {Uint8Array} */ async _getSelfPeerRecord () { const selfSignedPeerRecord = this.peerStore.addressBook.getRawEnvelope(this.peerId) @@ -340,6 +346,7 @@ class IdentifyService { module.exports.IdentifyService = IdentifyService /** * The protocols the IdentifyService supports + * * @property multicodecs */ module.exports.multicodecs = { diff --git a/src/index.js b/src/index.js index 8ff4e0f7ec..e7a70e013b 100644 --- a/src/index.js +++ b/src/index.js @@ -206,6 +206,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 +241,7 @@ class Libp2p extends EventEmitter { /** * Stop the libp2p node by closing its listeners and open connections + * * @async * @returns {void} */ @@ -281,6 +283,7 @@ class Libp2p extends EventEmitter { /** * Load keychain keys from the datastore. * Imports the private key as 'self', if needed. + * * @async * @returns {void} */ @@ -299,6 +302,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} */ get connections () { @@ -308,7 +312,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} @@ -321,8 +326,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 +356,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} + * + * @returns {Array} */ get multiaddrs () { // Filter noAnnounce multiaddrs @@ -376,7 +383,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} */ async hangUp (peer) { @@ -397,7 +405,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} */ ping (peer) { @@ -413,6 +422,7 @@ class Libp2p extends EventEmitter { /** * Registers the `handler` for each protocol + * * @param {string[]|string} protocols * @param {function({ connection:*, stream:*, protocol:string })} handler */ @@ -431,6 +441,7 @@ class Libp2p extends EventEmitter { /** * Removes the handler for each protocol. The protocol * will no longer be supported on streams. + * * @param {string[]|string} protocols */ unhandle (protocols) { @@ -471,6 +482,7 @@ class Libp2p extends EventEmitter { /** * Called when libp2p has started and before it returns + * * @private */ async _onDidStart () { @@ -496,6 +508,7 @@ class Libp2p extends EventEmitter { /** * Called whenever peer discovery services emit `peer` events. * Known peers may be emitted. + * * @private * @param {{ id: PeerId, multiaddrs: Array, protocols: Array }} peer */ @@ -513,6 +526,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 +600,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 = {}) { diff --git a/src/keychain/index.js b/src/keychain/index.js index d01acb68fb..c823eb3e46 100644 --- a/src/keychain/index.js +++ b/src/keychain/index.js @@ -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) { diff --git a/src/keychain/util.js b/src/keychain/util.js index 816b4366d7..56386fe488 100644 --- a/src/keychain/util.js +++ b/src/keychain/util.js @@ -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) diff --git a/src/metrics/index.js b/src/metrics/index.js index cfddd837d6..9d0f436041 100644 --- a/src/metrics/index.js +++ b/src/metrics/index.js @@ -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} */ 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} */ 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} diff --git a/src/metrics/old-peers.js b/src/metrics/old-peers.js index 4ebc015a90..08d317dc09 100644 --- a/src/metrics/old-peers.js +++ b/src/metrics/old-peers.js @@ -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) => { diff --git a/src/metrics/stats.js b/src/metrics/stats.js index fa8252757e..3517766309 100644 --- a/src/metrics/stats.js +++ b/src/metrics/stats.js @@ -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) { diff --git a/src/peer-routing.js b/src/peer-routing.js index a38eb53b17..7594f15d77 100644 --- a/src/peer-routing.js +++ b/src/peer-routing.js @@ -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 diff --git a/src/peer-store/address-book.js b/src/peer-store/address-book.js index 0036a0ce2b..c8fa2ec6f5 100644 --- a/src/peer-store/address-book.js +++ b/src/peer-store/address-book.js @@ -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
} addresses peer Addresses. - * @property {CertifiedRecord} record certified peer record. - */ + * Entry object for the addressBook + * + * @typedef {Object} Entry + * @property {Array
} 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>} */ 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} + * @returns {Promise} */ 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} 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} multiaddrs * @returns {AddressBook} @@ -258,6 +267,7 @@ class AddressBook extends Book { /** * Get the known data of a provided peer. + * * @override * @param {PeerId} peerId * @returns {Array} @@ -274,6 +284,7 @@ class AddressBook extends Book { /** * Transforms received multiaddrs into Address. + * * @private * @param {Array} 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|undefined} */ diff --git a/src/peer-store/book.js b/src/peer-store/book.js index 8827c9fa8c..f0a830f97a 100644 --- a/src/peer-store/book.js +++ b/src/peer-store/book.js @@ -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} */ this.data = new Map() @@ -36,6 +37,7 @@ class Book { /** * Set known data of a provided peer. + * * @param {PeerId} peerId * @param {Array|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|undefined} */ @@ -93,6 +98,7 @@ class Book { /** * Deletes the provided peer from the book. + * * @param {PeerId} peerId * @returns {boolean} */ diff --git a/src/peer-store/index.js b/src/peer-store/index.js index b379c96df2..69a1f15a57 100644 --- a/src/peer-store/index.js +++ b/src/peer-store/index.js @@ -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
} 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} */ 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} */ diff --git a/src/peer-store/key-book.js b/src/peer-store/key-book.js index a5b26edce3..607d12795f 100644 --- a/src/peer-store/key-book.js +++ b/src/peer-store/key-book.js @@ -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} */ 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)) { diff --git a/src/peer-store/metadata-book.js b/src/peer-store/metadata-book.js index bd84f53a27..490ef02b09 100644 --- a/src/peer-store/metadata-book.js +++ b/src/peer-store/metadata-book.js @@ -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>} */ 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} */ @@ -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} diff --git a/src/peer-store/persistent/index.js b/src/peer-store/persistent/index.js index 68505911f6..2d87620e61 100644 --- a/src/peer-store/persistent/index.js +++ b/src/peer-store/persistent/index.js @@ -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>} */ this._dirtyMetadata = new Map() @@ -57,7 +58,8 @@ class PersistentPeerStore extends PeerStore { /** * Start Persistent PeerStore. - * @return {Promise} + * + * @returns {Promise} */ async start () { log('PeerStore is starting') @@ -76,6 +78,11 @@ class PersistentPeerStore extends PeerStore { log('PeerStore started') } + /** + * Stop Persistent PeerStore. + * + * @returns {Promise} + */ 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 @@ -105,6 +113,7 @@ class PersistentPeerStore extends PeerStore { /** * Add modified metadata peer to the set. + * * @private * @param {Object} params * @param {PeerId} params.peerId @@ -131,9 +140,9 @@ class PersistentPeerStore extends PeerStore { /** * Add all the peers current data to a datastore batch and commit it. + * * @private - * @param {Array} peers - * @return {Promise} + * @returns {Promise} */ async _commitData () { const commitPeers = Array.from(this._dirtyPeers) @@ -170,6 +179,7 @@ class PersistentPeerStore extends PeerStore { /** * Add address book data of the peer to the batch. + * * @private * @param {PeerId} peerId * @param {Object} batch @@ -206,6 +216,7 @@ class PersistentPeerStore extends PeerStore { /** * Add Key book data of the peer to the batch. + * * @private * @param {PeerId} peerId * @param {Object} batch @@ -231,6 +242,7 @@ class PersistentPeerStore extends PeerStore { /** * Add metadata book data of the peer to the batch. + * * @private * @param {PeerId} peerId * @param {Object} batch @@ -257,6 +269,7 @@ class PersistentPeerStore extends PeerStore { /** * Add proto book data of the peer to the batch. + * * @private * @param {PeerId} peerId * @param {Object} batch @@ -284,11 +297,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} + * @param {Key} params.key - datastore key + * @param {Uint8Array} params.value - datastore value stored + * @returns {Promise} */ async _processDatastoreEntry ({ key, value }) { try { diff --git a/src/peer-store/proto-book.js b/src/peer-store/proto-book.js index edd906fbb3..073b7e47e5 100644 --- a/src/peer-store/proto-book.js +++ b/src/peer-store/proto-book.js @@ -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>} */ 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} 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} protocols * @returns {ProtoBook} diff --git a/src/ping/index.js b/src/ping/index.js index 7c481739ca..e882f81f4c 100644 --- a/src/ping/index.js +++ b/src/ping/index.js @@ -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} + * @returns {Promise} */ 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) { diff --git a/src/pnet/crypto.js b/src/pnet/crypto.js index ec8f445219..ae824bfcfb 100644 --- a/src/pnet/crypto.js +++ b/src/pnet/crypto.js @@ -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) => { diff --git a/src/pnet/index.js b/src/pnet/index.js index 43b5a52710..e5f82331a2 100644 --- a/src/pnet/index.js +++ b/src/pnet/index.js @@ -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) { diff --git a/src/pnet/key-generator.js b/src/pnet/key-generator.js index 64a24dfbb6..b3676dcccf 100644 --- a/src/pnet/key-generator.js +++ b/src/pnet/key-generator.js @@ -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) { diff --git a/src/pubsub-adapter.js b/src/pubsub-adapter.js index 6927178c75..1f42cc7d15 100644 --- a/src/pubsub-adapter.js +++ b/src/pubsub-adapter.js @@ -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] diff --git a/src/record/envelope/index.js b/src/record/envelope/index.js index 1eb4e108a7..6a73914f2a 100644 --- a/src/record/envelope/index.js +++ b/src/record/envelope/index.js @@ -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} + * @returns {Promise} */ 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} + * @returns {Promise} */ 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) diff --git a/src/record/peer-record/index.js b/src/record/peer-record/index.js index 2bd5cdc90e..51c43a7970 100644 --- a/src/record/peer-record/index.js +++ b/src/record/peer-record/index.js @@ -17,11 +17,11 @@ const { */ class PeerRecord extends Record { /** - * @constructor + * @class * @param {object} params * @param {PeerId} params.peerId - * @param {Array} 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} 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 diff --git a/src/registrar.js b/src/registrar.js index 6aa601f003..5130a02fcb 100644 --- a/src/registrar.js +++ b/src/registrar.js @@ -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} diff --git a/src/transport-manager.js b/src/transport-manager.js index beeb6bb6e2..e18841bf02 100644 --- a/src/transport-manager.js +++ b/src/transport-manager.js @@ -9,11 +9,11 @@ log.error = debug('libp2p:transports:error') 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 +26,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 +54,7 @@ class TransportManager { /** * Stops all listeners + * * @async */ async close () { @@ -75,6 +76,7 @@ class TransportManager { /** * Dials the given Multiaddr over it's supported transport + * * @param {Multiaddr} ma * @param {*} options * @returns {Promise} @@ -95,6 +97,7 @@ class TransportManager { /** * Returns all Multiaddr's the listeners are using + * * @returns {Multiaddr[]} */ getAddrs () { @@ -109,6 +112,7 @@ class TransportManager { /** * Returns all the transports instances. + * * @returns {Iterator} */ getTransports () { @@ -117,6 +121,7 @@ class TransportManager { /** * Finds a transport that matches the given Multiaddr + * * @param {Multiaddr} ma * @returns {Transport|null} */ @@ -130,6 +135,7 @@ class TransportManager { /** * Starts listeners for each listen Multiaddr. + * * @async */ async listen () { @@ -206,6 +212,7 @@ class TransportManager { /** * Removes all transports from the manager. * If any listeners are running, they will be closed. + * * @async */ async removeAll () { @@ -222,6 +229,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} */ diff --git a/src/upgrader.js b/src/upgrader.js index 11188f962e..b53b647598 100644 --- a/src/upgrader.js +++ b/src/upgrader.js @@ -14,7 +14,7 @@ const { codes } = require('./errors') /** * @typedef MultiaddrConnection - * @property {function} sink + * @property {Function} sink * @property {AsyncIterator} source * @property {*} conn * @property {Multiaddr} remoteAddr @@ -34,7 +34,7 @@ class Upgrader { * @param {Metrics} options.metrics * @param {Map} options.cryptos * @param {Map} 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 +57,7 @@ class Upgrader { /** * Upgrades an inbound connection + * * @async * @param {MultiaddrConnection} maConn * @returns {Promise} @@ -124,6 +125,7 @@ class Upgrader { /** * Upgrades an outbound connection + * * @async * @param {MultiaddrConnection} maConn * @returns {Promise} @@ -198,14 +200,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 ({ @@ -302,9 +305,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 +319,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} cryptos * @returns {CryptoResult} An encrypted connection, remote peer `PeerId` and the protocol of the `Crypto` used @@ -344,9 +349,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} cryptos @@ -374,10 +380,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} muxers The muxers to attempt multiplexing with + * @param {*} connection - A basic duplex connection to multiplex + * @param {Map} muxers - The muxers to attempt multiplexing with * @returns {*} A muxed connection */ async _multiplexOutbound (connection, muxers) { @@ -397,10 +404,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} muxers The muxers to attempt multiplexing with + * @param {*} connection - A basic duplex connection to multiplex + * @param {Map} muxers - The muxers to attempt multiplexing with * @returns {*} A muxed connection */ async _multiplexInbound (connection, muxers) { diff --git a/test/utils/creators/peer.js b/test/utils/creators/peer.js index 6e4a586597..2c77cb63a0 100644 --- a/test/utils/creators/peer.js +++ b/test/utils/creators/peer.js @@ -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>} + * @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>} */ 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>} + * @param {number} [properties.number] - number of peers (default: 1). + * @param {boolean} [properties.fixture] - use fixture for peer-id generation (default: true) + * @returns {Promise>} */ function createPeerId ({ number = 1, fixture = true } = {}) { return pTimes(number, (i) => fixture diff --git a/test/utils/mockConnection.js b/test/utils/mockConnection.js index d77253b875..b85c036ebb 100644 --- a/test/utils/mockConnection.js +++ b/test/utils/mockConnection.js @@ -58,9 +58,9 @@ module.exports = async (properties = {}) => { * Creates a full connection pair, without the transport or encryption * * @param {object} options - * @param {Multiaddr[]} options.addrs Should contain two addresses for the local and remote peer respectively - * @param {PeerId[]} options.remotePeer Should contain two peer ids, for the local and remote peer respectively - * @param {Map} options.protocols The protocols the connections should support + * @param {Multiaddr[]} options.addrs - Should contain two addresses for the local and remote peer respectively + * @param {Array} options.peers - Array containing local and remote peer ids + * @param {Map} options.protocols - The protocols the connections should support * @returns {{inbound:Connection, outbound:Connection}} */ module.exports.pair = function connectionPair ({ addrs, peers, protocols }) { diff --git a/test/utils/mockMultiaddrConn.js b/test/utils/mockMultiaddrConn.js index 9fd9c9b597..bb0cf6df7c 100644 --- a/test/utils/mockMultiaddrConn.js +++ b/test/utils/mockMultiaddrConn.js @@ -6,9 +6,10 @@ const AbortController = require('abort-controller') /** * Returns both sides of a mocked MultiaddrConnection + * * @param {object} options - * @param {Multiaddr[]} options.addrs Should contain two addresses for the local and remote peer - * @param {PeerId} options.remotePeer The peer that is being "dialed" + * @param {Multiaddr[]} options.addrs - Should contain two addresses for the local and remote peer + * @param {PeerId} options.remotePeer - The peer that is being "dialed" * @returns {{inbound:MultiaddrConnection, outbound:MultiaddrConnection}} */ module.exports = function mockMultiaddrConnPair ({ addrs, remotePeer }) { From 60d437f59539e6be8a5f216a63bb134fb912cc6d Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 6 Oct 2020 15:37:01 +0200 Subject: [PATCH 009/447] fix: flakey identify test firefox (#774) --- test/identify/index.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js index 799a3ebd49..1ccbf67122 100644 --- a/test/identify/index.spec.js +++ b/test/identify/index.spec.js @@ -364,7 +364,7 @@ describe('Identify', () => { 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() }) From 0d48fc4f5a19c73297dc017e8f681eb328a216b6 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 7 Oct 2020 14:50:01 +0200 Subject: [PATCH 010/447] 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 --- package.json | 2 +- src/peer-store/persistent/index.js | 29 ++++++++++++++-- test/fixtures/browser.js | 2 +- test/fixtures/peers.js | 36 ++++++++++---------- test/peer-store/persisted-peer-store.spec.js | 11 +++--- 5 files changed, 52 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index c4a7f7dd7b..6a963181a1 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "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", diff --git a/src/peer-store/persistent/index.js b/src/peer-store/persistent/index.js index 2d87620e61..5b5d4f8ac4 100644 --- a/src/peer-store/persistent/index.js +++ b/src/peer-store/persistent/index.js @@ -67,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 @@ -111,6 +111,31 @@ 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. * @@ -164,7 +189,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) diff --git a/test/fixtures/browser.js b/test/fixtures/browser.js index 901c32e6c9..f5f33259dc 100644 --- a/test/fixtures/browser.js +++ b/test/fixtures/browser.js @@ -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') ] diff --git a/test/fixtures/peers.js b/test/fixtures/peers.js index fad0d23e8b..27150e5e84 100644 --- a/test/fixtures/peers.js +++ b/test/fixtures/peers.js @@ -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' }] diff --git a/test/peer-store/persisted-peer-store.spec.js b/test/peer-store/persisted-peer-store.spec.js index ee6b1cedfb..3c58e21dcc 100644 --- a/test/peer-store/persisted-peer-store.spec.js +++ b/test/peer-store/persisted-peer-store.spec.js @@ -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) }) From a1053bdc54a51bc1adeddae338ab382c801f7fb7 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 7 Oct 2020 14:51:51 +0200 Subject: [PATCH 011/447] chore: remove outdated events from libp2p js docs (#766) --- src/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/index.js b/src/index.js index e7a70e013b..cd900457d6 100644 --- a/src/index.js +++ b/src/index.js @@ -35,8 +35,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 { From ec6f7d1cfd70f69a970d1af72ff0c2918df070ce Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 7 Oct 2020 15:39:24 +0200 Subject: [PATCH 012/447] chore: lint issue fixed (#775) --- src/peer-store/persistent/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/peer-store/persistent/index.js b/src/peer-store/persistent/index.js index 5b5d4f8ac4..70a417f4f7 100644 --- a/src/peer-store/persistent/index.js +++ b/src/peer-store/persistent/index.js @@ -113,6 +113,7 @@ class PersistentPeerStore extends PeerStore { /** * Add modified peer key to the dirty set + * * @private * @param {Object} params * @param {PeerId} params.peerId From 2fd3b0a0e590aee2963dfe6b4a550584d6e90561 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 7 Oct 2020 16:16:36 +0200 Subject: [PATCH 013/447] 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 --- doc/CONFIGURATION.md | 2 +- examples/chat/src/libp2p-bundle.js | 3 +-- examples/chat/src/listener.js | 1 - examples/chat/src/stream.js | 1 - examples/discovery-mechanisms/1.js | 3 +-- examples/discovery-mechanisms/2.js | 3 +-- examples/discovery-mechanisms/README.md | 8 ++++---- examples/echo/src/libp2p-bundle.js | 3 +-- examples/encrypted-communications/1.js | 3 +-- examples/encrypted-communications/README.md | 13 +++---------- examples/libp2p-in-the-browser/index.js | 3 +-- examples/libp2p-in-the-browser/package.json | 1 - examples/peer-and-content-routing/1.js | 3 +-- examples/peer-and-content-routing/2.js | 3 +-- examples/peer-and-content-routing/README.md | 2 +- examples/pnet/libp2p-node.js | 3 +-- examples/protocol-and-stream-muxing/1.js | 3 +-- examples/protocol-and-stream-muxing/2.js | 3 +-- examples/protocol-and-stream-muxing/3.js | 3 +-- examples/pubsub/1.js | 3 +-- examples/pubsub/README.md | 2 +- examples/pubsub/message-filtering/1.js | 3 +-- examples/pubsub/message-filtering/README.md | 2 +- examples/transports/1.js | 3 +-- examples/transports/2.js | 3 +-- examples/transports/3.js | 3 +-- examples/transports/README.md | 7 +++---- src/circuit/README.md | 4 ++-- 28 files changed, 33 insertions(+), 61 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 4c219481ee..4983754c2e 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -223,7 +223,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 diff --git a/examples/chat/src/libp2p-bundle.js b/examples/chat/src/libp2p-bundle.js index 92771d664a..dd4596708f 100644 --- a/examples/chat/src/libp2p-bundle.js +++ b/examples/chat/src/libp2p-bundle.js @@ -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 ] } } diff --git a/examples/chat/src/listener.js b/examples/chat/src/listener.js index 8b30773d64..9b4aa49576 100644 --- a/examples/chat/src/listener.js +++ b/examples/chat/src/listener.js @@ -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') diff --git a/examples/chat/src/stream.js b/examples/chat/src/stream.js index 6c44abcbf4..7e883db03c 100644 --- a/examples/chat/src/stream.js +++ b/examples/chat/src/stream.js @@ -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 diff --git a/examples/discovery-mechanisms/1.js b/examples/discovery-mechanisms/1.js index 5f006406c0..b21f6eddc9 100644 --- a/examples/discovery-mechanisms/1.js +++ b/examples/discovery-mechanisms/1.js @@ -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: { diff --git a/examples/discovery-mechanisms/2.js b/examples/discovery-mechanisms/2.js index 3481675c7b..15c801cdec 100644 --- a/examples/discovery-mechanisms/2.js +++ b/examples/discovery-mechanisms/2.js @@ -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: { diff --git a/examples/discovery-mechanisms/README.md b/examples/discovery-mechanisms/README.md index db75f76565..8234716d13 100644 --- a/examples/discovery-mechanisms/README.md +++ b/examples/discovery-mechanisms/README.md @@ -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: { diff --git a/examples/echo/src/libp2p-bundle.js b/examples/echo/src/libp2p-bundle.js index 106bf69411..0e11a7103d 100644 --- a/examples/echo/src/libp2p-bundle.js +++ b/examples/echo/src/libp2p-bundle.js @@ -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 ] } } diff --git a/examples/encrypted-communications/1.js b/examples/encrypted-communications/1.js index 8f908a6dee..d17498afcd 100644 --- a/examples/encrypted-communications/1.js +++ b/examples/encrypted-communications/1.js @@ -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] } }) diff --git a/examples/encrypted-communications/README.md b/examples/encrypted-communications/README.md index a17939c7bd..3e23ea6de5 100644 --- a/examples/encrypted-communications/README.md +++ b/examples/encrypted-communications/README.md @@ -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`* 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. - -_* 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. diff --git a/examples/libp2p-in-the-browser/index.js b/examples/libp2p-in-the-browser/index.js index dcd8b8da37..be03f08b6c 100644 --- a/examples/libp2p-in-the-browser/index.js +++ b/examples/libp2p-in-the-browser/index.js @@ -3,7 +3,6 @@ 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 Bootstrap from 'libp2p-bootstrap' @@ -21,7 +20,7 @@ document.addEventListener('DOMContentLoaded', async () => { }, modules: { transport: [Websockets, WebRTCStar], - connEncryption: [NOISE, Secio], + connEncryption: [NOISE], streamMuxer: [Mplex], peerDiscovery: [Bootstrap] }, diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json index 6c8b212361..8d09b86d31 100644 --- a/examples/libp2p-in-the-browser/package.json +++ b/examples/libp2p-in-the-browser/package.json @@ -19,7 +19,6 @@ "libp2p-bootstrap": "^0.12.1", "libp2p-mplex": "^0.10.0", "libp2p-noise": "^2.0.0", - "libp2p-secio": "^0.13.1", "libp2p-webrtc-star": "^0.20.0", "libp2p-websockets": "^0.14.0" }, diff --git a/examples/peer-and-content-routing/1.js b/examples/peer-and-content-routing/1.js index a6940cbe1f..46c54f1d79 100644 --- a/examples/peer-and-content-routing/1.js +++ b/examples/peer-and-content-routing/1.js @@ -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: { diff --git a/examples/peer-and-content-routing/2.js b/examples/peer-and-content-routing/2.js index 51fcb96426..14f513ecfd 100644 --- a/examples/peer-and-content-routing/2.js +++ b/examples/peer-and-content-routing/2.js @@ -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: { diff --git a/examples/peer-and-content-routing/README.md b/examples/peer-and-content-routing/README.md index 6079e07f38..9832003e22 100644 --- a/examples/peer-and-content-routing/README.md +++ b/examples/peer-and-content-routing/README.md @@ -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 }, diff --git a/examples/pnet/libp2p-node.js b/examples/pnet/libp2p-node.js index 849d47d35f..8fc8a01f4b 100644 --- a/examples/pnet/libp2p-node.js +++ b/examples/pnet/libp2p-node.js @@ -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 diff --git a/examples/protocol-and-stream-muxing/1.js b/examples/protocol-and-stream-muxing/1.js index 1c4ab27ee1..70ee3175f6 100644 --- a/examples/protocol-and-stream-muxing/1.js +++ b/examples/protocol-and-stream-muxing/1.js @@ -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] } }) diff --git a/examples/protocol-and-stream-muxing/2.js b/examples/protocol-and-stream-muxing/2.js index d2e46ccaa1..3ac6bd4206 100644 --- a/examples/protocol-and-stream-muxing/2.js +++ b/examples/protocol-and-stream-muxing/2.js @@ -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] } }) diff --git a/examples/protocol-and-stream-muxing/3.js b/examples/protocol-and-stream-muxing/3.js index a247722e00..bd15f55f8a 100644 --- a/examples/protocol-and-stream-muxing/3.js +++ b/examples/protocol-and-stream-muxing/3.js @@ -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] } }) diff --git a/examples/pubsub/1.js b/examples/pubsub/1.js index 62cf622d2c..8c6fdfdb91 100644 --- a/examples/pubsub/1.js +++ b/examples/pubsub/1.js @@ -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 } }) diff --git a/examples/pubsub/README.md b/examples/pubsub/README.md index 96e7892a60..17a896f3a6 100644 --- a/examples/pubsub/README.md +++ b/examples/pubsub/README.md @@ -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 } diff --git a/examples/pubsub/message-filtering/1.js b/examples/pubsub/message-filtering/1.js index fffcd2e7a2..85d7bcf8c4 100644 --- a/examples/pubsub/message-filtering/1.js +++ b/examples/pubsub/message-filtering/1.js @@ -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 } }) diff --git a/examples/pubsub/message-filtering/README.md b/examples/pubsub/message-filtering/README.md index bff85c9077..a9c0dad26d 100644 --- a/examples/pubsub/message-filtering/README.md +++ b/examples/pubsub/message-filtering/README.md @@ -17,7 +17,7 @@ const node = await Libp2p.create({ modules: { transport: [ TCP ], streamMuxer: [ Mplex ], - connEncryption: [ NOISE, SECIO ], + connEncryption: [ NOISE ], pubsub: Gossipsub } }) diff --git a/examples/transports/1.js b/examples/transports/1.js index 49fc386bf5..cd78019dfb 100644 --- a/examples/transports/1.js +++ b/examples/transports/1.js @@ -4,7 +4,6 @@ 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({ @@ -15,7 +14,7 @@ const createNode = async () => { }, modules: { transport: [TCP], - connEncryption: [NOISE, SECIO] + connEncryption: [NOISE] } }) diff --git a/examples/transports/2.js b/examples/transports/2.js index 7310ed867e..f7a6f63ce4 100644 --- a/examples/transports/2.js +++ b/examples/transports/2.js @@ -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') @@ -19,7 +18,7 @@ const createNode = async () => { }, modules: { transport: [TCP], - connEncryption: [NOISE, SECIO], + connEncryption: [NOISE], streamMuxer: [MPLEX] } }) diff --git a/examples/transports/3.js b/examples/transports/3.js index 93b82cbed2..39f9cc97cc 100644 --- a/examples/transports/3.js +++ b/examples/transports/3.js @@ -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') @@ -21,7 +20,7 @@ const createNode = async (transports, addresses = []) => { }, modules: { transport: transports, - connEncryption: [NOISE, SECIO], + connEncryption: [NOISE], streamMuxer: [MPLEX] } }) diff --git a/examples/transports/README.md b/examples/transports/README.md index 2aaa98b6b3..467c535c62 100644 --- a/examples/transports/README.md +++ b/examples/transports/README.md @@ -13,7 +13,7 @@ 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 peer-info ``` Then, on your favorite text editor create a file with the `.js` extension. I've called mine `1.js`. @@ -26,7 +26,6 @@ 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({ @@ -37,7 +36,7 @@ const createNode = async () => { }, modules: { transport: [ TCP ], - connEncryption: [ NOISE, SECIO ] + connEncryption: [ NOISE ] } }) @@ -174,7 +173,7 @@ const createNode = async (transports, multiaddrs = []) => { }, modules: { transport: transports, - connEncryption: [SECIO], + connEncryption: [NOISE], streamMuxer: [MPLEX] } }) diff --git a/src/circuit/README.md b/src/circuit/README.md index df4f293847..e2dd5018b9 100644 --- a/src/circuit/README.md +++ b/src/circuit/README.md @@ -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) From 4eabe07bde1b562e7eb42a270c6e4450b33d35fa Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Sun, 11 Oct 2020 18:02:09 +0200 Subject: [PATCH 014/447] chore: update node badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 34bd8267cc..e6d8af7285 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ - +

From 4c7a89b710ab8e1317f3d35c1fe510eb1cc47d29 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Mon, 12 Oct 2020 06:08:53 -0400 Subject: [PATCH 015/447] doc(pubsub): add topicValidators links in API.md table of contents --- doc/API.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/API.md b/doc/API.md index 4b412dd97b..69e0ca4f78 100644 --- a/doc/API.md +++ b/doc/API.md @@ -48,6 +48,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) From d7d8439e71c3e834caf5b0405f2f643888938499 Mon Sep 17 00:00:00 2001 From: Ethan Lam Date: Thu, 15 Oct 2020 10:28:01 -0500 Subject: [PATCH 016/447] docs: update transport example (#770) --- examples/transports/1.js | 2 +- examples/transports/2.js | 2 +- examples/transports/3.js | 2 +- examples/transports/README.md | 81 +++++++++++++++++++++-------------- 4 files changed, 53 insertions(+), 34 deletions(-) diff --git a/examples/transports/1.js b/examples/transports/1.js index cd78019dfb..73857aad4d 100644 --- a/examples/transports/1.js +++ b/examples/transports/1.js @@ -8,7 +8,7 @@ const { NOISE } = require('libp2p-noise') 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'] }, diff --git a/examples/transports/2.js b/examples/transports/2.js index f7a6f63ce4..2d62849db0 100644 --- a/examples/transports/2.js +++ b/examples/transports/2.js @@ -12,7 +12,7 @@ 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'] }, diff --git a/examples/transports/3.js b/examples/transports/3.js index 39f9cc97cc..c692444341 100644 --- a/examples/transports/3.js +++ b/examples/transports/3.js @@ -16,7 +16,7 @@ const createNode = async (transports, addresses = []) => { const node = await Libp2p.create({ addresses: { - listen: addresses.map((a) => a) + listen: addresses }, modules: { transport: transports, diff --git a/examples/transports/README.md b/examples/transports/README.md index 467c535c62..98b1a5e341 100644 --- a/examples/transports/README.md +++ b/examples/transports/README.md @@ -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-noise 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: @@ -30,7 +30,7 @@ const { NOISE } = require('libp2p-noise') 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'] }, @@ -77,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, SECIO], + 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) { @@ -99,7 +120,7 @@ function printAddrs (node, number) { } ``` -Then, +Then add, ```js ;(async () => { @@ -111,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( @@ -131,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 @@ -147,29 +166,29 @@ 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, @@ -231,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 }) { @@ -246,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 @@ -265,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. From 5f50054d9458e0d704f6903d6b5aee7734bdfb30 Mon Sep 17 00:00:00 2001 From: Cindy Wu Date: Wed, 21 Oct 2020 23:02:00 -1000 Subject: [PATCH 017/447] docs: fix typo in transports example readme (#788) --- examples/transports/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/transports/README.md b/examples/transports/README.md index 98b1a5e341..8378c9a3e1 100644 --- a/examples/transports/README.md +++ b/examples/transports/README.md @@ -102,7 +102,7 @@ const createNode = async () => { }, modules: { transport: [TCP], - connEncryption: [NOISE, SECIO], + connEncryption: [NOISE], streamMuxer: [MPLEX] // <--- Add this line } }) From 4c6be9158879161955b178d15e9898fad8592fd7 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Tue, 20 Oct 2020 12:52:17 +0200 Subject: [PATCH 018/447] fix: ensure streams are closed on connection close --- package.json | 2 +- src/upgrader.js | 12 +++++++--- test/dialing/direct.node.js | 46 +++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 6a963181a1..84c4bc57cd 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "libp2p-gossipsub": "^0.6.0", "libp2p-kad-dht": "^0.20.0", "libp2p-mdns": "^0.15.0", - "libp2p-mplex": "^0.10.0", + "libp2p-mplex": "libp2p/js-libp2p-mplex#fix/stream-close", "libp2p-noise": "^2.0.0", "libp2p-secio": "^0.13.1", "libp2p-tcp": "^0.15.1", diff --git a/src/upgrader.js b/src/upgrader.js index b53b647598..d583a8b8d6 100644 --- a/src/upgrader.js +++ b/src/upgrader.js @@ -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') @@ -268,8 +269,13 @@ 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 () => { + if (connection.stat.status === ConnectionStatus.OPEN) { + await connection.close() + } + this.onConnectionEnd(connection) + })() } return Reflect.set(...args) @@ -295,7 +301,7 @@ class Upgrader { }, newStream: newStream || errConnectionNotMultiplexed, getStreams: () => muxer ? muxer.streams : errConnectionNotMultiplexed, - close: err => maConn.close(err) + close: (err) => maConn.close(err) }) this.onConnection(connection) diff --git a/test/dialing/direct.node.js b/test/dialing/direct.node.js index 401f4eb501..752f462e06 100644 --- a/test/dialing/direct.node.js +++ b/test/dialing/direct.node.js @@ -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') @@ -299,6 +301,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, From e04224a1e264295551b73e056db8fbd16bb8e8f2 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Wed, 21 Oct 2020 17:52:26 +0200 Subject: [PATCH 019/447] fix: catch error in upgrader close call --- src/upgrader.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/upgrader.js b/src/upgrader.js index d583a8b8d6..fd8fe1195a 100644 --- a/src/upgrader.js +++ b/src/upgrader.js @@ -271,10 +271,15 @@ class Upgrader { if (connection && args[1] === 'close' && args[2] && !_timeline.close) { // Wait for close to finish before notifying of the closure (async () => { - if (connection.stat.status === ConnectionStatus.OPEN) { - await connection.close() + try { + if (connection.stat.status === ConnectionStatus.OPEN) { + await connection.close() + } + } catch (err) { + log.error(err) + } finally { + this.onConnectionEnd(connection) } - this.onConnectionEnd(connection) })() } From f2d010a3abffcf1271a3ff21928b263a9803d8a4 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Thu, 22 Oct 2020 13:24:15 +0200 Subject: [PATCH 020/447] chore: update mplex --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 84c4bc57cd..8d48271b26 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "libp2p-gossipsub": "^0.6.0", "libp2p-kad-dht": "^0.20.0", "libp2p-mdns": "^0.15.0", - "libp2p-mplex": "libp2p/js-libp2p-mplex#fix/stream-close", + "libp2p-mplex": "^0.10.1", "libp2p-noise": "^2.0.0", "libp2p-secio": "^0.13.1", "libp2p-tcp": "^0.15.1", From f75ae341bb22edd41fbc05c48d905962544116cf Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Thu, 22 Oct 2020 13:52:10 +0200 Subject: [PATCH 021/447] test: lock ci on node 14 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 839394510b..bb21bd4028 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ stages: node_js: - 'lts/*' - - 'stable' + - '14' os: - linux From 4a80afce8f5dd72e34e73e9e212cfd2b5b5ff3e3 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Thu, 22 Oct 2020 14:33:28 +0200 Subject: [PATCH 022/447] chore: update contributors --- package.json | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 8d48271b26..ca88d94868 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.29.0", + "version": "0.29.1", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", @@ -132,39 +132,41 @@ "Friedel Ziegelmayer ", "Maciej Krüger ", "Hugo Dias ", - "Volker Mische ", "dirkmc ", + "Volker Mische ", "Richard Littauer ", - "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", - "Andrew Nesbitt ", "Elven ", + "Andrew Nesbitt ", "Giovanni T. Parra ", "Ryan Bell ", "Thomas Eizinger ", + "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", "Didrik Nordström ", - "Francis Gulotta ", - "Florian-Merle ", + "Henrique Dias ", + "Fei Liu ", + "Irakli Gozalishvili ", + "Ethan Lam ", "Joel Gustafson ", "Julien Bouquillon ", "Kevin Kwok ", - "Felipe Martins ", "Nuno Nogueira ", - "Fei Liu ", - "RasmusErik Voel Jensen ", "Dmitriy Ryajov ", + "RasmusErik Voel Jensen ", + "Diogo Silva ", + "robertkiel ", "Soeren ", "Sönke Hahn ", "Tiago Alves ", - "Diogo Silva ", + "Daijiro Wachi ", "Yusef Napora ", "Zane Starr ", - "Daijiro Wachi ", + "Cindy Wu ", "Chris Bratlien ", "ebinks ", "Bernd Strehl ", "isan_rivkin ", - "Henrique Dias ", - "robertkiel ", - "Irakli Gozalishvili " + "Florian-Merle ", + "Francis Gulotta ", + "Felipe Martins " ] } From 887963436313dd1657c66d74a655683bcd3d125b Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Thu, 22 Oct 2020 14:33:29 +0200 Subject: [PATCH 023/447] chore: release version v0.29.1 --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9681e9374e..3c39591299 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ + +## [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)) + + + # [0.29.0](https://github.com/libp2p/js-libp2p/compare/v0.28.10...v0.29.0) (2020-08-27) From 06f26e586fb2652d2ea1258d3862f3ddd27ab2e0 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Fri, 23 Oct 2020 15:34:59 +0200 Subject: [PATCH 024/447] fix: cleanup open streams on conn close (#791) --- src/upgrader.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/upgrader.js b/src/upgrader.js index fd8fe1195a..92997eb15f 100644 --- a/src/upgrader.js +++ b/src/upgrader.js @@ -306,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) From f82da56901fd9355a5f2e5c4726d7459ff7d0b8d Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Fri, 23 Oct 2020 15:40:53 +0200 Subject: [PATCH 025/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ca88d94868..c52c8f630e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.29.1", + "version": "0.29.2", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From 61c36f9e09f61d630c1b00576e9fb2549bad1dfd Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Fri, 23 Oct 2020 15:40:54 +0200 Subject: [PATCH 026/447] chore: release version v0.29.2 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c39591299..4c915a1eef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ + +## [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)) + + + ## [0.29.1](https://github.com/libp2p/js-libp2p/compare/v0.29.0...v0.29.1) (2020-10-22) From 093c0ea13f9106de037fdf3b243a4709c5832de7 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 4 Nov 2020 13:54:50 +0100 Subject: [PATCH 027/447] feat: resolve multiaddrs before dial (#782) --- doc/CONFIGURATION.md | 8 +- doc/GETTING_STARTED.md | 4 +- examples/libp2p-in-the-browser/index.js | 11 +- package.json | 2 +- src/config.js | 7 +- src/dialer/index.js | 72 +++++++++- src/index.js | 3 +- test/dialing/direct.node.js | 6 +- test/dialing/direct.spec.js | 5 - test/dialing/resolver.spec.js | 176 ++++++++++++++++++++++++ 10 files changed, 267 insertions(+), 27 deletions(-) create mode 100644 test/dialing/resolver.spec.js diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 4983754c2e..527ee35733 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -465,6 +465,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: @@ -474,6 +475,8 @@ const TCP = require('libp2p-tcp') const MPLEX = require('libp2p-mplex') const { NOISE } = require('libp2p-noise') +const { dnsaddrResolver } = require('multiaddr/src/resolvers') + const node = await Libp2p.create({ modules: { transport: [TCP], @@ -483,7 +486,10 @@ const node = await Libp2p.create({ dialer: { maxParallelDials: 100, maxDialsPerPeer: 4, - dialTimeout: 30e3 + dialTimeout: 30e3, + resolvers: { + dnsaddr: dnsaddrResolver + } } ``` diff --git a/doc/GETTING_STARTED.md b/doc/GETTING_STARTED.md index e7398cc1d7..fd180e8428 100644 --- a/doc/GETTING_STARTED.md +++ b/doc/GETTING_STARTED.md @@ -204,8 +204,8 @@ 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({ diff --git a/examples/libp2p-in-the-browser/index.js b/examples/libp2p-in-the-browser/index.js index be03f08b6c..fe5e31afed 100644 --- a/examples/libp2p-in-the-browser/index.js +++ b/examples/libp2p-in-the-browser/index.js @@ -31,12 +31,11 @@ document.addEventListener('DOMContentLoaded', async () => { [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' ] } } diff --git a/package.json b/package.json index c52c8f630e..a1ea90db83 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "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", "multistream-select": "^1.0.0", "mutable-proxy": "^1.0.0", diff --git a/src/config.js b/src/config.js index 7e3d630efe..1cc0f097b4 100644 --- a/src/config.js +++ b/src/config.js @@ -1,6 +1,8 @@ 'use strict' const mergeOptions = require('merge-options') +const { dnsaddrResolver } = require('multiaddr/src/resolvers') + const Constants = require('./constants') const { FaultTolerance } = require('./transport-manager') @@ -20,7 +22,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 diff --git a/src/dialer/index.js b/src/dialer/index.js index fc02b357a1..49d77e467d 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -27,13 +27,15 @@ class Dialer { * @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 @@ -42,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) + } } /** @@ -69,7 +75,7 @@ class Dialer { * @returns {Promise} */ 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) @@ -105,22 +111,28 @@ class Dialer { * * @private * @param {PeerId|Multiaddr|string} peer - A PeerId or Multiaddr - * @returns {DialTarget} + * @returns {Promise} */ - _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 { @@ -190,6 +202,52 @@ class Dialer { log('token %d released', token) this.tokens.push(token) } + + /** + * Resolve multiaddr recursively. + * + * @param {Multiaddr} ma + * @returns {Promise>} + */ + 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>} + */ + 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 diff --git a/src/index.js b/src/index.js index cd900457d6..85547f4702 100644 --- a/src/index.js +++ b/src/index.js @@ -134,7 +134,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) => { diff --git a/test/dialing/direct.node.js b/test/dialing/direct.node.js index 752f462e06..6b89fee4af 100644 --- a/test/dialing/direct.node.js +++ b/test/dialing/direct.node.js @@ -158,9 +158,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, diff --git a/test/dialing/direct.spec.js b/test/dialing/direct.spec.js index e3d60e5960..540f528b65 100644 --- a/test/dialing/direct.spec.js +++ b/test/dialing/direct.spec.js @@ -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, diff --git a/test/dialing/resolver.spec.js b/test/dialing/resolver.spec.js new file mode 100644 index 0000000000..ba81a5c8b2 --- /dev/null +++ b/test/dialing/resolver.spec.js @@ -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) + }) +}) From 8f29a667a1e3be49d8097e13e579dbce5bea775a Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 4 Nov 2020 14:05:08 +0100 Subject: [PATCH 028/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a1ea90db83..41587182fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.29.2", + "version": "0.29.3", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From fef54b2b2c14944fef5751f07dfd3f56680e66de Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 4 Nov 2020 14:05:08 +0100 Subject: [PATCH 029/447] chore: release version v0.29.3 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c915a1eef..d2da849cc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ + +## [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)) + + + ## [0.29.2](https://github.com/libp2p/js-libp2p/compare/v0.29.1...v0.29.2) (2020-10-23) From 824a444f562abf898e960e9784258dc9383792d2 Mon Sep 17 00:00:00 2001 From: Samlior Date: Wed, 18 Nov 2020 17:28:43 +0800 Subject: [PATCH 030/447] docs(fix): fix contentRouting.getMany (#803) --- doc/API.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/API.md b/doc/API.md index 69e0ca4f78..d277ba87c4 100644 --- a/doc/API.md +++ b/doc/API.md @@ -665,7 +665,7 @@ Queries the DHT for the n values stored for the given key (without sorting). // ... const key = '/key' -const { from, val } = await libp2p.contentRouting.get(key) +const records = await libp2p.contentRouting.getMany(key, 2) ``` ### peerRouting.findPeer From d0a9fada32677ee3b810d5c2fb8d5e2c4f5a9fef Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 20 Nov 2020 15:14:01 +0100 Subject: [PATCH 031/447] feat: custom and store self agent version + store self protocol version (#800) * feat: custom and store self protocol and agent version * fix: do not enable custom protocolVersion --- doc/API.md | 1 + src/config.js | 4 ++ src/identify/index.js | 14 ++++++- test/identify/index.spec.js | 78 ++++++++++++++++++++++++++++++++----- 4 files changed, 85 insertions(+), 12 deletions(-) diff --git a/doc/API.md b/doc/API.md index d277ba87c4..f059741721 100644 --- a/doc/API.md +++ b/doc/API.md @@ -92,6 +92,7 @@ Creates an instance of Libp2p. | options.modules | [`Array`](./CONFIGURATION.md#modules) | libp2p [modules](./CONFIGURATION.md#modules) to use | | [options.addresses] | `{ listen: Array, announce: Array, noAnnounce: Array }` | Addresses for transport listening and to advertise to the network | | [options.config] | `object` | libp2p modules configuration and core configuration | +| [options.host] | `{ agentVersion: string }` | libp2p host options | | [options.connectionManager] | [`object`](./CONFIGURATION.md#configuring-connection-manager) | libp2p Connection Manager [configuration](./CONFIGURATION.md#configuring-connection-manager) | | [options.transportManager] | [`object`](./CONFIGURATION.md#configuring-transport-manager) | libp2p transport manager [configuration](./CONFIGURATION.md#configuring-transport-manager) | | [options.datastore] | `object` | must implement [ipfs/interface-datastore](https://github.com/ipfs/interface-datastore) (in memory datastore will be used if not provided) | diff --git a/src/config.js b/src/config.js index 1cc0f097b4..6e0997938d 100644 --- a/src/config.js +++ b/src/config.js @@ -4,6 +4,7 @@ const mergeOptions = require('merge-options') const { dnsaddrResolver } = require('multiaddr/src/resolvers') const Constants = require('./constants') +const { AGENT_VERSION } = require('./identify/consts') const { FaultTolerance } = require('./transport-manager') @@ -27,6 +28,9 @@ const DefaultConfig = { dnsaddr: dnsaddrResolver } }, + host: { + agentVersion: AGENT_VERSION + }, metrics: { enabled: false }, diff --git a/src/identify/index.js b/src/identify/index.js index f42a8b6f94..f39f95f03e 100644 --- a/src/identify/index.js +++ b/src/identify/index.js @@ -83,6 +83,16 @@ class IdentifyService { this._protocols = protocols this.handleMessage = this.handleMessage.bind(this) + + // Store self host metadata + this._host = { + agentVersion: AGENT_VERSION, + protocolVersion: PROTOCOL_VERSION, + ...libp2p._options.host + } + + this.peerStore.metadataBook.set(this.peerId, 'AgentVersion', uint8ArrayFromString(this._host.agentVersion)) + this.peerStore.metadataBook.set(this.peerId, 'ProtocolVersion', uint8ArrayFromString(this._host.protocolVersion)) } /** @@ -246,8 +256,8 @@ class IdentifyService { const signedPeerRecord = await this._getSelfPeerRecord() const message = Message.encode({ - protocolVersion: PROTOCOL_VERSION, - agentVersion: AGENT_VERSION, + protocolVersion: this._host.protocolVersion, + agentVersion: this._host.agentVersion, publicKey, listenAddrs: this._libp2p.multiaddrs.map((ma) => ma.bytes), signedPeerRecord, diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js index 1ccbf67122..74394dd158 100644 --- a/test/identify/index.spec.js +++ b/test/identify/index.spec.js @@ -53,7 +53,8 @@ describe('Identify', () => { peerId: localPeer, connectionManager: new EventEmitter(), peerStore: new PeerStore({ peerId: localPeer }), - multiaddrs: listenMaddrs + multiaddrs: listenMaddrs, + _options: { host: {} } }, protocols }) @@ -63,7 +64,8 @@ describe('Identify', () => { peerId: remotePeer, connectionManager: new EventEmitter(), peerStore: new PeerStore({ peerId: remotePeer }), - multiaddrs: listenMaddrs + multiaddrs: listenMaddrs, + _options: { host: {} } }, protocols }) @@ -106,7 +108,8 @@ describe('Identify', () => { peerId: localPeer, connectionManager: new EventEmitter(), peerStore: new PeerStore({ peerId: localPeer }), - multiaddrs: listenMaddrs + multiaddrs: listenMaddrs, + _options: { host: {} } }, protocols }) @@ -116,7 +119,8 @@ describe('Identify', () => { peerId: remotePeer, connectionManager: new EventEmitter(), peerStore: new PeerStore({ peerId: remotePeer }), - multiaddrs: listenMaddrs + multiaddrs: listenMaddrs, + _options: { host: {} } }, protocols }) @@ -165,7 +169,8 @@ describe('Identify', () => { peerId: localPeer, connectionManager: new EventEmitter(), peerStore: new PeerStore({ peerId: localPeer }), - multiaddrs: [] + multiaddrs: [], + _options: { host: {} } }, protocols }) @@ -174,7 +179,8 @@ describe('Identify', () => { peerId: remotePeer, connectionManager: new EventEmitter(), peerStore: new PeerStore({ peerId: remotePeer }), - multiaddrs: [] + multiaddrs: [], + _options: { host: {} } }, protocols }) @@ -201,6 +207,36 @@ describe('Identify', () => { .and.to.have.property('code', Errors.ERR_INVALID_PEER) }) + it('should store host data and protocol version into metadataBook', () => { + const agentVersion = 'js-project/1.0.0' + const peerStore = new PeerStore({ peerId: localPeer }) + + sinon.spy(peerStore.metadataBook, 'set') + + new IdentifyService({ // eslint-disable-line no-new + libp2p: { + peerId: localPeer, + connectionManager: new EventEmitter(), + peerStore, + multiaddrs: listenMaddrs, + _options: { + host: { + agentVersion + } + } + }, + protocols + }) + + expect(peerStore.metadataBook.set.callCount).to.eql(2) + + const storedAgentVersion = peerStore.metadataBook.getValue(localPeer, 'AgentVersion') + const storedProtocolVersion = peerStore.metadataBook.getValue(localPeer, 'ProtocolVersion') + + expect(agentVersion).to.eql(unit8ArrayToString(storedAgentVersion)) + expect(storedProtocolVersion).to.exist() + }) + describe('push', () => { it('should be able to push identify updates to another peer', async () => { const connectionManager = new EventEmitter() @@ -211,7 +247,8 @@ describe('Identify', () => { peerId: localPeer, connectionManager: new EventEmitter(), peerStore: new PeerStore({ peerId: localPeer }), - multiaddrs: listenMaddrs + multiaddrs: listenMaddrs, + _options: { host: {} } }, protocols: new Map([ [multicodecs.IDENTIFY], @@ -224,7 +261,8 @@ describe('Identify', () => { peerId: remotePeer, connectionManager, peerStore: new PeerStore({ peerId: remotePeer }), - multiaddrs: [] + multiaddrs: [], + _options: { host: {} } } }) @@ -272,7 +310,8 @@ describe('Identify', () => { peerId: localPeer, connectionManager: new EventEmitter(), peerStore: new PeerStore({ peerId: localPeer }), - multiaddrs: listenMaddrs + multiaddrs: listenMaddrs, + _options: { host: {} } }, protocols: new Map([ [multicodecs.IDENTIFY], @@ -285,7 +324,8 @@ describe('Identify', () => { peerId: remotePeer, connectionManager, peerStore: new PeerStore({ peerId: remotePeer }), - multiaddrs: [] + multiaddrs: [], + _options: { host: {} } } }) @@ -404,5 +444,23 @@ describe('Identify', () => { // Verify the streams close await pWaitFor(() => connection.streams.length === 0) }) + + it('should store host data and protocol version into metadataBook', () => { + const agentVersion = 'js-project/1.0.0' + + libp2p = new Libp2p({ + ...baseOptions, + peerId, + host: { + agentVersion + } + }) + + const storedAgentVersion = libp2p.peerStore.metadataBook.getValue(localPeer, 'AgentVersion') + const storedProtocolVersion = libp2p.peerStore.metadataBook.getValue(localPeer, 'ProtocolVersion') + + expect(agentVersion).to.eql(unit8ArrayToString(storedAgentVersion)) + expect(storedProtocolVersion).to.exist() + }) }) }) From e9e4b731a5be2a3c25506d4edf4a2ef84b748e3c Mon Sep 17 00:00:00 2001 From: a1300 Date: Fri, 27 Nov 2020 10:50:35 +0100 Subject: [PATCH 032/447] docs: fix JSDOc for stop and create (#812) (#813) --- src/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index 85547f4702..33ace0c078 100644 --- a/src/index.js +++ b/src/index.js @@ -242,7 +242,7 @@ class Libp2p extends EventEmitter { * Stop the libp2p node by closing its listeners and open connections * * @async - * @returns {void} + * @returns {Promise} */ async stop () { log('libp2p is stopping') @@ -601,7 +601,7 @@ class Libp2p extends EventEmitter { * instance if one is not provided in options. * * @param {object} options - Libp2p configuration options - * @returns {Libp2p} + * @returns {Promise} */ Libp2p.create = async function create (options = {}) { if (options.peerId) { From 73204958ee381cc1bf6bc532a270672d945a822f Mon Sep 17 00:00:00 2001 From: a1300 Date: Mon, 30 Nov 2020 11:15:09 +0100 Subject: [PATCH 033/447] docs: use Libp2p.create() in examples (#811) (#814) --- doc/API.md | 47 +++++++++++++++++++++++++----- examples/chat/src/dialer.js | 4 +-- examples/chat/src/libp2p-bundle.js | 25 +++++++--------- examples/chat/src/listener.js | 4 +-- examples/echo/src/dialer.js | 5 ++-- examples/echo/src/libp2p-bundle.js | 25 +++++++--------- examples/echo/src/listener.js | 4 +-- 7 files changed, 67 insertions(+), 47 deletions(-) diff --git a/doc/API.md b/doc/API.md index f059741721..84d971512a 100644 --- a/doc/API.md +++ b/doc/API.md @@ -114,12 +114,25 @@ For Libp2p configurations and modules details read the [Configuration Document]( ```js const Libp2p = require('libp2p') +const TCP = require('libp2p-tcp') +const MPLEX = require('libp2p-mplex') +const { NOISE } = require('libp2p-noise') + +async function main () { + // specify options + const options = { + modules: { + transport: [TCP], + streamMuxer: [MPLEX], + connEncryption: [NOISE] + } + } -// specify options -const options = {} + // create libp2p + const libp2p = await Libp2p.create(options) +} -// create libp2p -const libp2p = await Libp2p.create(options) +main() ``` Note: The [`PeerId`][peer-id] option is not required and will be generated if it is not provided. @@ -131,12 +144,30 @@ As an alternative, it is possible to create a Libp2p instance with the construct ```js const Libp2p = require('libp2p') +const TCP = require('libp2p-tcp') +const MPLEX = require('libp2p-mplex') +const { NOISE } = require('libp2p-noise') +const PeerId = require('peer-id') + +async function main () { + const peerId = await PeerId.create(); + + // specify options + // peerId is required when Libp2p is instantiated via the constructor + const options = { + peerId, + modules: { + transport: [TCP], + streamMuxer: [MPLEX], + connEncryption: [NOISE] + } + } -// specify options -const options = {} + // create libp2p + const libp2p = new Libp2p(options) +} -// create libp2p -const libp2p = new Libp2p(options) +main() ``` Required keys in the `options` object: diff --git a/examples/chat/src/dialer.js b/examples/chat/src/dialer.js index 12e969c301..10581ed237 100644 --- a/examples/chat/src/dialer.js +++ b/examples/chat/src/dialer.js @@ -3,7 +3,7 @@ const PeerId = require('peer-id') const multiaddr = require('multiaddr') -const Node = require('./libp2p-bundle') +const createLibp2p = require('./libp2p-bundle') const { stdinToStream, streamToConsole } = require('./stream') async function run() { @@ -13,7 +13,7 @@ async function run() { ]) // Create a new libp2p node on localhost with a randomly chosen port - const nodeDialer = new Node({ + const nodeDialer = await createLibp2p({ peerId: idDialer, addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] diff --git a/examples/chat/src/libp2p-bundle.js b/examples/chat/src/libp2p-bundle.js index dd4596708f..c4bfb88d65 100644 --- a/examples/chat/src/libp2p-bundle.js +++ b/examples/chat/src/libp2p-bundle.js @@ -7,21 +7,16 @@ const { NOISE } = require('libp2p-noise') const defaultsDeep = require('@nodeutils/defaults-deep') const libp2p = require('../../..') -class Node extends libp2p { - constructor (_options) { - const defaults = { - modules: { - transport: [ - TCP, - WS - ], - streamMuxer: [ mplex ], - connEncryption: [ NOISE ] - } - } - - super(defaultsDeep(_options, defaults)) +async function createLibp2p(_options) { + const defaults = { + modules: { + transport: [TCP, WS], + streamMuxer: [mplex], + connEncryption: [NOISE], + }, } + + return libp2p.create(defaultsDeep(_options, defaults)) } -module.exports = Node +module.exports = createLibp2p diff --git a/examples/chat/src/listener.js b/examples/chat/src/listener.js index 9b4aa49576..0a64660072 100644 --- a/examples/chat/src/listener.js +++ b/examples/chat/src/listener.js @@ -2,13 +2,13 @@ /* eslint-disable no-console */ const PeerId = require('peer-id') -const Node = require('./libp2p-bundle.js') +const createLibp2p = require('./libp2p-bundle.js') const { stdinToStream, streamToConsole } = require('./stream') async function run() { // Create a new libp2p node with the given multi-address const idListener = await PeerId.createFromJSON(require('./peer-id-listener')) - const nodeListener = new Node({ + const nodeListener = await createLibp2p({ peerId: idListener, addresses: { listen: ['/ip4/0.0.0.0/tcp/10333'] diff --git a/examples/echo/src/dialer.js b/examples/echo/src/dialer.js index 05227bf9ab..aa20208edd 100644 --- a/examples/echo/src/dialer.js +++ b/examples/echo/src/dialer.js @@ -5,9 +5,8 @@ * Dialer Node */ -const multiaddr = require('multiaddr') const PeerId = require('peer-id') -const Node = require('./libp2p-bundle') +const createLibp2p = require('./libp2p-bundle') const pipe = require('it-pipe') async function run() { @@ -17,7 +16,7 @@ async function run() { ]) // Dialer - const dialerNode = new Node({ + const dialerNode = await createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, diff --git a/examples/echo/src/libp2p-bundle.js b/examples/echo/src/libp2p-bundle.js index 0e11a7103d..4f14a6e14f 100644 --- a/examples/echo/src/libp2p-bundle.js +++ b/examples/echo/src/libp2p-bundle.js @@ -8,21 +8,16 @@ const { NOISE } = require('libp2p-noise') const defaultsDeep = require('@nodeutils/defaults-deep') const libp2p = require('../../..') -class Node extends libp2p { - constructor (_options) { - const defaults = { - modules: { - transport: [ - TCP, - WS - ], - streamMuxer: [ mplex ], - connEncryption: [ NOISE ] - } - } - - super(defaultsDeep(_options, defaults)) +async function createLibp2p(_options) { + const defaults = { + modules: { + transport: [TCP, WS], + streamMuxer: [mplex], + connEncryption: [NOISE], + }, } + + return libp2p.create(defaultsDeep(_options, defaults)) } -module.exports = Node +module.exports = createLibp2p diff --git a/examples/echo/src/listener.js b/examples/echo/src/listener.js index fffc57b57e..e4a9cd171b 100644 --- a/examples/echo/src/listener.js +++ b/examples/echo/src/listener.js @@ -6,14 +6,14 @@ */ const PeerId = require('peer-id') -const Node = require('./libp2p-bundle') +const createLibp2p = require('./libp2p-bundle') const pipe = require('it-pipe') async function run() { const listenerId = await PeerId.createFromJSON(require('./id-l')) // Listener libp2p node - const listenerNode = new Node({ + const listenerNode = await createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/tcp/10333'] }, From 8e3bb092794508dbc634769adc8e3d152b99ad6c Mon Sep 17 00:00:00 2001 From: Michael Burns <5170+mburns@users.noreply.github.com> Date: Tue, 1 Dec 2020 18:10:47 +0000 Subject: [PATCH 034/447] chore: remove references to Solarnet (#820) Co-authored-by: Vasco Santos --- examples/discovery-mechanisms/1.js | 13 ++++----- examples/discovery-mechanisms/README.md | 39 ++++++++++--------------- 2 files changed, 20 insertions(+), 32 deletions(-) diff --git a/examples/discovery-mechanisms/1.js b/examples/discovery-mechanisms/1.js index b21f6eddc9..8dbf31d184 100644 --- a/examples/discovery-mechanisms/1.js +++ b/examples/discovery-mechanisms/1.js @@ -10,14 +10,11 @@ const Bootstrap = require('libp2p-bootstrap') // Find this list at: https://github.com/ipfs/js-ipfs/blob/master/src/core/runtime/config-nodejs.json const bootstrapers = [ '/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ', - '/ip4/104.236.176.52/tcp/4001/p2p/QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z', - '/ip4/104.236.179.241/tcp/4001/p2p/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM', - '/ip4/162.243.248.213/tcp/4001/p2p/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm', - '/ip4/128.199.219.111/tcp/4001/p2p/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu', - '/ip4/104.236.76.40/tcp/4001/p2p/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64', - '/ip4/178.62.158.247/tcp/4001/p2p/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd', - '/ip4/178.62.61.185/tcp/4001/p2p/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3', - '/ip4/104.236.151.122/tcp/4001/p2p/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx' + '/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' ] ;(async () => { diff --git a/examples/discovery-mechanisms/README.md b/examples/discovery-mechanisms/README.md index 8234716d13..3b391ca3b5 100644 --- a/examples/discovery-mechanisms/README.md +++ b/examples/discovery-mechanisms/README.md @@ -40,14 +40,11 @@ In this configuration, we use a `bootstrappers` array listing peers to connect _ ```JavaScript const bootstrapers = [ '/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ', - '/ip4/104.236.176.52/tcp/4001/p2p/QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z', - '/ip4/104.236.179.241/tcp/4001/p2p/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM', - '/ip4/162.243.248.213/tcp/4001/p2p/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm', - '/ip4/128.199.219.111/tcp/4001/p2p/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu', - '/ip4/104.236.76.40/tcp/4001/p2p/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64', - '/ip4/178.62.158.247/tcp/4001/p2p/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd', - '/ip4/178.62.61.185/tcp/4001/p2p/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3', - '/ip4/104.236.151.122/tcp/4001/p2p/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx' + '/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' ] ``` @@ -93,23 +90,17 @@ From running [1.js](./1.js), you should see the following: ```bash > node 1.js Discovered: QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ -Discovered: QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z -Discovered: QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM -Discovered: QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm -Discovered: QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu -Discovered: QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64 -Discovered: QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd -Discovered: QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3 -Discovered: QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx +Discovered: QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN +Discovered: QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb +Discovered: QmZa1sAxajnQjVM8WjWXoMbmPd7NsWhfKsPkErzpm9wGkp +Discovered: QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa +Discovered: QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt Connection established to: QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ -Connection established to: QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z -Connection established to: QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM -Connection established to: QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm -Connection established to: QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu -Connection established to: QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64 -Connection established to: QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd -Connection established to: QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3 -Connection established to: QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx +Connection established to: QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN +Connection established to: QmZa1sAxajnQjVM8WjWXoMbmPd7NsWhfKsPkErzpm9wGkp +Connection established to: QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa +Connection established to: QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt +Connection established to: QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb ``` ## 2. MulticastDNS to find other peers in the network From 6350a187c7c207086e42436ccbcabd59af6f5e3d Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 9 Dec 2020 16:13:25 +0100 Subject: [PATCH 035/447] fix: dial self (#826) --- src/errors.js | 1 + src/index.js | 5 +++++ test/connection-manager/index.node.js | 23 ++++++++++++++++++----- test/dialing/direct.spec.js | 15 +++++++++++++++ 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/errors.js b/src/errors.js index 18e600c6dc..cbff9aa82e 100644 --- a/src/errors.js +++ b/src/errors.js @@ -16,6 +16,7 @@ exports.codes = { ERR_NODE_NOT_STARTED: 'ERR_NODE_NOT_STARTED', ERR_ALREADY_ABORTED: 'ERR_ALREADY_ABORTED', ERR_NO_VALID_ADDRESSES: 'ERR_NO_VALID_ADDRESSES', + ERR_DIALED_SELF: 'ERR_DIALED_SELF', ERR_DISCOVERED_SELF: 'ERR_DISCOVERED_SELF', ERR_DUPLICATE_TRANSPORT: 'ERR_DUPLICATE_TRANSPORT', ERR_ENCRYPTION_FAILED: 'ERR_ENCRYPTION_FAILED', diff --git a/src/index.js b/src/index.js index 33ace0c078..1af237b97d 100644 --- a/src/index.js +++ b/src/index.js @@ -335,6 +335,11 @@ class Libp2p extends EventEmitter { */ async dialProtocol (peer, protocols, options) { const { id, multiaddrs } = getPeer(peer) + + if (id.equals(this.peerId)) { + throw errCode(new Error('Cannot dial self'), codes.ERR_DIALED_SELF) + } + let connection = this.connectionManager.get(id) if (!connection) { diff --git a/test/connection-manager/index.node.js b/test/connection-manager/index.node.js index f7820e88f0..c1cc4ebcea 100644 --- a/test/connection-manager/index.node.js +++ b/test/connection-manager/index.node.js @@ -18,10 +18,16 @@ const listenMultiaddr = '/ip4/127.0.0.1/tcp/15002/ws' describe('Connection Manager', () => { let libp2p + let peerIds + + before(async () => { + peerIds = await peerUtils.createPeerId({ number: 2 }) + }) beforeEach(async () => { [libp2p] = await peerUtils.createPeer({ config: { + peerId: peerIds[0], addresses: { listen: [listenMultiaddr] }, @@ -33,12 +39,10 @@ describe('Connection Manager', () => { afterEach(() => libp2p.stop()) it('should filter connections on disconnect, removing the closed one', async () => { - const [localPeer, remotePeer] = await peerUtils.createPeerId({ number: 2 }) - - const conn1 = await mockConnection({ localPeer, remotePeer }) - const conn2 = await mockConnection({ localPeer, remotePeer }) + const conn1 = await mockConnection({ localPeer: peerIds[0], remotePeer: peerIds[1] }) + const conn2 = await mockConnection({ localPeer: peerIds[0], remotePeer: peerIds[1] }) - const id = remotePeer.toB58String() + const id = peerIds[1].toB58String() // Add connection to the connectionManager libp2p.connectionManager.onConnect(conn1) @@ -57,6 +61,7 @@ describe('Connection Manager', () => { it('should add connection on dial and remove on node stop', async () => { const [remoteLibp2p] = await peerUtils.createPeer({ config: { + peerId: peerIds[1], addresses: { listen: ['/ip4/127.0.0.1/tcp/15003/ws'] }, @@ -89,9 +94,16 @@ describe('Connection Manager', () => { }) describe('libp2p.connections', () => { + let peerIds + + before(async () => { + peerIds = await peerUtils.createPeerId({ number: 2 }) + }) + it('libp2p.connections gets the connectionManager conns', async () => { const [libp2p] = await peerUtils.createPeer({ config: { + peerId: peerIds[0], addresses: { listen: ['/ip4/127.0.0.1/tcp/15003/ws'] }, @@ -100,6 +112,7 @@ describe('libp2p.connections', () => { }) const [remoteLibp2p] = await peerUtils.createPeer({ config: { + peerId: peerIds[1], addresses: { listen: ['/ip4/127.0.0.1/tcp/15004/ws'] }, diff --git a/test/dialing/direct.spec.js b/test/dialing/direct.spec.js index 540f528b65..f7aa6d99d3 100644 --- a/test/dialing/direct.spec.js +++ b/test/dialing/direct.spec.js @@ -409,5 +409,20 @@ describe('Dialing (direct, WebSockets)', () => { expect(libp2p.dialer.destroy).to.have.property('callCount', 1) }) + + it('should fail to dial self', async () => { + libp2p = new Libp2p({ + peerId, + modules: { + transport: [Transport], + streamMuxer: [Muxer], + connEncryption: [Crypto] + } + }) + + await expect(libp2p.dial(peerId)) + .to.eventually.be.rejected() + .and.to.have.property('code', ErrorCodes.ERR_DIALED_SELF) + }) }) }) From 8691465a525b45c2737e2e44dd5651b0211f1348 Mon Sep 17 00:00:00 2001 From: Smite Chow Date: Wed, 9 Dec 2020 23:31:17 +0800 Subject: [PATCH 036/447] feat: support custom listener options (#822) * support custom listener options * fix get listener options * add doc to explain custom listener options * add ut * fix code style * Apply suggestions from code review Co-authored-by: Vasco Santos * add missing comma Co-authored-by: Vasco Santos --- doc/CONFIGURATION.md | 29 +++++++++++++++++++++++ src/transport-manager.js | 4 +++- test/transports/transport-manager.node.js | 6 ++++- test/transports/transport-manager.spec.js | 6 ++++- 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 527ee35733..4105551796 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -651,6 +651,35 @@ const node = await Libp2p.create({ }) ``` +During Libp2p startup, transport listeners will be created for the configured listen multiaddrs. Some transports support custom listener options and you can set them using the `listenerOptions` in the transport configuration. For example, [libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star) transport listener supports the configuration of its underlying [simple-peer](https://github.com/feross/simple-peer) ice server(STUN/TURN) config as follows: + +```js +const transportKey = WebRTCStar.prototype[Symbol.toStringTag] +const node = await Libp2p.create({ + modules: { + transport: [WebRTCStar], + streamMuxer: [MPLEX], + connEncryption: [NOISE] + }, + addresses: { + listen: ['/dns4/your-wrtc-star.pub/tcp/443/wss/p2p-webrtc-star'] // your webrtc dns multiaddr + }, + config: { + transport: { + [transportKey]: { + listenerOptions: { + config: { + iceServers: [ + {"urls": ["turn:YOUR.TURN.SERVER:3478"], "username": "YOUR.USER", "credential": "YOUR.PASSWORD"}, + {"urls": ["stun:YOUR.STUN.SERVER:3478"], "username": "", "credential": ""}] + } + } + } + } + } +}) +``` + ## Configuration examples As libp2p is designed to be a modular networking library, its usage will vary based on individual project needs. We've included links to some existing project configurations for your reference, in case you wish to replicate their configuration: diff --git a/src/transport-manager.js b/src/transport-manager.js index e18841bf02..f9cf8629c5 100644 --- a/src/transport-manager.js +++ b/src/transport-manager.js @@ -20,6 +20,7 @@ class TransportManager { this.upgrader = upgrader this._transports = new Map() this._listeners = new Map() + this._listenerOptions = new Map() this.faultTolerance = faultTolerance } @@ -47,6 +48,7 @@ class TransportManager { }) this._transports.set(key, transport) + this._listenerOptions.set(key, transportOptions.listenerOptions || {}) if (!this._listeners.has(key)) { this._listeners.set(key, []) } @@ -154,7 +156,7 @@ class TransportManager { // For each supported multiaddr, create a listener for (const addr of supportedAddrs) { log('creating listener for %s on %s', key, addr) - const listener = transport.createListener({}, this.onConnection) + const listener = transport.createListener(this._listenerOptions.get(key), this.onConnection) this._listeners.get(key).push(listener) // We need to attempt to listen on everything diff --git a/test/transports/transport-manager.node.js b/test/transports/transport-manager.node.js index 1036230acb..cd1e6fb8b5 100644 --- a/test/transports/transport-manager.node.js +++ b/test/transports/transport-manager.node.js @@ -10,6 +10,7 @@ const TransportManager = require('../../src/transport-manager') const Transport = require('libp2p-tcp') const multiaddr = require('multiaddr') const mockUpgrader = require('../utils/mockUpgrader') +const sinon = require('sinon') const addrs = [ multiaddr('/ip4/127.0.0.1/tcp/0'), multiaddr('/ip4/127.0.0.1/tcp/0') @@ -40,7 +41,9 @@ describe('Transport Manager (TCP)', () => { }) it('should be able to listen', async () => { - tm.add(Transport.prototype[Symbol.toStringTag], Transport) + tm.add(Transport.prototype[Symbol.toStringTag], Transport, { listenerOptions: { listen: 'carefully' } }) + const transport = tm._transports.get(Transport.prototype[Symbol.toStringTag]) + const spyListener = sinon.spy(transport, 'createListener') await tm.listen() expect(tm._listeners).to.have.key(Transport.prototype[Symbol.toStringTag]) expect(tm._listeners.get(Transport.prototype[Symbol.toStringTag])).to.have.length(addrs.length) @@ -48,6 +51,7 @@ describe('Transport Manager (TCP)', () => { expect(tm.getAddrs().length).to.equal(addrs.length) await tm.close() expect(tm._listeners.get(Transport.prototype[Symbol.toStringTag])).to.have.length(0) + expect(spyListener.firstCall.firstArg).to.deep.equal({ listen: 'carefully' }) }) it('should be able to dial', async () => { diff --git a/test/transports/transport-manager.spec.js b/test/transports/transport-manager.spec.js index b32b280725..f91de4afde 100644 --- a/test/transports/transport-manager.spec.js +++ b/test/transports/transport-manager.spec.js @@ -125,7 +125,10 @@ describe('libp2p.transportManager', () => { const spy = sinon.spy() const key = spy.prototype[Symbol.toStringTag] = 'TransportSpy' const customOptions = { - another: 'value' + another: 'value', + listenerOptions: { + listen: 'carefully' + } } libp2p = new Libp2p({ peerId, @@ -143,6 +146,7 @@ describe('libp2p.transportManager', () => { expect(libp2p.transportManager).to.exist() // Our transport and circuit relay expect(libp2p.transportManager._transports.size).to.equal(2) + expect(libp2p.transportManager._listenerOptions.size).to.equal(2) expect(spy).to.have.property('callCount', 1) expect(spy.getCall(0)).to.have.deep.property('args', [{ ...customOptions, From 1a5ae74741d67bbc971e252f833edba910bdf4b4 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Wed, 9 Dec 2020 16:42:20 +0100 Subject: [PATCH 037/447] chore: update contributors --- package.json | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 41587182fa..fd22a16def 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.29.3", + "version": "0.29.4", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", @@ -135,6 +135,7 @@ "dirkmc ", "Volker Mische ", "Richard Littauer ", + "a1300 ", "Elven ", "Andrew Nesbitt ", "Giovanni T. Parra ", @@ -142,8 +143,6 @@ "Thomas Eizinger ", "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", "Didrik Nordström ", - "Henrique Dias ", - "Fei Liu ", "Irakli Gozalishvili ", "Ethan Lam ", "Joel Gustafson ", @@ -153,9 +152,11 @@ "Dmitriy Ryajov ", "RasmusErik Voel Jensen ", "Diogo Silva ", - "robertkiel ", + "Samlior ", + "Smite Chow ", "Soeren ", "Sönke Hahn ", + "robertkiel ", "Tiago Alves ", "Daijiro Wachi ", "Yusef Napora ", @@ -164,9 +165,11 @@ "Chris Bratlien ", "ebinks ", "Bernd Strehl ", - "isan_rivkin ", "Florian-Merle ", "Francis Gulotta ", - "Felipe Martins " + "Felipe Martins ", + "isan_rivkin ", + "Henrique Dias ", + "Fei Liu " ] } From 48656712ea4da701798034dd3275f7340e9d9570 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Wed, 9 Dec 2020 16:42:21 +0100 Subject: [PATCH 038/447] chore: release version v0.29.4 --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2da849cc2..5d804f3480 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ + +## [0.29.4](https://github.com/libp2p/js-libp2p/compare/v0.29.3...v0.29.4) (2020-12-09) + + +### Bug Fixes + +* dial self ([#826](https://github.com/libp2p/js-libp2p/issues/826)) ([6350a18](https://github.com/libp2p/js-libp2p/commit/6350a18)) + + +### Features + +* custom and store self agent version + store self protocol version ([#800](https://github.com/libp2p/js-libp2p/issues/800)) ([d0a9fad](https://github.com/libp2p/js-libp2p/commit/d0a9fad)) +* support custom listener options ([#822](https://github.com/libp2p/js-libp2p/issues/822)) ([8691465](https://github.com/libp2p/js-libp2p/commit/8691465)) + + + ## [0.29.3](https://github.com/libp2p/js-libp2p/compare/v0.29.2...v0.29.3) (2020-11-04) From caf66ea1439f6b75a0c321a16bd5c5d7d6a2bd47 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 16 Sep 2020 16:43:09 +0200 Subject: [PATCH 039/447] 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 --- src/circuit/auto-relay.js | 231 +++++++++++ src/circuit/circuit/hop.js | 27 ++ src/circuit/index.js | 20 +- src/circuit/listener.js | 16 +- src/config.js | 4 + src/identify/index.js | 7 +- src/index.js | 9 +- src/peer-store/address-book.js | 2 +- src/transport-manager.js | 7 +- test/relay/auto-relay.node.js | 455 ++++++++++++++++++++++ test/{dialing => relay}/relay.node.js | 4 +- test/transports/transport-manager.node.js | 2 +- test/transports/transport-manager.spec.js | 2 +- 13 files changed, 761 insertions(+), 25 deletions(-) create mode 100644 src/circuit/auto-relay.js create mode 100644 test/relay/auto-relay.node.js rename test/{dialing => relay}/relay.node.js (97%) diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js new file mode 100644 index 0000000000..5617e94eff --- /dev/null +++ b/src/circuit/auto-relay.js @@ -0,0 +1,231 @@ +'use strict' + +const debug = require('debug') +const log = debug('libp2p:auto-relay') +log.error = debug('libp2p:auto-relay:error') + +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 circuitProtoCode = 290 +const hopMetadataKey = 'hop_relay' +const hopMetadataValue = 'true' + +class AutoRelay { + /** + * Creates an instance of AutoRelay. + * @constructor + * @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} + */ + 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} props.protocols + * @return {Promise} + */ + 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(circuitProtoCode)) { + 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, hopMetadataKey, uint8ArrayFromString(hopMetadataValue)) + await this._addListenRelay(connection, id) + } + } catch (err) { + log.error(err) + } + } + + /** + * Peer disconnects. + * @param {Connection} connection connection to the peer + * @return {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 + * @return {Promise} + */ + 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 + + try { + const remoteAddrs = this._peerStore.addressBook.get(connection.remotePeer) + // TODO: HOP Relays should avoid advertising private addresses! + remoteMultiaddr = remoteAddrs.find(a => a.isCertified).multiaddr // Get first announced address certified + } catch (_) { + log.error(`${id} does not have announced certified multiaddrs`) + return + } + + 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)]) + // TODO: push announce multiaddrs update + // await this._libp2p.identifyService.pushToPeerStore() + } catch (err) { + log.error(err) + this._listenRelays.delete(id) + } + } + + /** + * Remove listen relay. + * @private + * @param {string} id peer identifier string. + * @return {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} [peersToIgnore] + * @return {Promise} + */ + 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(hopMetadataKey) + + // Continue to next if it does not support Hop + if (!supportsHop || uint8ArrayToString(supportsHop) !== hopMetadataValue) { + 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 + } + } + + // TODO: Try to find relays to hop on the network + } +} + +module.exports = AutoRelay diff --git a/src/circuit/circuit/hop.js b/src/circuit/circuit/hop.js index f497f33a32..114e2768fe 100644 --- a/src/circuit/circuit/hop.js +++ b/src/circuit/circuit/hop.js @@ -116,6 +116,33 @@ 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} + */ +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 * diff --git a/src/circuit/index.js b/src/circuit/index.js index 15746c907c..705dcdad82 100644 --- a/src/circuit/index.js +++ b/src/circuit/index.js @@ -1,16 +1,18 @@ '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 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 AutoRelay = require('./auto-relay') const { relay: multicodec } = require('./multicodec') const createListener = require('./listener') const { handleCanHop, handleHop, hop } = require('./circuit/hop') @@ -35,11 +37,19 @@ class Circuit { this._libp2p = libp2p this.peerId = libp2p.peerId this._registrar.handle(multicodec, this._onProtocol.bind(this)) + + // Create autoRelay if enabled + this._autoRelay = this._options.autoRelay.enabled && new AutoRelay({ libp2p, ...this._options.autoRelay }) } - async _onProtocol ({ connection, stream, protocol }) { + async _onProtocol ({ connection, stream }) { const streamHandler = new StreamHandler({ stream }) const request = await streamHandler.read() + + if (!request) { + return + } + const circuit = this let virtualConnection @@ -163,7 +173,7 @@ class Circuit { // Called on successful HOP and STOP requests this.handler = handler - return createListener(this, options) + return createListener(this._libp2p, options) } /** diff --git a/src/circuit/listener.js b/src/circuit/listener.js index 76870501dc..f8caff0b41 100644 --- a/src/circuit/listener.js +++ b/src/circuit/listener.js @@ -8,13 +8,23 @@ 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) { + // TODO push announce multiaddrs update + // libp2p.identifyService.pushToPeerStore() + } + }) + /** * Add swarm handler and listen for incoming connections * @@ -24,7 +34,7 @@ module.exports = (circuit) => { 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) diff --git a/src/config.js b/src/config.js index 6e0997938d..18e3201e29 100644 --- a/src/config.js +++ b/src/config.js @@ -63,6 +63,10 @@ const DefaultConfig = { hop: { enabled: false, active: false + }, + autoRelay: { + enabled: false, + maxListeners: 2 } }, transport: {} diff --git a/src/identify/index.js b/src/identify/index.js index f39f95f03e..d24ce0bf6b 100644 --- a/src/identify/index.js +++ b/src/identify/index.js @@ -131,13 +131,12 @@ class IdentifyService { /** * Calls `push` for all peers in the `peerStore` that are connected - * - * @param {PeerStore} peerStore + * @returns {void} */ - pushToPeerStore (peerStore) { + pushToPeerStore () { 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) } diff --git a/src/index.js b/src/index.js index 1af237b97d..c6741709ef 100644 --- a/src/index.js +++ b/src/index.js @@ -438,7 +438,7 @@ class Libp2p extends EventEmitter { // Only push if libp2p is running if (this.isStarted() && this.identifyService) { - this.identifyService.pushToPeerStore(this.peerStore) + this.identifyService.pushToPeerStore() } } @@ -456,13 +456,14 @@ class Libp2p extends EventEmitter { // Only push if libp2p is running if (this.isStarted() && this.identifyService) { - this.identifyService.pushToPeerStore(this.peerStore) + this.identifyService.pushToPeerStore() } } 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() diff --git a/src/peer-store/address-book.js b/src/peer-store/address-book.js index c8fa2ec6f5..88ed327b3d 100644 --- a/src/peer-store/address-book.js +++ b/src/peer-store/address-book.js @@ -270,7 +270,7 @@ class AddressBook extends Book { * * @override * @param {PeerId} peerId - * @returns {Array} + * @returns {Array
|undefined} */ get (peerId) { if (!PeerId.isPeerId(peerId)) { diff --git a/src/transport-manager.js b/src/transport-manager.js index f9cf8629c5..a324f07afe 100644 --- a/src/transport-manager.js +++ b/src/transport-manager.js @@ -139,11 +139,10 @@ class TransportManager { * Starts listeners for each listen Multiaddr. * * @async + * @param {Array} 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 } diff --git a/test/relay/auto-relay.node.js b/test/relay/auto-relay.node.js new file mode 100644 index 0000000000..2a4ba20d57 --- /dev/null +++ b/test/relay/auto-relay.node.js @@ -0,0 +1,455 @@ +'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 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.transportManager._transports.get('Circuit')._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.transportManager._transports.get('Circuit')._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 + + // 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()) + + // Spy if identify push is fired + sinon.spy(relayLibp2p1.identifyService, 'pushToPeerStore') + + // 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) + // TODO: identify-push expect(relayLibp2p1.identifyService.pushToPeerStore.callCount).to.equal(1) + }) + + 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.transportManager._transports.get('Circuit')._autoRelay + autoRelay2 = relayLibp2p2.transportManager._transports.get('Circuit')._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) + }) + }) +}) diff --git a/test/dialing/relay.node.js b/test/relay/relay.node.js similarity index 97% rename from test/dialing/relay.node.js rename to test/relay/relay.node.js index a591940801..67f90a7f98 100644 --- a/test/dialing/relay.node.js +++ b/test/relay/relay.node.js @@ -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 diff --git a/test/transports/transport-manager.node.js b/test/transports/transport-manager.node.js index cd1e6fb8b5..58bc8737b6 100644 --- a/test/transports/transport-manager.node.js +++ b/test/transports/transport-manager.node.js @@ -56,7 +56,7 @@ describe('Transport Manager (TCP)', () => { 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() diff --git a/test/transports/transport-manager.spec.js b/test/transports/transport-manager.spec.js index f91de4afde..9f302b6cfa 100644 --- a/test/transports/transport-manager.spec.js +++ b/test/transports/transport-manager.spec.js @@ -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) }) From 4d1fcdb3d2dcd2fe265b3c20c611e0490696f99d Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 10 Sep 2020 11:53:33 +0200 Subject: [PATCH 040/447] chore: auto relay multiaddr update push --- src/circuit/auto-relay.js | 4 ++-- src/circuit/listener.js | 4 ++-- src/identify/index.js | 30 ++++++++++++++++++++++-------- src/index.js | 2 +- test/relay/auto-relay.node.js | 11 ++++++++--- 5 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js index 5617e94eff..71e951311b 100644 --- a/src/circuit/auto-relay.js +++ b/src/circuit/auto-relay.js @@ -143,8 +143,8 @@ class AutoRelay { try { await this._transportManager.listen([multiaddr(listenAddr)]) - // TODO: push announce multiaddrs update - // await this._libp2p.identifyService.pushToPeerStore() + // Announce multiaddrs update on listen success + await this._libp2p.identifyService.pushToPeerStore() } catch (err) { log.error(err) this._listenRelays.delete(id) diff --git a/src/circuit/listener.js b/src/circuit/listener.js index f8caff0b41..59ca0cec2c 100644 --- a/src/circuit/listener.js +++ b/src/circuit/listener.js @@ -20,8 +20,8 @@ module.exports = (libp2p) => { const deleted = listeningAddrs.delete(connection.remotePeer.toB58String()) if (deleted) { - // TODO push announce multiaddrs update - // libp2p.identifyService.pushToPeerStore() + // Announce multiaddrs update on listen success + libp2p.identifyService.pushToPeerStore() } }) diff --git a/src/identify/index.js b/src/identify/index.js index d24ce0bf6b..3e3b01ab7e 100644 --- a/src/identify/index.js +++ b/src/identify/index.js @@ -329,13 +329,30 @@ class IdentifyService { * @returns {Uint8Array} */ async _getSelfPeerRecord () { - const selfSignedPeerRecord = this.peerStore.addressBook.getRawEnvelope(this.peerId) + // Update self peer record if needed + await this._createOrUpdateSelfPeerRecord() - // TODO: support invalidation when dynamic multiaddrs are supported - if (selfSignedPeerRecord) { - return selfSignedPeerRecord + return this.peerStore.addressBook.getRawEnvelope(this.peerId) + } + + /** + * Creates or updates the self peer record if it exists and is outdated. + * @return {Promise} + */ + async _createOrUpdateSelfPeerRecord () { + const selfPeerRecordEnvelope = await this.peerStore.addressBook.getPeerRecord(this.peerId) + + if (selfPeerRecordEnvelope) { + const peerRecord = PeerRecord.createFromProtobuf(selfPeerRecordEnvelope.payload) + + const mIntersection = peerRecord.multiaddrs.filter((m) => this._libp2p.multiaddrs.some((newM) => m.equals(newM))) + if (mIntersection.length === this._libp2p.multiaddrs.length) { + // Same multiaddrs as already existing in the record, no need to proceed + return + } } + // Create / Update Peer record try { const peerRecord = new PeerRecord({ peerId: this.peerId, @@ -343,12 +360,9 @@ class IdentifyService { }) 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') + log.error('failed to create self peer record') } - return null } } diff --git a/src/index.js b/src/index.js index c6741709ef..8b89bddb20 100644 --- a/src/index.js +++ b/src/index.js @@ -259,6 +259,7 @@ class Libp2p extends EventEmitter { await this.peerStore.stop() await this.connectionManager.stop() + ping.unmount(this) await Promise.all([ this.pubsub && this.pubsub.stop(), this._dht && this._dht.stop(), @@ -267,7 +268,6 @@ class Libp2p extends EventEmitter { await this.transportManager.close() - ping.unmount(this) this.dialer.destroy() } catch (err) { if (err) { diff --git a/test/relay/auto-relay.node.js b/test/relay/auto-relay.node.js index 2a4ba20d57..96f94cd7bb 100644 --- a/test/relay/auto-relay.node.js +++ b/test/relay/auto-relay.node.js @@ -259,6 +259,9 @@ describe('auto-relay', () => { 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) @@ -268,8 +271,8 @@ describe('auto-relay', () => { expect(autoRelay1._listenRelays.size).to.equal(1) expect(relayLibp2p1.multiaddrs[originalMultiaddrs1Length].getPeerId()).to.eql(relayLibp2p2.peerId.toB58String()) - // Spy if identify push is fired - sinon.spy(relayLibp2p1.identifyService, 'pushToPeerStore') + // 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) @@ -277,7 +280,9 @@ describe('auto-relay', () => { // Wait for removed listening on the relay await pWaitFor(() => relayLibp2p1.multiaddrs.length === originalMultiaddrs1Length) expect(autoRelay1._listenRelays.size).to.equal(0) - // TODO: identify-push expect(relayLibp2p1.identifyService.pushToPeerStore.callCount).to.equal(1) + + // 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 () => { From 74bdfd1024be2ba68a395f891976db3f5b00704a Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 23 Sep 2020 11:19:59 +0200 Subject: [PATCH 041/447] chore: _isStarted is false when stop starts --- src/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index 8b89bddb20..b62df6ee53 100644 --- a/src/index.js +++ b/src/index.js @@ -248,6 +248,7 @@ class Libp2p extends EventEmitter { log('libp2p is stopping') try { + this._isStarted = false for (const service of this._discovery.values()) { service.removeListener('peer', this._onDiscoveryPeer) } @@ -259,7 +260,6 @@ class Libp2p extends EventEmitter { await this.peerStore.stop() await this.connectionManager.stop() - ping.unmount(this) await Promise.all([ this.pubsub && this.pubsub.stop(), this._dht && this._dht.stop(), @@ -268,6 +268,8 @@ class Libp2p extends EventEmitter { await this.transportManager.close() + ping.unmount(this) + this.dialer.destroy() } catch (err) { if (err) { @@ -275,7 +277,6 @@ class Libp2p extends EventEmitter { this.emit('error', err) } } - this._isStarted = false log('libp2p has stopped') } From 7b93ece7f2412ac226f6e6fe4dd9d7d7f78e3694 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 23 Sep 2020 13:14:53 +0200 Subject: [PATCH 042/447] chore: use listening events to create self peer record on updates --- doc/API.md | 9 ++++++ src/circuit/auto-relay.js | 3 +- src/circuit/listener.js | 4 +-- src/identify/index.js | 56 ++++++++++++++++++------------------- src/index.js | 1 - src/transport-manager.js | 14 +++++++++- test/identify/index.spec.js | 10 +++++++ 7 files changed, 63 insertions(+), 34 deletions(-) diff --git a/doc/API.md b/doc/API.md index 84d971512a..11ddeb56a7 100644 --- a/doc/API.md +++ b/doc/API.md @@ -73,6 +73,7 @@ * [`libp2p`](#libp2p) * [`libp2p.connectionManager`](#libp2pconnectionmanager) * [`libp2p.peerStore`](#libp2ppeerStore) + * [`libp2p.transportManager`](#libp2ptransportmanager) * [Types](#types) * [`Stats`](#stats) @@ -2019,6 +2020,14 @@ This event will be triggered anytime we are disconnected from another peer, rega - `peerId`: instance of [`PeerId`][peer-id] - `protocols`: array of known, supported protocols for the peer (string identifiers) +### libp2p.transportManager + +#### Listening addresses change + +This event will be triggered anytime the listening addresses change. + +`libp2p.transportManager.on('listening', () => {})` + ## Types ### Stats diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js index 71e951311b..bec8c3a8f0 100644 --- a/src/circuit/auto-relay.js +++ b/src/circuit/auto-relay.js @@ -143,8 +143,7 @@ class AutoRelay { try { await this._transportManager.listen([multiaddr(listenAddr)]) - // Announce multiaddrs update on listen success - await this._libp2p.identifyService.pushToPeerStore() + // Announce multiaddrs will update on listen success by TransportManager event being triggered } catch (err) { log.error(err) this._listenRelays.delete(id) diff --git a/src/circuit/listener.js b/src/circuit/listener.js index 59ca0cec2c..207150e8a8 100644 --- a/src/circuit/listener.js +++ b/src/circuit/listener.js @@ -20,8 +20,8 @@ module.exports = (libp2p) => { const deleted = listeningAddrs.delete(connection.remotePeer.toB58String()) if (deleted) { - // Announce multiaddrs update on listen success - libp2p.identifyService.pushToPeerStore() + // Announce listen addresses change + listener.emit('listening') } }) diff --git a/src/identify/index.js b/src/identify/index.js index 3e3b01ab7e..44e2f63076 100644 --- a/src/identify/index.js +++ b/src/identify/index.js @@ -64,11 +64,10 @@ class IdentifyService { */ this.connectionManager = libp2p.connectionManager - this.connectionManager.on('peer:connect', (connection) => { - const peerId = connection.remotePeer - - this.identify(connection, peerId).catch(log.error) - }) + /** + * @property {TransportManager} + */ + this.transportManager = libp2p.transportManager /** * @property {PeerId} @@ -93,6 +92,15 @@ class IdentifyService { this.peerStore.metadataBook.set(this.peerId, 'AgentVersion', uint8ArrayFromString(this._host.agentVersion)) this.peerStore.metadataBook.set(this.peerId, 'ProtocolVersion', uint8ArrayFromString(this._host.protocolVersion)) + this.connectionManager.on('peer:connect', (connection) => { + this.identify(connection).catch(log.error) + }) + + // When new addresses are used for listening, update self peer record + this.transportManager.on('listening', async () => { + await this._createSelfPeerRecord() + this.pushToPeerStore() + }) } /** @@ -325,34 +333,23 @@ class IdentifyService { /** * Get self signed peer record raw envelope. - * - * @returns {Uint8Array} + * @return {Promise} */ - async _getSelfPeerRecord () { - // Update self peer record if needed - await this._createOrUpdateSelfPeerRecord() + _getSelfPeerRecord () { + const selfSignedPeerRecord = this.peerStore.addressBook.getRawEnvelope(this.peerId) - return this.peerStore.addressBook.getRawEnvelope(this.peerId) + if (selfSignedPeerRecord) { + return selfSignedPeerRecord + } + + return this._createSelfPeerRecord() } /** - * Creates or updates the self peer record if it exists and is outdated. - * @return {Promise} + * Create self signed peer record raw envelope. + * @return {Uint8Array} */ - async _createOrUpdateSelfPeerRecord () { - const selfPeerRecordEnvelope = await this.peerStore.addressBook.getPeerRecord(this.peerId) - - if (selfPeerRecordEnvelope) { - const peerRecord = PeerRecord.createFromProtobuf(selfPeerRecordEnvelope.payload) - - const mIntersection = peerRecord.multiaddrs.filter((m) => this._libp2p.multiaddrs.some((newM) => m.equals(newM))) - if (mIntersection.length === this._libp2p.multiaddrs.length) { - // Same multiaddrs as already existing in the record, no need to proceed - return - } - } - - // Create / Update Peer record + async _createSelfPeerRecord () { try { const peerRecord = new PeerRecord({ peerId: this.peerId, @@ -360,9 +357,12 @@ class IdentifyService { }) 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 create self peer record') + log.error('failed to get self peer record') } + return null } } diff --git a/src/index.js b/src/index.js index b62df6ee53..5c5456af57 100644 --- a/src/index.js +++ b/src/index.js @@ -269,7 +269,6 @@ class Libp2p extends EventEmitter { await this.transportManager.close() ping.unmount(this) - this.dialer.destroy() } catch (err) { if (err) { diff --git a/src/transport-manager.js b/src/transport-manager.js index a324f07afe..d7b2af801d 100644 --- a/src/transport-manager.js +++ b/src/transport-manager.js @@ -1,5 +1,6 @@ 'use strict' +const { EventEmitter } = require('events') const pSettle = require('p-settle') const { codes } = require('./errors') const errCode = require('err-code') @@ -7,7 +8,11 @@ const debug = require('debug') const log = debug('libp2p:transports') log.error = debug('libp2p:transports:error') -class TransportManager { +/** + * Responsible for managing the transports and their listeners. + * @fires TransportManager#listening Emitted when listening addresses change. + */ +class TransportManager extends EventEmitter { /** * @class * @param {object} options @@ -16,6 +21,8 @@ class TransportManager { * @param {boolean} [options.faultTolerance = FAULT_TOLERANCE.FATAL_ALL] - Address listen error tolerance. */ constructor ({ libp2p, upgrader, faultTolerance = FAULT_TOLERANCE.FATAL_ALL }) { + super() + this.libp2p = libp2p this.upgrader = upgrader this._transports = new Map() @@ -65,6 +72,7 @@ class TransportManager { log('closing listeners for %s', key) while (listeners.length) { const listener = listeners.pop() + listener.removeAllListeners('listening') tasks.push(listener.close()) } } @@ -158,6 +166,9 @@ class TransportManager { const listener = transport.createListener(this._listenerOptions.get(key), this.onConnection) this._listeners.get(key).push(listener) + // Track listen events + listener.on('listening', () => this.emit('listening')) + // We need to attempt to listen on everything tasks.push(listener.listen(addr)) } @@ -202,6 +213,7 @@ class TransportManager { if (this._listeners.has(key)) { // Close any running listeners for (const listener of this._listeners.get(key)) { + listener.removeAllListeners('listening') await listener.close() } } diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js index 74394dd158..3b3a631cfd 100644 --- a/test/identify/index.spec.js +++ b/test/identify/index.spec.js @@ -52,6 +52,7 @@ describe('Identify', () => { libp2p: { peerId: localPeer, connectionManager: new EventEmitter(), + transportManager: new EventEmitter(), peerStore: new PeerStore({ peerId: localPeer }), multiaddrs: listenMaddrs, _options: { host: {} } @@ -63,6 +64,7 @@ describe('Identify', () => { libp2p: { peerId: remotePeer, connectionManager: new EventEmitter(), + transportManager: new EventEmitter(), peerStore: new PeerStore({ peerId: remotePeer }), multiaddrs: listenMaddrs, _options: { host: {} } @@ -107,6 +109,7 @@ describe('Identify', () => { libp2p: { peerId: localPeer, connectionManager: new EventEmitter(), + transportManager: new EventEmitter(), peerStore: new PeerStore({ peerId: localPeer }), multiaddrs: listenMaddrs, _options: { host: {} } @@ -118,6 +121,7 @@ describe('Identify', () => { libp2p: { peerId: remotePeer, connectionManager: new EventEmitter(), + transportManager: new EventEmitter(), peerStore: new PeerStore({ peerId: remotePeer }), multiaddrs: listenMaddrs, _options: { host: {} } @@ -168,6 +172,7 @@ describe('Identify', () => { libp2p: { peerId: localPeer, connectionManager: new EventEmitter(), + transportManager: new EventEmitter(), peerStore: new PeerStore({ peerId: localPeer }), multiaddrs: [], _options: { host: {} } @@ -178,6 +183,7 @@ describe('Identify', () => { libp2p: { peerId: remotePeer, connectionManager: new EventEmitter(), + transportManager: new EventEmitter(), peerStore: new PeerStore({ peerId: remotePeer }), multiaddrs: [], _options: { host: {} } @@ -246,6 +252,7 @@ describe('Identify', () => { libp2p: { peerId: localPeer, connectionManager: new EventEmitter(), + transportManager: new EventEmitter(), peerStore: new PeerStore({ peerId: localPeer }), multiaddrs: listenMaddrs, _options: { host: {} } @@ -260,6 +267,7 @@ describe('Identify', () => { libp2p: { peerId: remotePeer, connectionManager, + transportManager: new EventEmitter(), peerStore: new PeerStore({ peerId: remotePeer }), multiaddrs: [], _options: { host: {} } @@ -309,6 +317,7 @@ describe('Identify', () => { libp2p: { peerId: localPeer, connectionManager: new EventEmitter(), + transportManager: new EventEmitter(), peerStore: new PeerStore({ peerId: localPeer }), multiaddrs: listenMaddrs, _options: { host: {} } @@ -323,6 +332,7 @@ describe('Identify', () => { libp2p: { peerId: remotePeer, connectionManager, + transportManager: new EventEmitter(), peerStore: new PeerStore({ peerId: remotePeer }), multiaddrs: [], _options: { host: {} } From 43eda43f06c6b048fea9128c8877c2454aacf60b Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 23 Sep 2020 18:45:01 +0200 Subject: [PATCH 043/447] chore: create signed peer record on new listen addresses in transport manager --- doc/API.md | 9 ---- src/circuit/listener.js | 2 +- src/identify/index.js | 52 +++-------------------- src/transport-manager.js | 39 ++++++++++++----- test/dialing/direct.spec.js | 3 -- test/identify/index.spec.js | 39 +++++++++++------ test/transports/transport-manager.node.js | 18 +++++++- 7 files changed, 80 insertions(+), 82 deletions(-) diff --git a/doc/API.md b/doc/API.md index 11ddeb56a7..84d971512a 100644 --- a/doc/API.md +++ b/doc/API.md @@ -73,7 +73,6 @@ * [`libp2p`](#libp2p) * [`libp2p.connectionManager`](#libp2pconnectionmanager) * [`libp2p.peerStore`](#libp2ppeerStore) - * [`libp2p.transportManager`](#libp2ptransportmanager) * [Types](#types) * [`Stats`](#stats) @@ -2020,14 +2019,6 @@ This event will be triggered anytime we are disconnected from another peer, rega - `peerId`: instance of [`PeerId`][peer-id] - `protocols`: array of known, supported protocols for the peer (string identifiers) -### libp2p.transportManager - -#### Listening addresses change - -This event will be triggered anytime the listening addresses change. - -`libp2p.transportManager.on('listening', () => {})` - ## Types ### Stats diff --git a/src/circuit/listener.js b/src/circuit/listener.js index 207150e8a8..02e371fb8b 100644 --- a/src/circuit/listener.js +++ b/src/circuit/listener.js @@ -21,7 +21,7 @@ module.exports = (libp2p) => { if (deleted) { // Announce listen addresses change - listener.emit('listening') + listener.emit('close') } }) diff --git a/src/identify/index.js b/src/identify/index.js index 44e2f63076..58ab2e956b 100644 --- a/src/identify/index.js +++ b/src/identify/index.js @@ -64,11 +64,6 @@ class IdentifyService { */ this.connectionManager = libp2p.connectionManager - /** - * @property {TransportManager} - */ - this.transportManager = libp2p.transportManager - /** * @property {PeerId} */ @@ -96,10 +91,11 @@ class IdentifyService { this.identify(connection).catch(log.error) }) - // When new addresses are used for listening, update self peer record - this.transportManager.on('listening', async () => { - await this._createSelfPeerRecord() - this.pushToPeerStore() + // When self multiaddrs change, trigger identify-push + this.peerStore.on('change:multiaddrs', ({ peerId }) => { + if (peerId.toString() === this.peerId.toString()) { + this.pushToPeerStore() + } }) } @@ -110,7 +106,7 @@ class IdentifyService { * @returns {Promise} */ 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()) @@ -260,7 +256,7 @@ class IdentifyService { publicKey = this.peerId.pubKey.bytes } - const signedPeerRecord = await this._getSelfPeerRecord() + const signedPeerRecord = await this.peerStore.addressBook.getRawEnvelope(this.peerId) const message = Message.encode({ protocolVersion: this._host.protocolVersion, @@ -330,40 +326,6 @@ class IdentifyService { // Update the protocols this.peerStore.protoBook.set(id, message.protocols) } - - /** - * Get self signed peer record raw envelope. - * @return {Promise} - */ - _getSelfPeerRecord () { - const selfSignedPeerRecord = this.peerStore.addressBook.getRawEnvelope(this.peerId) - - if (selfSignedPeerRecord) { - return selfSignedPeerRecord - } - - return this._createSelfPeerRecord() - } - - /** - * Create self signed peer record raw envelope. - * @return {Uint8Array} - */ - async _createSelfPeerRecord () { - 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 diff --git a/src/transport-manager.js b/src/transport-manager.js index d7b2af801d..64aa18eceb 100644 --- a/src/transport-manager.js +++ b/src/transport-manager.js @@ -1,6 +1,5 @@ 'use strict' -const { EventEmitter } = require('events') const pSettle = require('p-settle') const { codes } = require('./errors') const errCode = require('err-code') @@ -8,11 +7,10 @@ const debug = require('debug') const log = debug('libp2p:transports') log.error = debug('libp2p:transports:error') -/** - * Responsible for managing the transports and their listeners. - * @fires TransportManager#listening Emitted when listening addresses change. - */ -class TransportManager extends EventEmitter { +const Envelope = require('./record/envelope') +const PeerRecord = require('./record/peer-record') + +class TransportManager { /** * @class * @param {object} options @@ -21,8 +19,6 @@ class TransportManager extends EventEmitter { * @param {boolean} [options.faultTolerance = FAULT_TOLERANCE.FATAL_ALL] - Address listen error tolerance. */ constructor ({ libp2p, upgrader, faultTolerance = FAULT_TOLERANCE.FATAL_ALL }) { - super() - this.libp2p = libp2p this.upgrader = upgrader this._transports = new Map() @@ -73,6 +69,7 @@ class TransportManager extends EventEmitter { while (listeners.length) { const listener = listeners.pop() listener.removeAllListeners('listening') + listener.removeAllListeners('close') tasks.push(listener.close()) } } @@ -166,8 +163,9 @@ class TransportManager extends EventEmitter { const listener = transport.createListener(this._listenerOptions.get(key), this.onConnection) this._listeners.get(key).push(listener) - // Track listen events - listener.on('listening', () => this.emit('listening')) + // Track listen/close events + listener.on('listening', () => this._createSelfPeerRecord()) + listener.on('close', () => this._createSelfPeerRecord()) // We need to attempt to listen on everything tasks.push(listener.listen(addr)) @@ -214,6 +212,7 @@ class TransportManager extends EventEmitter { // Close any running listeners for (const listener of this._listeners.get(key)) { listener.removeAllListeners('listening') + listener.removeAllListeners('close') await listener.close() } } @@ -236,6 +235,26 @@ class TransportManager extends EventEmitter { await Promise.all(tasks) } + + /** + * Create self signed peer record raw envelope. + * @return {Uint8Array} + */ + async _createSelfPeerRecord () { + try { + const peerRecord = new PeerRecord({ + peerId: this.libp2p.peerId, + multiaddrs: this.libp2p.multiaddrs + }) + const envelope = await Envelope.seal(peerRecord, this.libp2p.peerId) + this.libp2p.peerStore.addressBook.consumePeerRecord(envelope) + + return this.libp2p.peerStore.addressBook.getRawEnvelope(this.libp2p.peerId) + } catch (err) { + log.error('failed to get self peer record') + } + return null + } } /** diff --git a/test/dialing/direct.spec.js b/test/dialing/direct.spec.js index f7aa6d99d3..8ef78a915b 100644 --- a/test/dialing/direct.spec.js +++ b/test/dialing/direct.spec.js @@ -349,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 @@ -358,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) }) diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js index 3b3a631cfd..7356497f08 100644 --- a/test/identify/index.spec.js +++ b/test/identify/index.spec.js @@ -20,6 +20,7 @@ const { IdentifyService, multicodecs } = require('../../src/identify') const Peers = require('../fixtures/peers') const Libp2p = require('../../src') const Envelope = require('../../src/record/envelope') +const PeerRecord = require('../../src/record/peer-record') const PeerStore = require('../../src/peer-store') const baseOptions = require('../utils/base-options.browser') const pkg = require('../../package.json') @@ -52,7 +53,6 @@ describe('Identify', () => { libp2p: { peerId: localPeer, connectionManager: new EventEmitter(), - transportManager: new EventEmitter(), peerStore: new PeerStore({ peerId: localPeer }), multiaddrs: listenMaddrs, _options: { host: {} } @@ -64,7 +64,6 @@ describe('Identify', () => { libp2p: { peerId: remotePeer, connectionManager: new EventEmitter(), - transportManager: new EventEmitter(), peerStore: new PeerStore({ peerId: remotePeer }), multiaddrs: listenMaddrs, _options: { host: {} } @@ -82,6 +81,9 @@ describe('Identify', () => { sinon.spy(localIdentify.peerStore.addressBook, 'consumePeerRecord') sinon.spy(localIdentify.peerStore.protoBook, 'set') + // Transport Manager creates signed peer record + await _createSelfPeerRecord(remoteIdentify._libp2p) + // Run identify await Promise.all([ localIdentify.identify(localConnectionMock), @@ -109,7 +111,6 @@ describe('Identify', () => { libp2p: { peerId: localPeer, connectionManager: new EventEmitter(), - transportManager: new EventEmitter(), peerStore: new PeerStore({ peerId: localPeer }), multiaddrs: listenMaddrs, _options: { host: {} } @@ -121,7 +122,6 @@ describe('Identify', () => { libp2p: { peerId: remotePeer, connectionManager: new EventEmitter(), - transportManager: new EventEmitter(), peerStore: new PeerStore({ peerId: remotePeer }), multiaddrs: listenMaddrs, _options: { host: {} } @@ -172,7 +172,6 @@ describe('Identify', () => { libp2p: { peerId: localPeer, connectionManager: new EventEmitter(), - transportManager: new EventEmitter(), peerStore: new PeerStore({ peerId: localPeer }), multiaddrs: [], _options: { host: {} } @@ -183,7 +182,6 @@ describe('Identify', () => { libp2p: { peerId: remotePeer, connectionManager: new EventEmitter(), - transportManager: new EventEmitter(), peerStore: new PeerStore({ peerId: remotePeer }), multiaddrs: [], _options: { host: {} } @@ -252,7 +250,6 @@ describe('Identify', () => { libp2p: { peerId: localPeer, connectionManager: new EventEmitter(), - transportManager: new EventEmitter(), peerStore: new PeerStore({ peerId: localPeer }), multiaddrs: listenMaddrs, _options: { host: {} } @@ -267,7 +264,6 @@ describe('Identify', () => { libp2p: { peerId: remotePeer, connectionManager, - transportManager: new EventEmitter(), peerStore: new PeerStore({ peerId: remotePeer }), multiaddrs: [], _options: { host: {} } @@ -285,6 +281,10 @@ describe('Identify', () => { sinon.spy(remoteIdentify.peerStore.addressBook, 'consumePeerRecord') sinon.spy(remoteIdentify.peerStore.protoBook, 'set') + // Transport Manager creates signed peer record + await _createSelfPeerRecord(localIdentify._libp2p) + await _createSelfPeerRecord(remoteIdentify._libp2p) + // Run identify await Promise.all([ localIdentify.push([localConnectionMock]), @@ -295,7 +295,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) @@ -317,7 +317,6 @@ describe('Identify', () => { libp2p: { peerId: localPeer, connectionManager: new EventEmitter(), - transportManager: new EventEmitter(), peerStore: new PeerStore({ peerId: localPeer }), multiaddrs: listenMaddrs, _options: { host: {} } @@ -332,7 +331,6 @@ describe('Identify', () => { libp2p: { peerId: remotePeer, connectionManager, - transportManager: new EventEmitter(), peerStore: new PeerStore({ peerId: remotePeer }), multiaddrs: [], _options: { host: {} } @@ -409,8 +407,8 @@ 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 @@ -474,3 +472,18 @@ describe('Identify', () => { }) }) }) + +// Self peer record creating on Transport Manager simulation +const _createSelfPeerRecord = async (libp2p) => { + try { + const peerRecord = new PeerRecord({ + peerId: libp2p.peerId, + multiaddrs: libp2p.multiaddrs + }) + const envelope = await Envelope.seal(peerRecord, libp2p.peerId) + libp2p.peerStore.addressBook.consumePeerRecord(envelope) + + return libp2p.peerStore.addressBook.getRawEnvelope(libp2p.peerId) + } catch (_) {} + return null +} diff --git a/test/transports/transport-manager.node.js b/test/transports/transport-manager.node.js index 58bc8737b6..d88d20f5c6 100644 --- a/test/transports/transport-manager.node.js +++ b/test/transports/transport-manager.node.js @@ -4,13 +4,17 @@ const chai = require('chai') chai.use(require('dirty-chai')) const { expect } = chai +const sinon = require('sinon') const AddressManager = require('../../src/address-manager') const TransportManager = require('../../src/transport-manager') +const PeerStore = require('../../src/peer-store') const Transport = require('libp2p-tcp') +const PeerId = require('peer-id') const multiaddr = require('multiaddr') const mockUpgrader = require('../utils/mockUpgrader') const sinon = require('sinon') +const Peers = require('../fixtures/peers') const addrs = [ multiaddr('/ip4/127.0.0.1/tcp/0'), multiaddr('/ip4/127.0.0.1/tcp/0') @@ -18,11 +22,17 @@ const addrs = [ describe('Transport Manager (TCP)', () => { let tm + let localPeer + + before(async () => { + localPeer = await PeerId.createFromJSON(Peers[0]) + }) before(() => { tm = new TransportManager({ libp2p: { - addressManager: new AddressManager({ listen: addrs }) + addressManager: new AddressManager({ listen: addrs }), + PeerStore: new PeerStore({ peerId: localPeer }) }, upgrader: mockUpgrader, onConnection: () => {} @@ -41,12 +51,18 @@ describe('Transport Manager (TCP)', () => { }) it('should be able to listen', async () => { + sinon.spy(tm, '_createSelfPeerRecord') + tm.add(Transport.prototype[Symbol.toStringTag], Transport, { listenerOptions: { listen: 'carefully' } }) const transport = tm._transports.get(Transport.prototype[Symbol.toStringTag]) const spyListener = sinon.spy(transport, 'createListener') await tm.listen() expect(tm._listeners).to.have.key(Transport.prototype[Symbol.toStringTag]) expect(tm._listeners.get(Transport.prototype[Symbol.toStringTag])).to.have.length(addrs.length) + + // Created Self Peer record on new listen address + expect(tm._createSelfPeerRecord.callCount).to.equal(addrs.length) + // Ephemeral ip addresses may result in multiple listeners expect(tm.getAddrs().length).to.equal(addrs.length) await tm.close() From 447d0ed0dd43a16b926029bee2d77cca03790bad Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 24 Sep 2020 11:12:02 +0200 Subject: [PATCH 044/447] chore: add identify test for multiaddr change --- test/identify/index.spec.js | 39 ++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js index 7356497f08..ab2451c2a9 100644 --- a/test/identify/index.spec.js +++ b/test/identify/index.spec.js @@ -470,6 +470,42 @@ describe('Identify', () => { expect(agentVersion).to.eql(unit8ArrayToString(storedAgentVersion)) expect(storedProtocolVersion).to.exist() }) + + 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 nextTick to trigger the identify call + await delay(1) + + // 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) + }) }) }) @@ -482,8 +518,5 @@ const _createSelfPeerRecord = async (libp2p) => { }) const envelope = await Envelope.seal(peerRecord, libp2p.peerId) libp2p.peerStore.addressBook.consumePeerRecord(envelope) - - return libp2p.peerStore.addressBook.getRawEnvelope(libp2p.peerId) } catch (_) {} - return null } From bb83cacb5ac0038ae5a7bd33cc625108fe5aecd2 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 24 Sep 2020 12:49:48 +0200 Subject: [PATCH 045/447] chore: address review --- src/record/utils.js | 20 ++++++++++++++ src/transport-manager.js | 27 +++---------------- test/dialing/direct.node.js | 23 ++++++++++------ test/identify/index.spec.js | 25 +++--------------- test/transports/transport-manager.node.js | 32 ++++++++++++++++++----- 5 files changed, 67 insertions(+), 60 deletions(-) create mode 100644 src/record/utils.js diff --git a/src/record/utils.js b/src/record/utils.js new file mode 100644 index 0000000000..509fea7eec --- /dev/null +++ b/src/record/utils.js @@ -0,0 +1,20 @@ +'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} + */ +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 diff --git a/src/transport-manager.js b/src/transport-manager.js index 64aa18eceb..654ae9d547 100644 --- a/src/transport-manager.js +++ b/src/transport-manager.js @@ -7,8 +7,7 @@ const debug = require('debug') const log = debug('libp2p:transports') log.error = debug('libp2p:transports:error') -const Envelope = require('./record/envelope') -const PeerRecord = require('./record/peer-record') +const { updateSelfPeerRecord } = require('./record/utils') class TransportManager { /** @@ -164,8 +163,8 @@ class TransportManager { this._listeners.get(key).push(listener) // Track listen/close events - listener.on('listening', () => this._createSelfPeerRecord()) - listener.on('close', () => this._createSelfPeerRecord()) + 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)) @@ -235,26 +234,6 @@ class TransportManager { await Promise.all(tasks) } - - /** - * Create self signed peer record raw envelope. - * @return {Uint8Array} - */ - async _createSelfPeerRecord () { - try { - const peerRecord = new PeerRecord({ - peerId: this.libp2p.peerId, - multiaddrs: this.libp2p.multiaddrs - }) - const envelope = await Envelope.seal(peerRecord, this.libp2p.peerId) - this.libp2p.peerStore.addressBook.consumePeerRecord(envelope) - - return this.libp2p.peerStore.addressBook.getRawEnvelope(this.libp2p.peerId) - } catch (err) { - log.error('failed to get self peer record') - } - return null - } } /** diff --git a/test/dialing/direct.node.js b/test/dialing/direct.node.js index 6b89fee4af..0d9d1dd718 100644 --- a/test/dialing/direct.node.js +++ b/test/dialing/direct.node.js @@ -42,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) @@ -66,7 +73,7 @@ describe('Dialing (direct, TCP)', () => { remoteAddr = remoteTM.getAddrs()[0].encapsulate(`/p2p/${remotePeerId.toB58String()}`) }) - after(() => remoteTM.close()) + afterEach(() => remoteTM.close()) afterEach(() => { sinon.restore() @@ -112,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() diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js index ab2451c2a9..c45ae2fa48 100644 --- a/test/identify/index.spec.js +++ b/test/identify/index.spec.js @@ -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') @@ -20,9 +19,9 @@ const { IdentifyService, multicodecs } = require('../../src/identify') const Peers = require('../fixtures/peers') const Libp2p = require('../../src') const Envelope = require('../../src/record/envelope') -const PeerRecord = require('../../src/record/peer-record') 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') @@ -82,7 +81,7 @@ describe('Identify', () => { sinon.spy(localIdentify.peerStore.protoBook, 'set') // Transport Manager creates signed peer record - await _createSelfPeerRecord(remoteIdentify._libp2p) + await updateSelfPeerRecord(remoteIdentify._libp2p) // Run identify await Promise.all([ @@ -282,8 +281,8 @@ describe('Identify', () => { sinon.spy(remoteIdentify.peerStore.protoBook, 'set') // Transport Manager creates signed peer record - await _createSelfPeerRecord(localIdentify._libp2p) - await _createSelfPeerRecord(remoteIdentify._libp2p) + await updateSelfPeerRecord(localIdentify._libp2p) + await updateSelfPeerRecord(remoteIdentify._libp2p) // Run identify await Promise.all([ @@ -429,8 +428,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 @@ -484,8 +481,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 @@ -508,15 +503,3 @@ describe('Identify', () => { }) }) }) - -// Self peer record creating on Transport Manager simulation -const _createSelfPeerRecord = async (libp2p) => { - try { - const peerRecord = new PeerRecord({ - peerId: libp2p.peerId, - multiaddrs: libp2p.multiaddrs - }) - const envelope = await Envelope.seal(peerRecord, libp2p.peerId) - libp2p.peerStore.addressBook.consumePeerRecord(envelope) - } catch (_) {} -} diff --git a/test/transports/transport-manager.node.js b/test/transports/transport-manager.node.js index d88d20f5c6..2e17f88b59 100644 --- a/test/transports/transport-manager.node.js +++ b/test/transports/transport-manager.node.js @@ -4,11 +4,11 @@ const chai = require('chai') chai.use(require('dirty-chai')) const { expect } = chai -const sinon = require('sinon') 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') @@ -28,11 +28,13 @@ describe('Transport Manager (TCP)', () => { localPeer = await PeerId.createFromJSON(Peers[0]) }) - before(() => { + beforeEach(() => { tm = new TransportManager({ libp2p: { + peerId: localPeer, + multiaddrs: addrs, addressManager: new AddressManager({ listen: addrs }), - PeerStore: new PeerStore({ peerId: localPeer }) + peerStore: new PeerStore({ peerId: localPeer }) }, upgrader: mockUpgrader, onConnection: () => {} @@ -56,13 +58,10 @@ describe('Transport Manager (TCP)', () => { tm.add(Transport.prototype[Symbol.toStringTag], Transport, { listenerOptions: { listen: 'carefully' } }) const transport = tm._transports.get(Transport.prototype[Symbol.toStringTag]) const spyListener = sinon.spy(transport, 'createListener') - 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) - // Created Self Peer record on new listen address - expect(tm._createSelfPeerRecord.callCount).to.equal(addrs.length) - // Ephemeral ip addresses may result in multiple listeners expect(tm.getAddrs().length).to.equal(addrs.length) await tm.close() @@ -70,6 +69,25 @@ describe('Transport Manager (TCP)', () => { expect(spyListener.firstCall.firstArg).to.deep.equal({ listen: 'carefully' }) }) + 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(addrs) From 55020056ee298ad85467513dd43f33b6e1b25b3d Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 7 Oct 2020 17:29:42 +0200 Subject: [PATCH 046/447] chore: lint issue fixed 0.30 --- src/circuit/auto-relay.js | 28 +++++++++++++++++----------- src/circuit/circuit/hop.js | 3 ++- src/identify/index.js | 1 + src/record/utils.js | 1 + src/transport-manager.js | 2 +- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js index bec8c3a8f0..c0dafc4b17 100644 --- a/src/circuit/auto-relay.js +++ b/src/circuit/auto-relay.js @@ -19,10 +19,11 @@ const hopMetadataValue = 'true' class AutoRelay { /** * Creates an instance of AutoRelay. - * @constructor + * + * @class * @param {object} props * @param {Libp2p} props.libp2p - * @param {number} [props.maxListeners = 1] maximum number of relays to listen. + * @param {number} [props.maxListeners = 1] - maximum number of relays to listen. */ constructor ({ libp2p, maxListeners = 1 }) { this._libp2p = libp2p @@ -50,10 +51,11 @@ class AutoRelay { * 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} props.protocols - * @return {Promise} + * @returns {Promise} */ async _onProtocolChange ({ peerId, protocols }) { const id = peerId.toB58String() @@ -92,8 +94,9 @@ class AutoRelay { /** * Peer disconnects. - * @param {Connection} connection connection to the peer - * @return {void} + * + * @param {Connection} connection - connection to the peer + * @returns {void} */ _onPeerDisconnected (connection) { const peerId = connection.remotePeer @@ -109,10 +112,11 @@ class AutoRelay { /** * Attempt to listen on the given relay connection. + * * @private - * @param {Connection} connection connection to the peer - * @param {string} id peer identifier string - * @return {Promise} + * @param {Connection} connection - connection to the peer + * @param {string} id - peer identifier string + * @returns {Promise} */ async _addListenRelay (connection, id) { // Check if already listening on enough relays @@ -152,9 +156,10 @@ class AutoRelay { /** * Remove listen relay. + * * @private - * @param {string} id peer identifier string. - * @return {void} + * @param {string} id - peer identifier string. + * @returns {void} */ _removeListenRelay (id) { if (this._listenRelays.delete(id)) { @@ -169,8 +174,9 @@ class AutoRelay { * 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} [peersToIgnore] - * @return {Promise} + * @returns {Promise} */ async _listenOnAvailableHopRelays (peersToIgnore = []) { // TODO: The peer redial issue on disconnect should be handled by connection gating diff --git a/src/circuit/circuit/hop.js b/src/circuit/circuit/hop.js index 114e2768fe..c653a7c9ae 100644 --- a/src/circuit/circuit/hop.js +++ b/src/circuit/circuit/hop.js @@ -118,8 +118,9 @@ module.exports.hop = async function hop ({ /** * 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 + * @param {Connection} options.connection - Connection to the relay * @returns {Promise} */ module.exports.canHop = async function canHop ({ diff --git a/src/identify/index.js b/src/identify/index.js index 58ab2e956b..289155e707 100644 --- a/src/identify/index.js +++ b/src/identify/index.js @@ -135,6 +135,7 @@ class IdentifyService { /** * Calls `push` for all peers in the `peerStore` that are connected + * * @returns {void} */ pushToPeerStore () { diff --git a/src/record/utils.js b/src/record/utils.js index 509fea7eec..65696156b8 100644 --- a/src/record/utils.js +++ b/src/record/utils.js @@ -5,6 +5,7 @@ const PeerRecord = require('./peer-record') /** * Create (or update if existing) self peer record and store it in the AddressBook. + * * @param {libp2p} libp2p * @returns {Promise} */ diff --git a/src/transport-manager.js b/src/transport-manager.js index 654ae9d547..ab07f2addc 100644 --- a/src/transport-manager.js +++ b/src/transport-manager.js @@ -143,7 +143,7 @@ class TransportManager { * Starts listeners for each listen Multiaddr. * * @async - * @param {Array} addrs addresses to attempt to listen on + * @param {Array} addrs - addresses to attempt to listen on */ async listen (addrs) { if (!addrs || addrs.length === 0) { From 0bf0b7cf8968d55002ac4c559ffb59985feeb092 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 11 Sep 2020 19:02:56 +0200 Subject: [PATCH 047/447] feat: auto relay network query for new relays --- package.json | 3 +- src/circuit/auto-relay.js | 58 ++++++++-- src/circuit/constants.js | 12 ++ src/circuit/index.js | 211 ++++++++-------------------------- src/circuit/transport.js | 194 +++++++++++++++++++++++++++++++ src/circuit/utils.js | 16 +++ src/config.js | 5 + src/index.js | 11 +- test/relay/auto-relay.node.js | 132 ++++++++++++++++++++- 9 files changed, 458 insertions(+), 184 deletions(-) create mode 100644 src/circuit/constants.js create mode 100644 src/circuit/transport.js create mode 100644 src/circuit/utils.js diff --git a/package.json b/package.json index fd22a16def..b3e1d0ca5f 100644 --- a/package.json +++ b/package.json @@ -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", @@ -66,6 +67,7 @@ "moving-average": "^1.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", @@ -89,7 +91,6 @@ "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", diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js index c0dafc4b17..c9559ce9a1 100644 --- a/src/circuit/auto-relay.js +++ b/src/circuit/auto-relay.js @@ -11,10 +11,13 @@ const PeerId = require('peer-id') const { relay: multicodec } = require('./multicodec') const { canHop } = require('./circuit/hop') - -const circuitProtoCode = 290 -const hopMetadataKey = 'hop_relay' -const hopMetadataValue = 'true' +const { namespaceToCid } = require('./utils') +const { + CIRCUIT_PROTO_CODE, + HOP_METADATA_KEY, + HOP_METADATA_VALUE, + RELAY_RENDEZVOUS_NS +} = require('./constants') class AutoRelay { /** @@ -76,7 +79,7 @@ class AutoRelay { const connection = this._connectionManager.get(peerId) // Do not hop on a relayed connection - if (connection.remoteAddr.protoCodes().includes(circuitProtoCode)) { + if (connection.remoteAddr.protoCodes().includes(CIRCUIT_PROTO_CODE)) { log(`relayed connection to ${id} will not be used to hop on`) return } @@ -84,7 +87,7 @@ class AutoRelay { const supportsHop = await canHop({ connection }) if (supportsHop) { - this._peerStore.metadataBook.set(peerId, hopMetadataKey, uint8ArrayFromString(hopMetadataValue)) + this._peerStore.metadataBook.set(peerId, HOP_METADATA_KEY, uint8ArrayFromString(HOP_METADATA_VALUE)) await this._addListenRelay(connection, id) } } catch (err) { @@ -125,15 +128,21 @@ class AutoRelay { } // Create relay listen addr - let listenAddr, remoteMultiaddr + let listenAddr, remoteMultiaddr, remoteAddrs try { - const remoteAddrs = this._peerStore.addressBook.get(connection.remotePeer) + remoteAddrs = this._peerStore.addressBook.get(connection.remotePeer) // TODO: HOP Relays should avoid advertising private addresses! remoteMultiaddr = remoteAddrs.find(a => a.isCertified).multiaddr // Get first announced address certified } catch (_) { log.error(`${id} does not have announced certified multiaddrs`) - return + + // Attempt first if existing + if (!remoteAddrs || !remoteAddrs.length) { + return + } + + remoteMultiaddr = remoteAddrs[0].multiaddr } if (!remoteMultiaddr.protoNames().includes('p2p')) { @@ -194,10 +203,10 @@ class AutoRelay { continue } - const supportsHop = metadataMap.get(hopMetadataKey) + const supportsHop = metadataMap.get(HOP_METADATA_KEY) // Continue to next if it does not support Hop - if (!supportsHop || uint8ArrayToString(supportsHop) !== hopMetadataValue) { + if (!supportsHop || uint8ArrayToString(supportsHop) !== HOP_METADATA_VALUE) { continue } @@ -229,7 +238,32 @@ class AutoRelay { } } - // TODO: Try to find relays to hop on the network + // 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 || !provider.id || !provider.multiaddrs || !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) { + if (err.code !== 'NO_ROUTERS_AVAILABLE') { + throw err + } else { + log('there are no routers configured to find hop relay services') + } + } } } diff --git a/src/circuit/constants.js b/src/circuit/constants.js new file mode 100644 index 0000000000..53bd6505d6 --- /dev/null +++ b/src/circuit/constants.js @@ -0,0 +1,12 @@ +'use strict' + +const minute = 60 * 1000 + +module.exports = { + ADVERTISE_BOOT_DELAY: 15 * minute, + ADVERTISE_TTL: 30 * minute, + CIRCUIT_PROTO_CODE: 290, + HOP_METADATA_KEY: 'hop_relay', + HOP_METADATA_VALUE: 'true', + RELAY_RENDEZVOUS_NS: '/libp2p/relay' +} diff --git a/src/circuit/index.js b/src/circuit/index.js index 705dcdad82..d0df9041ae 100644 --- a/src/circuit/index.js +++ b/src/circuit/index.js @@ -1,197 +1,76 @@ '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 log = debug('libp2p:relay') +log.error = debug('libp2p:relay:error') const AutoRelay = require('./auto-relay') -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 { +const { namespaceToCid } = require('./utils') +const { + ADVERTISE_BOOT_DELAY, + ADVERTISE_TTL, + RELAY_RENDEZVOUS_NS +} = require('./constants') + +class Relay { /** - * Creates an instance of Circuit. + * Creates an instance of Relay. * * @class - * @param {object} options - * @param {Libp2p} options.libp2p - * @param {Upgrader} options.upgrader + * @param {Libp2p} libp2p */ - constructor ({ libp2p, upgrader }) { - this._dialer = libp2p.dialer - this._registrar = libp2p.registrar - this._connectionManager = libp2p.connectionManager - this._upgrader = upgrader + constructor (libp2p) { this._options = libp2p._config.relay this._libp2p = libp2p - this.peerId = libp2p.peerId - this._registrar.handle(multicodec, this._onProtocol.bind(this)) // Create autoRelay if enabled this._autoRelay = this._options.autoRelay.enabled && new AutoRelay({ libp2p, ...this._options.autoRelay }) } - 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 + * Start Relay service. + * @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 - } - - 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 + start () { + // Advertise service if HOP enabled + const canHop = this._options.hop.enabled + + if (canHop) { + this._timeout = setTimeout(() => { + this._advertiseService() + }, this._options.advertise.bootDelay || ADVERTISE_BOOT_DELAY) } } /** - * Create a listener - * - * @param {any} options - * @param {Function} handler - * @returns {listener} + * Stop Relay service. + * @returns {void} */ - 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) + stop () { + clearTimeout(this._timeout) } /** - * Filter check for all Multiaddrs that this transport can dial on - * - * @param {Array} multiaddrs - * @returns {Array} + * Advertise hop relay service in the network. + * @returns {Promise} */ - filter (multiaddrs) { - multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs] + async _advertiseService () { + try { + const cid = await namespaceToCid(RELAY_RENDEZVOUS_NS) + await this._libp2p.contentRouting.provide(cid) + } catch (err) { + if (err.code === 'NO_ROUTERS_AVAILABLE') { + log('there are no routers configured to advertise hop relay service') + } else { + log.error(err) + } + } - return multiaddrs.filter((ma) => { - return mafmt.Circuit.matches(ma) - }) + // Restart timeout + this._timeout = setTimeout(() => { + this._advertiseService() + }, this._options.advertise.ttl || ADVERTISE_TTL) } } -/** - * @type {Circuit} - */ -module.exports = withIs(Circuit, { className: 'Circuit', symbolName: '@libp2p/js-libp2p-circuit/circuit' }) +module.exports = Relay diff --git a/src/circuit/transport.js b/src/circuit/transport.js new file mode 100644 index 0000000000..e876fa6005 --- /dev/null +++ b/src/circuit/transport.js @@ -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. + * + * @constructor + * @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 + * @return {listener} + */ + createListener (options, handler) { + if (typeof options === 'function') { + handler = options + options = {} + } + + // Called on successful HOP and STOP requests + this.handler = handler + + return createListener(this._libp2p, options) + } + + /** + * Filter check for all Multiaddrs that this transport can dial on + * + * @param {Array} multiaddrs + * @returns {Array} + */ + 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' }) diff --git a/src/circuit/utils.js b/src/circuit/utils.js new file mode 100644 index 0000000000..7426271cb7 --- /dev/null +++ b/src/circuit/utils.js @@ -0,0 +1,16 @@ +'use strict' + +const CID = require('cids') +const multihashing = require('multihashing-async') + +/** + * Convert a namespace string into a cid. + * @param {string} namespace + * @return {Promise} + */ +module.exports.namespaceToCid = async (namespace) => { + const bytes = new TextEncoder('utf8').encode(namespace) + const hash = await multihashing(bytes, 'sha2-256') + + return new CID(hash) +} diff --git a/src/config.js b/src/config.js index 18e3201e29..bc1b5675dc 100644 --- a/src/config.js +++ b/src/config.js @@ -5,6 +5,7 @@ const { dnsaddrResolver } = require('multiaddr/src/resolvers') const Constants = require('./constants') const { AGENT_VERSION } = require('./identify/consts') +const RelayConstants = require('./circuit/constants') const { FaultTolerance } = require('./transport-manager') @@ -60,6 +61,10 @@ const DefaultConfig = { }, relay: { enabled: true, + advertise: { + bootDelay: RelayConstants.ADVERTISE_BOOT_DELAY, + ttl: RelayConstants.ADVERTISE_TTL + }, hop: { enabled: false, active: false diff --git a/src/index.js b/src/index.js index 5c5456af57..30c3db42fd 100644 --- a/src/index.js +++ b/src/index.js @@ -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') @@ -146,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 @@ -249,6 +251,10 @@ class Libp2p extends EventEmitter { try { this._isStarted = false + + // Relay + this.relay && this.relay.stop() + for (const service of this._discovery.values()) { service.removeListener('peer', this._onDiscoveryPeer) } @@ -508,6 +514,9 @@ class Libp2p extends EventEmitter { // Peer discovery await this._setupPeerDiscovery() + + // Relay + this.relay && this.relay.start() } /** diff --git a/test/relay/auto-relay.node.js b/test/relay/auto-relay.node.js index 96f94cd7bb..cd0add3771 100644 --- a/test/relay/auto-relay.node.js +++ b/test/relay/auto-relay.node.js @@ -8,7 +8,10 @@ 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') @@ -59,7 +62,7 @@ describe('auto-relay', () => { }) }) - autoRelay = libp2p.transportManager._transports.get('Circuit')._autoRelay + autoRelay = libp2p.relay._autoRelay expect(autoRelay.maxListeners).to.eql(1) }) @@ -144,7 +147,7 @@ describe('auto-relay', () => { }) }) - autoRelay1 = relayLibp2p1.transportManager._transports.get('Circuit')._autoRelay + autoRelay1 = relayLibp2p1.relay._autoRelay expect(autoRelay1.maxListeners).to.eql(1) }) @@ -412,8 +415,8 @@ describe('auto-relay', () => { }) }) - autoRelay1 = relayLibp2p1.transportManager._transports.get('Circuit')._autoRelay - autoRelay2 = relayLibp2p2.transportManager._transports.get('Circuit')._autoRelay + autoRelay1 = relayLibp2p1.relay._autoRelay + autoRelay2 = relayLibp2p2.relay._autoRelay }) beforeEach(() => { @@ -457,4 +460,125 @@ describe('auto-relay', () => { expect(autoRelay1._listenRelays.size).to.equal(1) }) }) + + describe('discovery', () => { + let libp2p + let libp2p2 + let relayLibp2p + + beforeEach(async () => { + const peerIds = await createPeerId({ number: 3 }) + + // Create 2 nodes, and turn HOP on for the relay + ;[libp2p, libp2p2, 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 + }, + 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([libp2p, libp2p2, 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([libp2p, libp2p2, relayLibp2p].map(libp2p => libp2p.stop())) + }) + + it('should find providers for relay and add it as listen relay', async () => { + const originalMultiaddrsLength = libp2p.multiaddrs.length + + // Spy add listen relay + sinon.spy(libp2p.relay._autoRelay, '_addListenRelay') + // Spy Find Providers + sinon.spy(libp2p.contentRouting, 'findProviders') + + // Try to listen on Available hop relays + await libp2p.relay._autoRelay._listenOnAvailableHopRelays() + + // Should try to find relay service providers + await pWaitFor(() => libp2p.contentRouting.findProviders.callCount === 1) + // Wait for peer added as listen relay + await pWaitFor(() => libp2p.relay._autoRelay._addListenRelay.callCount === 1) + expect(libp2p.relay._autoRelay._listenRelays.size).to.equal(1) + await pWaitFor(() => libp2p.multiaddrs.length === originalMultiaddrsLength + 1) + + const relayedAddr = libp2p.multiaddrs[libp2p.multiaddrs.length - 1] + libp2p2.peerStore.addressBook.set(libp2p2.peerId, [relayedAddr]) + + // Dial from peer 2 through the relayed address + const conn = await libp2p2.dial(libp2p2.peerId) + expect(conn).to.exist() + }) + }) }) From 5c72424e5744f93f9e5eb77db0eceaad37965ed4 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 23 Sep 2020 16:40:33 +0200 Subject: [PATCH 048/447] chore: address review --- src/circuit/auto-relay.js | 2 +- src/circuit/constants.js | 12 ++++++------ src/circuit/index.js | 20 ++++++++++++++++---- src/config.js | 1 + test/relay/auto-relay.node.js | 34 +++++++++++++++++----------------- 5 files changed, 41 insertions(+), 28 deletions(-) diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js index c9559ce9a1..4bffecc06d 100644 --- a/src/circuit/auto-relay.js +++ b/src/circuit/auto-relay.js @@ -242,7 +242,7 @@ class AutoRelay { try { const cid = await namespaceToCid(RELAY_RENDEZVOUS_NS) for await (const provider of this._libp2p.contentRouting.findProviders(cid)) { - if (!provider || !provider.id || !provider.multiaddrs || !provider.multiaddrs.length) { + if (!provider || !provider.multiaddrs.length) { continue } const peerId = provider.id diff --git a/src/circuit/constants.js b/src/circuit/constants.js index 53bd6505d6..b4de629c63 100644 --- a/src/circuit/constants.js +++ b/src/circuit/constants.js @@ -3,10 +3,10 @@ const minute = 60 * 1000 module.exports = { - ADVERTISE_BOOT_DELAY: 15 * minute, - ADVERTISE_TTL: 30 * minute, - CIRCUIT_PROTO_CODE: 290, - HOP_METADATA_KEY: 'hop_relay', - HOP_METADATA_VALUE: 'true', - RELAY_RENDEZVOUS_NS: '/libp2p/relay' + 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 } diff --git a/src/circuit/index.js b/src/circuit/index.js index d0df9041ae..ae54ce1863 100644 --- a/src/circuit/index.js +++ b/src/circuit/index.js @@ -20,8 +20,16 @@ class Relay { * @param {Libp2p} libp2p */ constructor (libp2p) { - this._options = libp2p._config.relay this._libp2p = libp2p + this._options = { + advertise: { + bootDelay: ADVERTISE_BOOT_DELAY, + enabled: true, + ttl: ADVERTISE_TTL, + ...libp2p._config.relay.advertise + }, + ...libp2p._config.relay + } // Create autoRelay if enabled this._autoRelay = this._options.autoRelay.enabled && new AutoRelay({ libp2p, ...this._options.autoRelay }) @@ -35,10 +43,10 @@ class Relay { // Advertise service if HOP enabled const canHop = this._options.hop.enabled - if (canHop) { + if (canHop && this._options.advertise.enabled) { this._timeout = setTimeout(() => { this._advertiseService() - }, this._options.advertise.bootDelay || ADVERTISE_BOOT_DELAY) + }, this._options.advertise.bootDelay) } } @@ -64,12 +72,16 @@ class Relay { } else { log.error(err) } + // Stop the advertise + this.stop() + + return } // Restart timeout this._timeout = setTimeout(() => { this._advertiseService() - }, this._options.advertise.ttl || ADVERTISE_TTL) + }, this._options.advertise.ttl) } } diff --git a/src/config.js b/src/config.js index bc1b5675dc..4f18f38cfa 100644 --- a/src/config.js +++ b/src/config.js @@ -63,6 +63,7 @@ const DefaultConfig = { enabled: true, advertise: { bootDelay: RelayConstants.ADVERTISE_BOOT_DELAY, + enabled: true, ttl: RelayConstants.ADVERTISE_TTL }, hop: { diff --git a/test/relay/auto-relay.node.js b/test/relay/auto-relay.node.js index cd0add3771..43f42a6ab8 100644 --- a/test/relay/auto-relay.node.js +++ b/test/relay/auto-relay.node.js @@ -462,15 +462,15 @@ describe('auto-relay', () => { }) describe('discovery', () => { - let libp2p - let libp2p2 + let local + let remote let relayLibp2p beforeEach(async () => { const peerIds = await createPeerId({ number: 3 }) // Create 2 nodes, and turn HOP on for the relay - ;[libp2p, libp2p2, relayLibp2p] = peerIds.map((peerId, index) => { + ;[local, remote, relayLibp2p] = peerIds.map((peerId, index) => { const delegate = new DelegatedContentRouter(peerId, ipfsHttpClient({ host: '0.0.0.0', protocol: 'http', @@ -532,7 +532,7 @@ describe('auto-relay', () => { ]) // Start each node - await Promise.all([libp2p, libp2p2, relayLibp2p].map(libp2p => libp2p.start())) + await Promise.all([local, remote, relayLibp2p].map(libp2p => libp2p.start())) // Should provide on start await pWaitFor(() => relayLibp2p.contentRouting.provide.callCount === 1) @@ -552,32 +552,32 @@ describe('auto-relay', () => { afterEach(() => { // Stop each node - return Promise.all([libp2p, libp2p2, relayLibp2p].map(libp2p => libp2p.stop())) + 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 = libp2p.multiaddrs.length + const originalMultiaddrsLength = local.multiaddrs.length // Spy add listen relay - sinon.spy(libp2p.relay._autoRelay, '_addListenRelay') + sinon.spy(local.relay._autoRelay, '_addListenRelay') // Spy Find Providers - sinon.spy(libp2p.contentRouting, 'findProviders') + sinon.spy(local.contentRouting, 'findProviders') // Try to listen on Available hop relays - await libp2p.relay._autoRelay._listenOnAvailableHopRelays() + await local.relay._autoRelay._listenOnAvailableHopRelays() // Should try to find relay service providers - await pWaitFor(() => libp2p.contentRouting.findProviders.callCount === 1) + await pWaitFor(() => local.contentRouting.findProviders.callCount === 1) // Wait for peer added as listen relay - await pWaitFor(() => libp2p.relay._autoRelay._addListenRelay.callCount === 1) - expect(libp2p.relay._autoRelay._listenRelays.size).to.equal(1) - await pWaitFor(() => libp2p.multiaddrs.length === originalMultiaddrsLength + 1) + 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 = libp2p.multiaddrs[libp2p.multiaddrs.length - 1] - libp2p2.peerStore.addressBook.set(libp2p2.peerId, [relayedAddr]) + const relayedAddr = local.multiaddrs[local.multiaddrs.length - 1] + remote.peerStore.addressBook.set(local.peerId, [relayedAddr]) - // Dial from peer 2 through the relayed address - const conn = await libp2p2.dial(libp2p2.peerId) + // Dial from remote through the relayed address + const conn = await remote.dial(local.peerId) expect(conn).to.exist() }) }) From 11a46ea71ee8b2cd6e278a24ff325dd486f759d9 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 23 Sep 2020 16:47:03 +0200 Subject: [PATCH 049/447] chore: add configuration docs for auto relay and hop service --- doc/CONFIGURATION.md | 32 ++++++++++++++++++++++++++++++++ src/circuit/auto-relay.js | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 4105551796..4804185a1c 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -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) @@ -419,12 +420,43 @@ const node = await Libp2p.create({ 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') +const MPLEX = require('libp2p-mplex') +const SECIO = require('libp2p-secio') + +const node = await Libp2p.create({ + modules: { + transport: [TCP], + streamMuxer: [MPLEX], + connEncryption: [SECIO] + }, + config: { + relay: { // Circuit Relay options (this config is part of libp2p core configurations) + enabled: true, // Allows you to dial and accept relayed connections. Does not make you a relay. + autoRelay: { + enabled: false, // Allows you to bind to relays with HOP enabled for improving node dialability + maxListeners: 2 // Configure maximum number of HOP relays to use + }, + } + } +}) +``` + #### Setup with Keychain Libp2p allows you to setup a secure keychain to manage your keys. The keychain configuration object should have the following properties: diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js index 4bffecc06d..c0475dbada 100644 --- a/src/circuit/auto-relay.js +++ b/src/circuit/auto-relay.js @@ -242,7 +242,7 @@ class AutoRelay { try { const cid = await namespaceToCid(RELAY_RENDEZVOUS_NS) for await (const provider of this._libp2p.contentRouting.findProviders(cid)) { - if (!provider || !provider.multiaddrs.length) { + if (!provider.multiaddrs.length) { continue } const peerId = provider.id From ee23fb9508939840cbc8a1f6181b0278540df10d Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 5 Oct 2020 16:49:43 +0200 Subject: [PATCH 050/447] chore: apply suggestions from code review Co-authored-by: Jacob Heun --- doc/CONFIGURATION.md | 4 ++-- src/circuit/index.js | 6 +++--- src/config.js | 2 +- test/relay/auto-relay.node.js | 3 ++- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 4804185a1c..acd5660cf7 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -449,9 +449,9 @@ const node = await Libp2p.create({ 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. autoRelay: { - enabled: false, // Allows you to bind to relays with HOP enabled for improving node dialability + 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 - }, + } } } }) diff --git a/src/circuit/index.js b/src/circuit/index.js index ae54ce1863..dbb70e61a4 100644 --- a/src/circuit/index.js +++ b/src/circuit/index.js @@ -68,12 +68,12 @@ class Relay { await this._libp2p.contentRouting.provide(cid) } catch (err) { if (err.code === 'NO_ROUTERS_AVAILABLE') { - log('there are no routers configured to advertise hop relay service') + 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) } - // Stop the advertise - this.stop() return } diff --git a/src/config.js b/src/config.js index 4f18f38cfa..4c9bc54954 100644 --- a/src/config.js +++ b/src/config.js @@ -63,7 +63,7 @@ const DefaultConfig = { enabled: true, advertise: { bootDelay: RelayConstants.ADVERTISE_BOOT_DELAY, - enabled: true, + enabled: false, ttl: RelayConstants.ADVERTISE_TTL }, hop: { diff --git a/test/relay/auto-relay.node.js b/test/relay/auto-relay.node.js index 43f42a6ab8..8d0cdfd27e 100644 --- a/test/relay/auto-relay.node.js +++ b/test/relay/auto-relay.node.js @@ -486,7 +486,8 @@ describe('auto-relay', () => { relay: { advertise: { bootDelay: 1000, - ttl: 1000 + ttl: 1000, + enabled: true }, hop: { enabled: index === 2 From a5337c1797673ae25c40f71a9d3b45f86021e468 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 7 Oct 2020 17:58:11 +0200 Subject: [PATCH 051/447] chore: lint issues fixed --- src/circuit/index.js | 3 +++ src/circuit/transport.js | 4 ++-- src/circuit/utils.js | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/circuit/index.js b/src/circuit/index.js index dbb70e61a4..d12d882432 100644 --- a/src/circuit/index.js +++ b/src/circuit/index.js @@ -37,6 +37,7 @@ class Relay { /** * Start Relay service. + * * @returns {void} */ start () { @@ -52,6 +53,7 @@ class Relay { /** * Stop Relay service. + * * @returns {void} */ stop () { @@ -60,6 +62,7 @@ class Relay { /** * Advertise hop relay service in the network. + * * @returns {Promise} */ async _advertiseService () { diff --git a/src/circuit/transport.js b/src/circuit/transport.js index e876fa6005..cc79870564 100644 --- a/src/circuit/transport.js +++ b/src/circuit/transport.js @@ -22,7 +22,7 @@ class Circuit { /** * Creates an instance of the Circuit Transport. * - * @constructor + * @class * @param {object} options * @param {Libp2p} options.libp2p * @param {Upgrader} options.upgrader @@ -159,7 +159,7 @@ class Circuit { * * @param {any} options * @param {Function} handler - * @return {listener} + * @returns {listener} */ createListener (options, handler) { if (typeof options === 'function') { diff --git a/src/circuit/utils.js b/src/circuit/utils.js index 7426271cb7..18b61eafbb 100644 --- a/src/circuit/utils.js +++ b/src/circuit/utils.js @@ -5,8 +5,9 @@ const multihashing = require('multihashing-async') /** * Convert a namespace string into a cid. + * * @param {string} namespace - * @return {Promise} + * @returns {Promise} */ module.exports.namespaceToCid = async (namespace) => { const bytes = new TextEncoder('utf8').encode(namespace) From e977039c8a0d790301f96ae20f28fc5f5498dfe5 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 8 Oct 2020 10:42:18 +0200 Subject: [PATCH 052/447] chore: sort relay addresses to listen for public first --- package.json | 2 +- src/circuit/auto-relay.js | 28 +++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b3e1d0ca5f..00e0546fd9 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "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", diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js index c0475dbada..768d17eb37 100644 --- a/src/circuit/auto-relay.js +++ b/src/circuit/auto-relay.js @@ -4,6 +4,8 @@ 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') @@ -131,9 +133,13 @@ class AutoRelay { let listenAddr, remoteMultiaddr, remoteAddrs try { + // Get peer known addresses and sort them per public addresses first remoteAddrs = this._peerStore.addressBook.get(connection.remotePeer) - // TODO: HOP Relays should avoid advertising private addresses! + // 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`) @@ -267,4 +273,24 @@ class AutoRelay { } } +/** + * 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 From e36b67a212b29c5a204514fd2733200b081ec78c Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 8 Oct 2020 19:54:20 +0200 Subject: [PATCH 053/447] chore: improve logging for auto relay active listen --- src/circuit/auto-relay.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js index 768d17eb37..00ebc6f588 100644 --- a/src/circuit/auto-relay.js +++ b/src/circuit/auto-relay.js @@ -264,11 +264,7 @@ class AutoRelay { } } } catch (err) { - if (err.code !== 'NO_ROUTERS_AVAILABLE') { - throw err - } else { - log('there are no routers configured to find hop relay services') - } + log.error(err) } } } From 97e3633f47bfbded8ef979beab71a710f9dbebff Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 27 Oct 2020 13:50:14 +0000 Subject: [PATCH 054/447] chore: store self protocols in protobook (#760) --- doc/API.md | 77 +++++++++++++------- src/identify/index.js | 22 ++++-- src/index.js | 17 ++--- src/peer-store/proto-book.js | 41 ++++++++++- test/identify/index.spec.js | 109 +++++++++++++++-------------- test/peer-store/proto-book.spec.js | 92 ++++++++++++++++++++++++ 6 files changed, 258 insertions(+), 100 deletions(-) diff --git a/doc/API.md b/doc/API.md index 84d971512a..ee14ee140c 100644 --- a/doc/API.md +++ b/doc/API.md @@ -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) @@ -875,32 +876,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` | 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. @@ -1123,6 +1098,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` | 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. @@ -1179,6 +1179,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` | 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. diff --git a/src/identify/index.js b/src/identify/index.js index 289155e707..97673c12db 100644 --- a/src/identify/index.js +++ b/src/identify/index.js @@ -51,9 +51,8 @@ class IdentifyService { * @class * @param {object} options * @param {Libp2p} options.libp2p - * @param {Map} options.protocols - A reference to the protocols we support */ - constructor ({ libp2p, protocols }) { + constructor ({ libp2p }) { /** * @property {PeerStore} */ @@ -74,8 +73,6 @@ class IdentifyService { */ this._libp2p = libp2p - this._protocols = protocols - this.handleMessage = this.handleMessage.bind(this) // Store self host metadata @@ -97,6 +94,13 @@ class IdentifyService { this.pushToPeerStore() } }) + + // When self protocols change, trigger identify-push + this.peerStore.on('change:protocols', ({ peerId }) => { + if (peerId.toString() === this.peerId.toString()) { + this.pushToPeerStore() + } + }) } /** @@ -108,7 +112,7 @@ class IdentifyService { async push (connections) { 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 { @@ -139,6 +143,11 @@ class IdentifyService { * @returns {void} */ 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 this.peerStore.peers.values()) { @@ -258,6 +267,7 @@ class IdentifyService { } const signedPeerRecord = await this.peerStore.addressBook.getRawEnvelope(this.peerId) + const protocols = this.peerStore.protoBook.get(this.peerId) || [] const message = Message.encode({ protocolVersion: this._host.protocolVersion, @@ -266,7 +276,7 @@ class IdentifyService { listenAddrs: this._libp2p.multiaddrs.map((ma) => ma.bytes), signedPeerRecord, observedAddr: connection.remoteAddr.bytes, - protocols: Array.from(this._protocols.keys()) + protocols }) try { diff --git a/src/index.js b/src/index.js index 30c3db42fd..065acd841e 100644 --- a/src/index.js +++ b/src/index.js @@ -158,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) } @@ -442,10 +439,8 @@ class Libp2p extends EventEmitter { this.upgrader.protocols.set(protocol, handler) }) - // Only push if libp2p is running - if (this.isStarted() && this.identifyService) { - this.identifyService.pushToPeerStore() - } + // Add new protocols to self protocols in the Protobook + this.peerStore.protoBook.add(this.peerId, protocols) } /** @@ -460,10 +455,8 @@ class Libp2p extends EventEmitter { this.upgrader.protocols.delete(protocol) }) - // Only push if libp2p is running - if (this.isStarted() && this.identifyService) { - this.identifyService.pushToPeerStore() - } + // Remove protocols from self protocols in the Protobook + this.peerStore.protoBook.remove(this.peerId, protocols) } async _onStarting () { diff --git a/src/peer-store/proto-book.js b/src/peer-store/proto-book.js index 073b7e47e5..a08f5a284d 100644 --- a/src/peer-store/proto-book.js +++ b/src/peer-store/proto-book.js @@ -112,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} 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 diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js index c45ae2fa48..6cf9579878 100644 --- a/test/identify/index.spec.js +++ b/test/identify/index.spec.js @@ -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,22 +55,19 @@ describe('Identify', () => { libp2p: { peerId: localPeer, connectionManager: new EventEmitter(), - peerStore: new PeerStore({ peerId: localPeer }), + peerStore: localPeerStore, multiaddrs: listenMaddrs, - _options: { host: {} } - }, - protocols + isStarted: () => true + } }) - const remoteIdentify = new IdentifyService({ libp2p: { peerId: remotePeer, connectionManager: new EventEmitter(), - peerStore: new PeerStore({ peerId: remotePeer }), + peerStore: remotePeerStore, multiaddrs: listenMaddrs, - _options: { host: {} } - }, - protocols + isStarted: () => true + } }) const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234') @@ -110,22 +110,20 @@ describe('Identify', () => { libp2p: { peerId: localPeer, connectionManager: new EventEmitter(), - peerStore: new PeerStore({ peerId: localPeer }), + peerStore: localPeerStore, multiaddrs: listenMaddrs, - _options: { host: {} } - }, - protocols + isStarted: () => true + } }) const remoteIdentify = new IdentifyService({ libp2p: { peerId: remotePeer, connectionManager: new EventEmitter(), - peerStore: new PeerStore({ peerId: remotePeer }), + peerStore: remotePeerStore, multiaddrs: listenMaddrs, - _options: { host: {} } - }, - protocols + isStarted: () => true + } }) const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234') @@ -171,21 +169,17 @@ describe('Identify', () => { libp2p: { peerId: localPeer, connectionManager: new EventEmitter(), - peerStore: new PeerStore({ peerId: localPeer }), - multiaddrs: [], - _options: { host: {} } - }, - protocols + peerStore: localPeerStore, + multiaddrs: [] + } }) const remoteIdentify = new IdentifyService({ libp2p: { peerId: remotePeer, connectionManager: new EventEmitter(), - peerStore: new PeerStore({ peerId: remotePeer }), - multiaddrs: [], - _options: { host: {} } - }, - protocols + peerStore: remotePeerStore, + multiaddrs: [] + } }) const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234') @@ -242,35 +236,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 }), + peerStore: localPeerStore, multiaddrs: listenMaddrs, - _options: { host: {} } - }, - protocols: new Map([ - [multicodecs.IDENTIFY], - [multicodecs.IDENTIFY_PUSH], - ['/echo/1.0.0'] - ]) + 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 }), + peerStore: remotePeerStore, multiaddrs: [], - _options: { host: {} } + 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 } @@ -309,35 +306,39 @@ 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 }), + peerStore: localPeerStore, multiaddrs: listenMaddrs, - _options: { host: {} } - }, - protocols: new Map([ - [multicodecs.IDENTIFY], - [multicodecs.IDENTIFY_PUSH], - ['/echo/1.0.0'] - ]) + 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: [], - _options: { host: {} } + _options: { host: {} }, + 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 } diff --git a/test/peer-store/proto-book.spec.js b/test/peer-store/proto-book.spec.js index 15b5199757..06dfcdf798 100644 --- a/test/peer-store/proto-book.spec.js +++ b/test/peer-store/proto-book.spec.js @@ -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 From ef9d3ca2c6f35d692d6079e74088c5146d46eebe Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 15 Oct 2020 15:31:33 +0100 Subject: [PATCH 055/447] feat: custom announce filter --- doc/CONFIGURATION.md | 1 + package.json | 3 -- src/index.js | 6 ++-- test/addresses/address-manager.spec.js | 6 +--- test/addresses/addresses.node.js | 35 ++++++++++++++++--- test/connection-manager/index.node.js | 5 +-- test/connection-manager/index.spec.js | 5 +-- test/content-routing/content-routing.node.js | 5 +-- .../content-routing/dht/configuration.node.js | 5 +-- test/core/encryption.spec.js | 5 +-- test/core/listening.node.js | 4 +-- test/core/ping.node.js | 4 +-- test/dialing/dial-request.spec.js | 5 +-- test/dialing/direct.spec.js | 5 +-- test/dialing/utils.js | 0 test/identify/index.spec.js | 5 +-- test/insecure/plaintext.spec.js | 4 +-- test/keychain/cms-interop.spec.js | 5 +-- test/keychain/keychain.spec.js | 4 +-- test/keychain/peerid.spec.js | 5 +-- test/metrics/index.node.js | 5 +-- test/metrics/index.spec.js | 5 +-- test/peer-discovery/index.node.js | 4 +-- test/peer-discovery/index.spec.js | 4 +-- test/peer-routing/peer-routing.node.js | 4 +-- test/peer-store/address-book.spec.js | 5 +-- test/peer-store/key-book.spec.js | 4 +-- test/peer-store/metadata-book.spec.js | 4 +-- test/peer-store/peer-store.node.js | 4 +-- test/peer-store/peer-store.spec.js | 4 +-- test/peer-store/persisted-peer-store.spec.js | 4 +-- test/peer-store/proto-book.spec.js | 6 ++-- test/pnet/index.spec.js | 5 +-- test/pubsub/configuration.node.js | 5 +-- test/pubsub/implementations.node.js | 5 +-- test/pubsub/operation.node.js | 4 +-- test/record/envelope.spec.js | 6 +--- test/record/peer-record.spec.js | 4 +-- test/registrar/registrar.spec.js | 4 +-- test/relay/auto-relay.node.js | 5 +-- test/relay/relay.node.js | 5 +-- test/transports/transport-manager.node.js | 4 +-- test/transports/transport-manager.spec.js | 5 +-- 43 files changed, 75 insertions(+), 147 deletions(-) create mode 100644 test/dialing/utils.js diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index acd5660cf7..dcd277a24f 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -215,6 +215,7 @@ Besides the `modules` and `config`, libp2p allows other internal options and con - `listen` addresses will be provided to the libp2p underlying transports for listening on them. - `announce` addresses will be used to compute the advertises that the node should advertise to the network. - `noAnnounce` addresses will be used as a filter to compute the advertises that the node should advertise to the network. + - `announceFilter`: filter function used to filter announced addresses programmatically: `(ma: Array) => Array`. Default: bypass all addresses. [`libp2p-utils`](https://github.com/libp2p/js-libp2p-utils) provides useful [multiaddr utilities](https://github.com/libp2p/js-libp2p-utils/blob/master/API.md#multiaddr-isloopbackma) to create your filters. ### Examples diff --git a/package.json b/package.json index 00e0546fd9..2c9b6a271b 100644 --- a/package.json +++ b/package.json @@ -87,12 +87,9 @@ "@nodeutils/defaults-deep": "^1.1.0", "abortable-iterator": "^3.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", "delay": "^4.3.0", - "dirty-chai": "^2.0.1", "interop-libp2p": "^0.3.0", "ipfs-http-client": "^47.0.1", "it-concat": "^1.0.0", diff --git a/src/index.js b/src/index.js index 065acd841e..222577a283 100644 --- a/src/index.js +++ b/src/index.js @@ -367,11 +367,13 @@ class Libp2p extends EventEmitter { * @returns {Array} */ get multiaddrs () { + const announceFilter = this._options.addresses.announceFilter || ((multiaddrs) => multiaddrs) + // Filter noAnnounce multiaddrs const filterMa = this.addressManager.getNoAnnounceAddrs() // Create advertising list - return this.transportManager.getAddrs() + return announceFilter(this.transportManager.getAddrs() .concat(this.addressManager.getAnnounceAddrs()) .filter((ma, index, array) => { // Filter out if repeated @@ -385,7 +387,7 @@ class Libp2p extends EventEmitter { } return true - }) + })) } /** diff --git a/test/addresses/address-manager.spec.js b/test/addresses/address-manager.spec.js index 3e0d0efdc3..c1d98e839e 100644 --- a/test/addresses/address-manager.spec.js +++ b/test/addresses/address-manager.spec.js @@ -1,11 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -chai.use(require('chai-as-promised')) -const { expect } = chai - +const { expect } = require('aegir/utils/chai') const multiaddr = require('multiaddr') const AddressManager = require('../../src/address-manager') diff --git a/test/addresses/addresses.node.js b/test/addresses/addresses.node.js index 8993fe1c54..3797f2d5ce 100644 --- a/test/addresses/addresses.node.js +++ b/test/addresses/addresses.node.js @@ -1,17 +1,16 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -chai.use(require('chai-as-promised')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const sinon = require('sinon') +const isLoopback = require('libp2p-utils/src/multiaddr/is-loopback') + const { AddressesOptions } = require('./utils') const peerUtils = require('../utils/creators/peer') const listenAddresses = ['/ip4/127.0.0.1/tcp/0', '/ip4/127.0.0.1/tcp/8000/ws'] -const announceAddreses = ['/dns4/peer.io'] +const announceAddreses = ['/dns4/peer.io/tcp/433/p2p/12D3KooWNvSZnPi3RrhrTwEY4LuuBeB6K6facKUCJcyWG1aoDd2p'] describe('libp2p.multiaddrs', () => { let libp2p @@ -123,4 +122,30 @@ describe('libp2p.multiaddrs', () => { expect(advertiseMultiaddrs).to.not.include(m) }) }) + + it('can filter out loopback addresses to announced by the announce filter', async () => { + [libp2p] = await peerUtils.createPeer({ + started: false, + config: { + ...AddressesOptions, + addresses: { + listen: listenAddresses, + announce: announceAddreses, + announceFilter: (multiaddrs) => multiaddrs.filter(m => !isLoopback(m)) + } + } + }) + + const listenAddrs = libp2p.addressManager.listen + expect(listenAddrs.size).to.equal(listenAddresses.length) + expect(listenAddrs.has(listenAddresses[0])).to.equal(true) + expect(listenAddrs.has(listenAddresses[1])).to.equal(true) + + await libp2p.start() + + const multiaddrs = libp2p.multiaddrs + expect(multiaddrs.length).to.equal(announceAddreses.length) + expect(multiaddrs.includes(listenAddresses[0])).to.equal(false) + expect(multiaddrs.includes(listenAddresses[1])).to.equal(false) + }) }) diff --git a/test/connection-manager/index.node.js b/test/connection-manager/index.node.js index c1cc4ebcea..b2f3ba5d2b 100644 --- a/test/connection-manager/index.node.js +++ b/test/connection-manager/index.node.js @@ -1,10 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -chai.use(require('chai-as-promised')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const sinon = require('sinon') const delay = require('delay') diff --git a/test/connection-manager/index.spec.js b/test/connection-manager/index.spec.js index caf6becb8a..77e0934c64 100644 --- a/test/connection-manager/index.spec.js +++ b/test/connection-manager/index.spec.js @@ -1,10 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -chai.use(require('chai-as-promised')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const sinon = require('sinon') const peerUtils = require('../utils/creators/peer') diff --git a/test/content-routing/content-routing.node.js b/test/content-routing/content-routing.node.js index 17850e9924..1bef142d22 100644 --- a/test/content-routing/content-routing.node.js +++ b/test/content-routing/content-routing.node.js @@ -1,10 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -chai.use(require('chai-as-promised')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const nock = require('nock') const sinon = require('sinon') diff --git a/test/content-routing/dht/configuration.node.js b/test/content-routing/dht/configuration.node.js index e4a0bda929..9213d6e22d 100644 --- a/test/content-routing/dht/configuration.node.js +++ b/test/content-routing/dht/configuration.node.js @@ -1,10 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -const { expect } = chai - +const { expect } = require('aegir/utils/chai') const mergeOptions = require('merge-options') const { create } = require('../../../src') diff --git a/test/core/encryption.spec.js b/test/core/encryption.spec.js index 1173828568..bac84be800 100644 --- a/test/core/encryption.spec.js +++ b/test/core/encryption.spec.js @@ -1,10 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -chai.use(require('chai-as-promised')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const Transport = require('libp2p-websockets') const { NOISE: Crypto } = require('libp2p-noise') diff --git a/test/core/listening.node.js b/test/core/listening.node.js index 46976733c5..fe202b1ab1 100644 --- a/test/core/listening.node.js +++ b/test/core/listening.node.js @@ -1,9 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const Transport = require('libp2p-tcp') const { NOISE: Crypto } = require('libp2p-noise') diff --git a/test/core/ping.node.js b/test/core/ping.node.js index 2438758f69..510e5e3cb7 100644 --- a/test/core/ping.node.js +++ b/test/core/ping.node.js @@ -1,9 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const pTimes = require('p-times') const pipe = require('it-pipe') diff --git a/test/dialing/dial-request.spec.js b/test/dialing/dial-request.spec.js index f88db77275..4ca25e9d28 100644 --- a/test/dialing/dial-request.spec.js +++ b/test/dialing/dial-request.spec.js @@ -1,10 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -chai.use(require('chai-as-promised')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const sinon = require('sinon') const { AbortError } = require('libp2p-interfaces/src/transport/errors') diff --git a/test/dialing/direct.spec.js b/test/dialing/direct.spec.js index 8ef78a915b..f80e587465 100644 --- a/test/dialing/direct.spec.js +++ b/test/dialing/direct.spec.js @@ -1,10 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -chai.use(require('chai-as-promised')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const sinon = require('sinon') const pDefer = require('p-defer') const pWaitFor = require('p-wait-for') diff --git a/test/dialing/utils.js b/test/dialing/utils.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js index 6cf9579878..41a3701236 100644 --- a/test/identify/index.spec.js +++ b/test/identify/index.spec.js @@ -1,10 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -chai.use(require('chai-as-promised')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const sinon = require('sinon') const { EventEmitter } = require('events') diff --git a/test/insecure/plaintext.spec.js b/test/insecure/plaintext.spec.js index b0c0e9cb61..41f10c5a5b 100644 --- a/test/insecure/plaintext.spec.js +++ b/test/insecure/plaintext.spec.js @@ -1,9 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const sinon = require('sinon') const PeerId = require('peer-id') diff --git a/test/keychain/cms-interop.spec.js b/test/keychain/cms-interop.spec.js index 1c39eda5bd..546d163c79 100644 --- a/test/keychain/cms-interop.spec.js +++ b/test/keychain/cms-interop.spec.js @@ -2,10 +2,7 @@ /* eslint-env mocha */ 'use strict' -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) +const { chai, expect } = require('aegir/utils/chai') chai.use(require('chai-string')) const uint8ArrayFromString = require('uint8arrays/from-string') const uint8ArrayToString = require('uint8arrays/to-string') diff --git a/test/keychain/keychain.spec.js b/test/keychain/keychain.spec.js index 8124c72c2f..1b74c91eb2 100644 --- a/test/keychain/keychain.spec.js +++ b/test/keychain/keychain.spec.js @@ -73,9 +73,9 @@ describe('keychain', () => { 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()}` + const name = `key-${Math.random()}` - await keychainWithPassword.createKey(id, 'ed25519') + const { id } = await keychainWithPassword.createKey(name, 'ed25519') await expect(keychain.findKeyById(id)).to.eventually.be.ok() }) diff --git a/test/keychain/peerid.spec.js b/test/keychain/peerid.spec.js index 430d47a775..fd393d6a5c 100644 --- a/test/keychain/peerid.spec.js +++ b/test/keychain/peerid.spec.js @@ -1,10 +1,7 @@ /* eslint-env mocha */ 'use strict' -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) +const { expect } = require('aegir/utils/chai') const PeerId = require('peer-id') const multihash = require('multihashes') const crypto = require('libp2p-crypto') diff --git a/test/metrics/index.node.js b/test/metrics/index.node.js index 4387bf16e7..cdd43e7e88 100644 --- a/test/metrics/index.node.js +++ b/test/metrics/index.node.js @@ -1,10 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -chai.use(require('chai-as-promised')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const sinon = require('sinon') const { randomBytes } = require('libp2p-crypto') diff --git a/test/metrics/index.spec.js b/test/metrics/index.spec.js index da0c10d555..5f401e8ac6 100644 --- a/test/metrics/index.spec.js +++ b/test/metrics/index.spec.js @@ -1,10 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -chai.use(require('chai-as-promised')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const sinon = require('sinon') const { EventEmitter } = require('events') diff --git a/test/peer-discovery/index.node.js b/test/peer-discovery/index.node.js index bbea8a0a08..63976cb83c 100644 --- a/test/peer-discovery/index.node.js +++ b/test/peer-discovery/index.node.js @@ -1,9 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const sinon = require('sinon') const defer = require('p-defer') const mergeOptions = require('merge-options') diff --git a/test/peer-discovery/index.spec.js b/test/peer-discovery/index.spec.js index fcada30ca9..2fd037a0ef 100644 --- a/test/peer-discovery/index.spec.js +++ b/test/peer-discovery/index.spec.js @@ -1,9 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const sinon = require('sinon') const defer = require('p-defer') diff --git a/test/peer-routing/peer-routing.node.js b/test/peer-routing/peer-routing.node.js index 105ef6148f..cdce080c1e 100644 --- a/test/peer-routing/peer-routing.node.js +++ b/test/peer-routing/peer-routing.node.js @@ -1,9 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const nock = require('nock') const sinon = require('sinon') diff --git a/test/peer-store/address-book.spec.js b/test/peer-store/address-book.spec.js index a5578e918f..f6b8f28089 100644 --- a/test/peer-store/address-book.spec.js +++ b/test/peer-store/address-book.spec.js @@ -2,10 +2,7 @@ /* eslint-env mocha */ /* eslint max-nested-callbacks: ["error", 6] */ -const chai = require('chai') -chai.use(require('dirty-chai')) -const { expect } = chai - +const { expect } = require('aegir/utils/chai') const { Buffer } = require('buffer') const multiaddr = require('multiaddr') const arrayEquals = require('libp2p-utils/src/array-equals') diff --git a/test/peer-store/key-book.spec.js b/test/peer-store/key-book.spec.js index 4f51acb05e..af41a334e1 100644 --- a/test/peer-store/key-book.spec.js +++ b/test/peer-store/key-book.spec.js @@ -1,10 +1,8 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) +const { chai, expect } = require('aegir/utils/chai') chai.use(require('chai-bytes')) -const { expect } = chai const sinon = require('sinon') const PeerStore = require('../../src/peer-store') diff --git a/test/peer-store/metadata-book.spec.js b/test/peer-store/metadata-book.spec.js index 8d3e815bde..155b220599 100644 --- a/test/peer-store/metadata-book.spec.js +++ b/test/peer-store/metadata-book.spec.js @@ -1,10 +1,8 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) +const { chai, expect } = require('aegir/utils/chai') chai.use(require('chai-bytes')) -const { expect } = chai const uint8ArrayFromString = require('uint8arrays/from-string') const pDefer = require('p-defer') diff --git a/test/peer-store/peer-store.node.js b/test/peer-store/peer-store.node.js index f89c166bc6..8c322a2cce 100644 --- a/test/peer-store/peer-store.node.js +++ b/test/peer-store/peer-store.node.js @@ -1,10 +1,8 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) +const { chai, expect } = require('aegir/utils/chai') chai.use(require('chai-bytes')) -const { expect } = chai const sinon = require('sinon') const baseOptions = require('../utils/base-options') diff --git a/test/peer-store/peer-store.spec.js b/test/peer-store/peer-store.spec.js index c9d1880069..39c2c7187d 100644 --- a/test/peer-store/peer-store.spec.js +++ b/test/peer-store/peer-store.spec.js @@ -1,9 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const PeerStore = require('../../src/peer-store') const multiaddr = require('multiaddr') diff --git a/test/peer-store/persisted-peer-store.spec.js b/test/peer-store/persisted-peer-store.spec.js index 3c58e21dcc..e1d7047629 100644 --- a/test/peer-store/persisted-peer-store.spec.js +++ b/test/peer-store/persisted-peer-store.spec.js @@ -1,9 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const sinon = require('sinon') const Envelope = require('../../src/record/envelope') diff --git a/test/peer-store/proto-book.spec.js b/test/peer-store/proto-book.spec.js index 06dfcdf798..db05955f69 100644 --- a/test/peer-store/proto-book.spec.js +++ b/test/peer-store/proto-book.spec.js @@ -1,11 +1,9 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -const { expect } = chai - +const { expect } = require('aegir/utils/chai') const sinon = require('sinon') + const pDefer = require('p-defer') const pWaitFor = require('p-wait-for') diff --git a/test/pnet/index.spec.js b/test/pnet/index.spec.js index 9fa2b9f2ae..98327146d8 100644 --- a/test/pnet/index.spec.js +++ b/test/pnet/index.spec.js @@ -1,10 +1,7 @@ /* eslint-env mocha */ 'use strict' -const chai = require('chai') -const dirtyChai = require('dirty-chai') -chai.use(dirtyChai) -const expect = chai.expect +const { expect } = require('aegir/utils/chai') const duplexPair = require('it-pair/duplex') const pipe = require('it-pipe') const { collect } = require('streaming-iterables') diff --git a/test/pubsub/configuration.node.js b/test/pubsub/configuration.node.js index ed8cd90c44..12b0b584a6 100644 --- a/test/pubsub/configuration.node.js +++ b/test/pubsub/configuration.node.js @@ -1,10 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -const { expect } = chai - +const { expect } = require('aegir/utils/chai') const mergeOptions = require('merge-options') const multiaddr = require('multiaddr') diff --git a/test/pubsub/implementations.node.js b/test/pubsub/implementations.node.js index a17d7744ed..3243d94b94 100644 --- a/test/pubsub/implementations.node.js +++ b/test/pubsub/implementations.node.js @@ -1,10 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -const { expect } = chai - +const { expect } = require('aegir/utils/chai') const pWaitFor = require('p-wait-for') const pDefer = require('p-defer') const mergeOptions = require('merge-options') diff --git a/test/pubsub/operation.node.js b/test/pubsub/operation.node.js index 08e2afbc02..2ebe39dae4 100644 --- a/test/pubsub/operation.node.js +++ b/test/pubsub/operation.node.js @@ -1,9 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const sinon = require('sinon') const pWaitFor = require('p-wait-for') diff --git a/test/record/envelope.spec.js b/test/record/envelope.spec.js index 81cbfb1296..ca7408f26f 100644 --- a/test/record/envelope.spec.js +++ b/test/record/envelope.spec.js @@ -1,12 +1,8 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) +const { chai, expect } = require('aegir/utils/chai') chai.use(require('chai-bytes')) -chai.use(require('chai-as-promised')) -const { expect } = chai - const uint8arrayFromString = require('uint8arrays/from-string') const uint8arrayEquals = require('uint8arrays/equals') const Envelope = require('../../src/record/envelope') diff --git a/test/record/peer-record.spec.js b/test/record/peer-record.spec.js index 638e145835..e8b8ed64cd 100644 --- a/test/record/peer-record.spec.js +++ b/test/record/peer-record.spec.js @@ -1,9 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const tests = require('libp2p-interfaces/src/record/tests') const multiaddr = require('multiaddr') diff --git a/test/registrar/registrar.spec.js b/test/registrar/registrar.spec.js index 6befd0599b..8f90b9fcf4 100644 --- a/test/registrar/registrar.spec.js +++ b/test/registrar/registrar.spec.js @@ -1,9 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const pDefer = require('p-defer') const { EventEmitter } = require('events') diff --git a/test/relay/auto-relay.node.js b/test/relay/auto-relay.node.js index 8d0cdfd27e..2411f48a57 100644 --- a/test/relay/auto-relay.node.js +++ b/test/relay/auto-relay.node.js @@ -1,10 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -const { expect } = chai - +const { expect } = require('aegir/utils/chai') const delay = require('delay') const pWaitFor = require('p-wait-for') const sinon = require('sinon') diff --git a/test/relay/relay.node.js b/test/relay/relay.node.js index 67f90a7f98..163bd83105 100644 --- a/test/relay/relay.node.js +++ b/test/relay/relay.node.js @@ -1,10 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -chai.use(require('chai-as-promised')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const sinon = require('sinon') const multiaddr = require('multiaddr') diff --git a/test/transports/transport-manager.node.js b/test/transports/transport-manager.node.js index 2e17f88b59..fae5576a8f 100644 --- a/test/transports/transport-manager.node.js +++ b/test/transports/transport-manager.node.js @@ -1,9 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const AddressManager = require('../../src/address-manager') const TransportManager = require('../../src/transport-manager') diff --git a/test/transports/transport-manager.spec.js b/test/transports/transport-manager.spec.js index 9f302b6cfa..cbdef79b0a 100644 --- a/test/transports/transport-manager.spec.js +++ b/test/transports/transport-manager.spec.js @@ -1,10 +1,7 @@ 'use strict' /* eslint-env mocha */ -const chai = require('chai') -chai.use(require('dirty-chai')) -chai.use(require('chai-as-promised')) -const { expect } = chai +const { expect } = require('aegir/utils/chai') const sinon = require('sinon') const multiaddr = require('multiaddr') From 5758db8ea9c53fbbcbcd06c1e7fe0867c583714f Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 27 Oct 2020 15:58:18 +0000 Subject: [PATCH 056/447] chore: remove noAnnounce from address manager --- doc/API.md | 25 +------- doc/CONFIGURATION.md | 5 +- src/address-manager/index.js | 20 ++---- src/index.js | 24 ++----- test/addresses/address-manager.spec.js | 23 +------ test/addresses/addresses.node.js | 87 +++++++++----------------- 6 files changed, 45 insertions(+), 139 deletions(-) diff --git a/doc/API.md b/doc/API.md index ee14ee140c..b199d55687 100644 --- a/doc/API.md +++ b/doc/API.md @@ -13,8 +13,7 @@ * [`ping`](#ping) * [`multiaddrs`](#multiaddrs) * [`addressManager.getListenAddrs`](#addressmanagergetlistenaddrs) - * [`addressmger.getAnnounceAddrs`](#addressmanagergetannounceaddrs) - * [`addressManager.getNoAnnounceAddrs`](#addressmanagergetnoannounceaddrs) + * [`addressManager.getAnnounceAddrs`](#addressmanagergetannounceaddrs) * [`contentRouting.findProviders`](#contentroutingfindproviders) * [`contentRouting.provide`](#contentroutingprovide) * [`contentRouting.put`](#contentroutingput) @@ -91,7 +90,7 @@ Creates an instance of Libp2p. |------|------|-------------| | options | `object` | libp2p options | | options.modules | [`Array`](./CONFIGURATION.md#modules) | libp2p [modules](./CONFIGURATION.md#modules) to use | -| [options.addresses] | `{ listen: Array, announce: Array, noAnnounce: Array }` | Addresses for transport listening and to advertise to the network | +| [options.addresses] | `{ listen: Array, announce: Array, announceFilter: (ma: Array) => Array }` | Addresses for transport listening and to advertise to the network | | [options.config] | `object` | libp2p modules configuration and core configuration | | [options.host] | `{ agentVersion: string }` | libp2p host options | | [options.connectionManager] | [`object`](./CONFIGURATION.md#configuring-connection-manager) | libp2p Connection Manager [configuration](./CONFIGURATION.md#configuring-connection-manager) | @@ -515,26 +514,6 @@ const announceMa = libp2p.addressManager.getAnnounceAddrs() // [ ] ``` -### addressManager.getNoAnnounceAddrs - -Get the multiaddrs that were provided to not announce to the network. - -`libp2p.addressManager.getNoAnnounceAddrs()` - -#### Returns - -| Type | Description | -|------|-------------| -| `Array` | Provided noAnnounce multiaddrs | - -#### Example - -```js -// ... -const noAnnounceMa = libp2p.addressManager.getNoAnnounceAddrs() -// [ ] -``` - ### transportManager.getAddrs Get the multiaddrs that libp2p transports are using to listen on. diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index dcd277a24f..1a39381aad 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -211,11 +211,10 @@ Besides the `modules` and `config`, libp2p allows other internal options and con - This is used in modules such as the DHT. If it is not provided, `js-libp2p` will use an in memory datastore. - `peerId`: the identity of the node, an instance of [libp2p/js-peer-id](https://github.com/libp2p/js-peer-id). - This is particularly useful if you want to reuse the same `peer-id`, as well as for modules like `libp2p-delegated-content-routing`, which need a `peer-id` in their instantiation. -- `addresses`: an object containing `listen`, `announce` and `noAnnounce` properties with `Array`: +- `addresses`: an object containing `listen`, `announce` and `announceFilter`: - `listen` addresses will be provided to the libp2p underlying transports for listening on them. - `announce` addresses will be used to compute the advertises that the node should advertise to the network. - - `noAnnounce` addresses will be used as a filter to compute the advertises that the node should advertise to the network. - - `announceFilter`: filter function used to filter announced addresses programmatically: `(ma: Array) => Array`. Default: bypass all addresses. [`libp2p-utils`](https://github.com/libp2p/js-libp2p-utils) provides useful [multiaddr utilities](https://github.com/libp2p/js-libp2p-utils/blob/master/API.md#multiaddr-isloopbackma) to create your filters. + - `announceFilter`: filter function used to filter announced addresses programmatically: `(ma: Array) => Array`. Default: returns all addresses. [`libp2p-utils`](https://github.com/libp2p/js-libp2p-utils) provides useful [multiaddr utilities](https://github.com/libp2p/js-libp2p-utils/blob/master/API.md#multiaddr-isloopbackma) to create your filters. ### Examples diff --git a/src/address-manager/index.js b/src/address-manager/index.js index 0ba0df03c3..314f0a1ae6 100644 --- a/src/address-manager/index.js +++ b/src/address-manager/index.js @@ -7,11 +7,10 @@ log.error = debug('libp2p:addresses:error') const multiaddr = require('multiaddr') /** - * Responsible for managing this peers addresses. - * Peers can specify their listen, announce and noAnnounce addresses. + * Responsible for managing the peer addresses. + * Peers can specify their listen and announce addresses. * The listen addresses will be used by the libp2p transports to listen for new connections, - * while the announce an noAnnounce addresses will be combined with the listen addresses for - * address adverstising to other peers in the network. + * while the announce addresses will be used for the peer addresses' to other peers in the network. */ class AddressManager { /** @@ -19,12 +18,10 @@ class AddressManager { * @param {object} [options] * @param {Array} [options.listen = []] - list of multiaddrs string representation to listen. * @param {Array} [options.announce = []] - list of multiaddrs string representation to announce. - * @param {Array} [options.noAnnounce = []] - list of multiaddrs string representation to not announce. */ - constructor ({ listen = [], announce = [], noAnnounce = [] } = {}) { + constructor ({ listen = [], announce = [] } = {}) { this.listen = new Set(listen) this.announce = new Set(announce) - this.noAnnounce = new Set(noAnnounce) } /** @@ -44,15 +41,6 @@ class AddressManager { getAnnounceAddrs () { return Array.from(this.announce).map((a) => multiaddr(a)) } - - /** - * Get peer noAnnouncing multiaddrs. - * - * @returns {Array} - */ - getNoAnnounceAddrs () { - return Array.from(this.noAnnounce).map((a) => multiaddr(a)) - } } module.exports = AddressManager diff --git a/src/index.js b/src/index.js index 222577a283..e2950f1d4f 100644 --- a/src/index.js +++ b/src/index.js @@ -367,27 +367,15 @@ class Libp2p extends EventEmitter { * @returns {Array} */ get multiaddrs () { - const announceFilter = this._options.addresses.announceFilter || ((multiaddrs) => multiaddrs) + const announceAddrs = this.addressManager.getAnnounceAddrs() + if (announceAddrs.length) { + return announceAddrs + } - // Filter noAnnounce multiaddrs - const filterMa = this.addressManager.getNoAnnounceAddrs() + const announceFilter = this._options.addresses.announceFilter || ((multiaddrs) => multiaddrs) // Create advertising list - return announceFilter(this.transportManager.getAddrs() - .concat(this.addressManager.getAnnounceAddrs()) - .filter((ma, index, array) => { - // Filter out if repeated - if (array.findIndex((otherMa) => otherMa.equals(ma)) !== index) { - return false - } - - // Filter out if in noAnnounceMultiaddrs - if (filterMa.find((fm) => fm.equals(ma))) { - return false - } - - return true - })) + return announceFilter(this.transportManager.getAddrs()) } /** diff --git a/test/addresses/address-manager.spec.js b/test/addresses/address-manager.spec.js index c1d98e839e..4d58387a23 100644 --- a/test/addresses/address-manager.spec.js +++ b/test/addresses/address-manager.spec.js @@ -16,7 +16,6 @@ describe('Address Manager', () => { expect(am.listen.size).to.equal(0) expect(am.announce.size).to.equal(0) - expect(am.noAnnounce.size).to.equal(0) }) it('should return listen multiaddrs on get', () => { @@ -26,7 +25,6 @@ describe('Address Manager', () => { expect(am.listen.size).to.equal(listenAddresses.length) expect(am.announce.size).to.equal(0) - expect(am.noAnnounce.size).to.equal(0) const listenMultiaddrs = am.getListenAddrs() expect(listenMultiaddrs.length).to.equal(2) @@ -42,28 +40,11 @@ describe('Address Manager', () => { expect(am.listen.size).to.equal(listenAddresses.length) expect(am.announce.size).to.equal(announceAddreses.length) - expect(am.noAnnounce.size).to.equal(0) const announceMultiaddrs = am.getAnnounceAddrs() expect(announceMultiaddrs.length).to.equal(1) expect(announceMultiaddrs[0].equals(multiaddr(announceAddreses[0]))).to.equal(true) }) - - it('should return noAnnounce multiaddrs on get', () => { - const am = new AddressManager({ - listen: listenAddresses, - noAnnounce: listenAddresses - }) - - expect(am.listen.size).to.equal(listenAddresses.length) - expect(am.announce.size).to.equal(0) - expect(am.noAnnounce.size).to.equal(listenAddresses.length) - - const noAnnounceMultiaddrs = am.getNoAnnounceAddrs() - expect(noAnnounceMultiaddrs.length).to.equal(2) - expect(noAnnounceMultiaddrs[0].equals(multiaddr(listenAddresses[0]))).to.equal(true) - expect(noAnnounceMultiaddrs[1].equals(multiaddr(listenAddresses[1]))).to.equal(true) - }) }) describe('libp2p.addressManager', () => { @@ -76,14 +57,12 @@ describe('libp2p.addressManager', () => { config: { addresses: { listen: listenAddresses, - announce: announceAddreses, - noAnnounce: listenAddresses + announce: announceAddreses } } }) expect(libp2p.addressManager.listen.size).to.equal(listenAddresses.length) expect(libp2p.addressManager.announce.size).to.equal(announceAddreses.length) - expect(libp2p.addressManager.noAnnounce.size).to.equal(listenAddresses.length) }) }) diff --git a/test/addresses/addresses.node.js b/test/addresses/addresses.node.js index 3797f2d5ce..a45bad001f 100644 --- a/test/addresses/addresses.node.js +++ b/test/addresses/addresses.node.js @@ -4,6 +4,7 @@ const { expect } = require('aegir/utils/chai') const sinon = require('sinon') +const multiaddr = require('multiaddr') const isLoopback = require('libp2p-utils/src/multiaddr/is-loopback') const { AddressesOptions } = require('./utils') @@ -44,108 +45,80 @@ describe('libp2p.multiaddrs', () => { expect(listenAddrs.has(listenAddresses[1])).to.equal(true) }) - it('should advertise all addresses if noAnnounce addresses are not provided, but with correct ports', async () => { + it('should announce transport listen addresses if announce addresses are not provided', async () => { [libp2p] = await peerUtils.createPeer({ + started: false, config: { ...AddressesOptions, addresses: { - listen: listenAddresses, - announce: announceAddreses + listen: listenAddresses } } }) - const tmListen = libp2p.transportManager.getAddrs().map((ma) => ma.toString()) + await libp2p.start() - const spyAnnounce = sinon.spy(libp2p.addressManager, 'getAnnounceAddrs') - const spyNoAnnounce = sinon.spy(libp2p.addressManager, 'getNoAnnounceAddrs') - const spyListen = sinon.spy(libp2p.addressManager, 'getListenAddrs') - const spyTranspMgr = sinon.spy(libp2p.transportManager, 'getAddrs') + const tmListen = libp2p.transportManager.getAddrs().map((ma) => ma.toString()) + // Announce 2 listen (transport) const advertiseMultiaddrs = libp2p.multiaddrs.map((ma) => ma.toString()) - - expect(spyAnnounce).to.have.property('callCount', 1) - expect(spyNoAnnounce).to.have.property('callCount', 1) - expect(spyListen).to.have.property('callCount', 0) // Listen addr should not be used - expect(spyTranspMgr).to.have.property('callCount', 1) - - // Announce 2 listen (transport) + 1 announce - expect(advertiseMultiaddrs.length).to.equal(3) + expect(advertiseMultiaddrs.length).to.equal(2) tmListen.forEach((m) => { expect(advertiseMultiaddrs).to.include(m) }) - announceAddreses.forEach((m) => { - expect(advertiseMultiaddrs).to.include(m) - }) expect(advertiseMultiaddrs).to.not.include(listenAddresses[0]) // Random Port switch }) - it('should remove replicated addresses', async () => { + it('should only announce the given announce addresses when provided', async () => { [libp2p] = await peerUtils.createPeer({ + started: false, config: { ...AddressesOptions, addresses: { listen: listenAddresses, - announce: [listenAddresses[1]] + announce: announceAddreses } } }) - const advertiseMultiaddrs = libp2p.multiaddrs.map((ma) => ma.toString()) - - // Announce 2 listen (transport), ignoring duplicated in announce - expect(advertiseMultiaddrs.length).to.equal(2) - }) + await libp2p.start() - it('should not advertise noAnnounce addresses', async () => { - const noAnnounce = [listenAddresses[1]] - ;[libp2p] = await peerUtils.createPeer({ - config: { - ...AddressesOptions, - addresses: { - listen: listenAddresses, - announce: announceAddreses, - noAnnounce - } - } - }) + const tmListen = libp2p.transportManager.getAddrs().map((ma) => ma.toString()) + // Announce 1 announce addr const advertiseMultiaddrs = libp2p.multiaddrs.map((ma) => ma.toString()) - - // Announce 1 listen (transport) not in the noAnnounce and the announce - expect(advertiseMultiaddrs.length).to.equal(2) - - announceAddreses.forEach((m) => { - expect(advertiseMultiaddrs).to.include(m) - }) - noAnnounce.forEach((m) => { - expect(advertiseMultiaddrs).to.not.include(m) + expect(advertiseMultiaddrs.length).to.equal(announceAddreses.length) + advertiseMultiaddrs.forEach((m) => { + expect(tmListen).to.not.include(m) + expect(announceAddreses).to.include(m) }) }) - it('can filter out loopback addresses to announced by the announce filter', async () => { + it('can filter out loopback addresses by the announce filter', async () => { [libp2p] = await peerUtils.createPeer({ started: false, config: { ...AddressesOptions, addresses: { listen: listenAddresses, - announce: announceAddreses, announceFilter: (multiaddrs) => multiaddrs.filter(m => !isLoopback(m)) } } }) - const listenAddrs = libp2p.addressManager.listen - expect(listenAddrs.size).to.equal(listenAddresses.length) - expect(listenAddrs.has(listenAddresses[0])).to.equal(true) - expect(listenAddrs.has(listenAddresses[1])).to.equal(true) - await libp2p.start() + expect(libp2p.multiaddrs.length).to.equal(0) + + // Stub transportManager addresses to add a public address + const stubMa = multiaddr('/ip4/120.220.10.1/tcp/1000') + sinon.stub(libp2p.transportManager, 'getAddrs').returns([ + ...listenAddresses.map((a) => multiaddr(a)), + stubMa + ]) + const multiaddrs = libp2p.multiaddrs - expect(multiaddrs.length).to.equal(announceAddreses.length) - expect(multiaddrs.includes(listenAddresses[0])).to.equal(false) - expect(multiaddrs.includes(listenAddresses[1])).to.equal(false) + expect(multiaddrs.length).to.equal(1) + expect(multiaddrs[0].equals(stubMa)).to.eql(true) }) }) From 1a13e2c6ca03a78dedb721b7b5db8951d3948117 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 3 Nov 2020 14:32:10 +0100 Subject: [PATCH 057/447] chore: update address manager readme --- src/address-manager/README.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/address-manager/README.md b/src/address-manager/README.md index 53f9324802..44b107c892 100644 --- a/src/address-manager/README.md +++ b/src/address-manager/README.md @@ -1,6 +1,6 @@ # Address Manager -The Address manager is responsible for keeping an updated register of the peer's addresses. It includes 3 different types of Addresses: `Listen Addresses`, `Announce Addresses` and `No Announce Addresses`. +The Address manager is responsible for keeping an updated register of the peer's addresses. It includes 2 different types of Addresses: `Listen Addresses` and `Announce Addresses`. These Addresses should be specified in your libp2p [configuration](../../doc/CONFIGURATION.md) when you create your node. @@ -20,17 +20,11 @@ Scenarios for Announce Addresses include: - when you setup a libp2p node in your private network at home, but you need to announce your public IP Address to the outside world; - when you want to announce a DNS address, which maps to your public IP Address. -## No Announce Addresses - -While we need to add Announce Addresses to enable peers' connectivity, we should also avoid announcing addresses that will not be reachable. No Announce Addresses should be specified so that they are filtered from the advertised multiaddrs. - -As stated in the Listen Addresses section, Listen Addresses might be modified by libp2p transports after the successfully bind to those addresses. Libp2p should also take these changes into account so that they can be matched when No Announce Addresses are being filtered out of the advertised multiaddrs. - ## Implementation When a libp2p node is created, the Address Manager will be populated from the provided addresses through the libp2p configuration. Once the node is started, the Transport Manager component will gather the listen addresses from the Address Manager, so that the libp2p transports can attempt to bind to them. -Libp2p will use the the Address Manager as the source of truth when advertising the peers addresses. After all transports are ready, other libp2p components/subsystems will kickoff, namely the Identify Service and the DHT. Both of them will announce the node addresses to the other peers in the network. The announce and noAnnounce addresses will have an important role here and will be gathered by libp2p to compute its current addresses to advertise everytime it is needed. +Libp2p will use the the Address Manager as the source of truth when advertising the peers addresses. After all transports are ready, other libp2p components/subsystems will kickoff, namely the Identify Service and the DHT. Both of them will announce the node addresses to the other peers in the network. The announce addresses will have an important role here and will be gathered by libp2p to compute its current addresses to advertise everytime it is needed. ## Future Considerations From 689c35ed1c68e514293a9895d496e2e8440454e9 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 10 Nov 2020 09:14:13 +0100 Subject: [PATCH 058/447] fix: remove test/dialing/utils extra file --- test/dialing/utils.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test/dialing/utils.js diff --git a/test/dialing/utils.js b/test/dialing/utils.js deleted file mode 100644 index e69de29bb2..0000000000 From 49fffda23c8894d5b0e5134786f41e1c659d8e68 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 15 Oct 2020 15:31:33 +0100 Subject: [PATCH 059/447] test: custom announce filter --- test/addresses/addresses.node.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/addresses/addresses.node.js b/test/addresses/addresses.node.js index a45bad001f..2d6bceade5 100644 --- a/test/addresses/addresses.node.js +++ b/test/addresses/addresses.node.js @@ -121,4 +121,30 @@ describe('libp2p.multiaddrs', () => { expect(multiaddrs.length).to.equal(1) expect(multiaddrs[0].equals(stubMa)).to.eql(true) }) + + it('can filter out loopback addresses to announced by the announce filter', async () => { + [libp2p] = await peerUtils.createPeer({ + started: false, + config: { + ...AddressesOptions, + addresses: { + listen: listenAddresses, + announce: announceAddreses, + announceFilter: (multiaddrs) => multiaddrs.filter(m => !isLoopback(m)) + } + } + }) + + const listenAddrs = libp2p.addressManager.listen + expect(listenAddrs.size).to.equal(listenAddresses.length) + expect(listenAddrs.has(listenAddresses[0])).to.equal(true) + expect(listenAddrs.has(listenAddresses[1])).to.equal(true) + + await libp2p.start() + + const multiaddrs = libp2p.multiaddrs + expect(multiaddrs.length).to.equal(announceAddreses.length) + expect(multiaddrs.includes(listenAddresses[0])).to.equal(false) + expect(multiaddrs.includes(listenAddresses[1])).to.equal(false) + }) }) From e50c6abcf2ebc80ebf2dfadd015ab21a20cffadc Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 13 Nov 2020 15:14:21 +0100 Subject: [PATCH 060/447] chore: update pubsub (#801) BREAKING CHANGE: pubsub signing policy properties were changed according to libp2p-interfaces changes to a single property. The emitSelf option default value was also modified to match the routers value --- doc/CONFIGURATION.md | 7 ++++--- package.json | 6 +++--- src/config.js | 5 +---- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 1a39381aad..7af18d8bd4 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -322,6 +322,8 @@ const MPLEX = require('libp2p-mplex') const { NOISE } = require('libp2p-noise') const GossipSub = require('libp2p-gossipsub') +const { SignaturePolicy } = require('libp2p-interfaces/src/pubsub/signature-policy') + const node = await Libp2p.create({ modules: { transport: [TCP], @@ -332,9 +334,8 @@ const node = await Libp2p.create({ config: { pubsub: { // The pubsub options (and defaults) can be found in the pubsub router documentation enabled: true, - emitSelf: true, // whether the node should emit to self on publish - signMessages: true, // if messages should be signed - strictSigning: true // if message signing should be required + emitSelf: false, // whether the node should emit to self on publish + globalSignaturePolicy: SignaturePolicy.StrictSign // message signing policy } } }) diff --git a/package.json b/package.json index 2c9b6a271b..0c3e7c734d 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "it-pipe": "^1.1.0", "it-protocol-buffers": "^0.2.0", "libp2p-crypto": "^0.18.0", - "libp2p-interfaces": "^0.5.1", + "libp2p-interfaces": "^0.7.2", "libp2p-utils": "^0.2.1", "mafmt": "^8.0.0", "merge-options": "^2.0.0", @@ -99,8 +99,8 @@ "libp2p-bootstrap": "^0.12.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-floodsub": "^0.24.0", + "libp2p-gossipsub": "^0.7.0", "libp2p-kad-dht": "^0.20.0", "libp2p-mdns": "^0.15.0", "libp2p-mplex": "^0.10.1", diff --git a/src/config.js b/src/config.js index 4c9bc54954..c95b1b159e 100644 --- a/src/config.js +++ b/src/config.js @@ -54,10 +54,7 @@ const DefaultConfig = { autoDial: true }, pubsub: { - enabled: true, - emitSelf: true, - signMessages: true, - strictSigning: true + enabled: true }, relay: { enabled: true, From 585ad52b4c71dd7514e99a287e0318b2b837ec48 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 20 Nov 2020 15:16:40 +0100 Subject: [PATCH 061/447] feat: custom dialer addr sorter (#792) * feat: custom dialer addr sorter * chore: use libp2p utils sorter via addressBook getMultiaddrsForPeer * chore: use new libp2p utils * chore: apply suggestions from code review Co-authored-by: Jacob Heun Co-authored-by: Jacob Heun --- doc/CONFIGURATION.md | 5 ++- package.json | 2 +- src/circuit/auto-relay.js | 57 +++++----------------------- src/config.js | 4 +- src/dialer/index.js | 6 ++- src/index.js | 3 +- src/peer-store/address-book.js | 8 ++-- test/dialing/direct.spec.js | 33 ++++++++++++++++ test/peer-store/address-book.spec.js | 15 +++++++- 9 files changed, 76 insertions(+), 57 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 7af18d8bd4..917ec7cfb7 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -499,6 +499,7 @@ Dialing in libp2p can be configured to limit the rate of dialing, and how long d | 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 | +| addressSorter | `(Array
) => Array
` | Sort the known addresses of a peer before trying to dial. | The below configuration example shows how the dialer should be configured, with the current defaults: @@ -509,6 +510,7 @@ const MPLEX = require('libp2p-mplex') const { NOISE } = require('libp2p-noise') const { dnsaddrResolver } = require('multiaddr/src/resolvers') +const { publicAddressesFirst } = require('libp2p-utils/src/address-sort') const node = await Libp2p.create({ modules: { @@ -522,7 +524,8 @@ const node = await Libp2p.create({ dialTimeout: 30e3, resolvers: { dnsaddr: dnsaddrResolver - } + }, + addressSorter: publicAddressesFirst } ``` diff --git a/package.json b/package.json index 0c3e7c734d..38bbd7ff07 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "it-protocol-buffers": "^0.2.0", "libp2p-crypto": "^0.18.0", "libp2p-interfaces": "^0.7.2", - "libp2p-utils": "^0.2.1", + "libp2p-utils": "^0.2.2", "mafmt": "^8.0.0", "merge-options": "^2.0.0", "moving-average": "^1.0.0", diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js index 00ebc6f588..6b9a40392d 100644 --- a/src/circuit/auto-relay.js +++ b/src/circuit/auto-relay.js @@ -4,8 +4,6 @@ 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') @@ -36,6 +34,7 @@ class AutoRelay { this._peerStore = libp2p.peerStore this._connectionManager = libp2p.connectionManager this._transportManager = libp2p.transportManager + this._addressSorter = libp2p.dialer.addressSorter this.maxListeners = maxListeners @@ -129,37 +128,19 @@ class AutoRelay { 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 - } + // Get peer known addresses and sort them per public addresses first + const remoteAddrs = this._peerStore.addressBook.getMultiaddrsForPeer( + connection.remotePeer, this._addressSorter + ) - if (!remoteMultiaddr.protoNames().includes('p2p')) { - listenAddr = `${remoteMultiaddr.toString()}/p2p/${connection.remotePeer.toB58String()}/p2p-circuit` - } else { - listenAddr = `${remoteMultiaddr.toString()}/p2p-circuit` + if (!remoteAddrs || !remoteAddrs.length) { + return } - // Attempt to listen on relay + const listenAddr = `${remoteAddrs[0].toString()}/p2p-circuit` this._listenRelays.add(id) + // Attempt to listen on relay try { await this._transportManager.listen([multiaddr(listenAddr)]) // Announce multiaddrs will update on listen success by TransportManager event being triggered @@ -269,24 +250,4 @@ class AutoRelay { } } -/** - * 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 diff --git a/src/config.js b/src/config.js index c95b1b159e..1134050c14 100644 --- a/src/config.js +++ b/src/config.js @@ -7,6 +7,7 @@ const Constants = require('./constants') const { AGENT_VERSION } = require('./identify/consts') const RelayConstants = require('./circuit/constants') +const { publicAddressesFirst } = require('libp2p-utils/src/address-sort') const { FaultTolerance } = require('./transport-manager') const DefaultConfig = { @@ -27,7 +28,8 @@ const DefaultConfig = { dialTimeout: Constants.DIAL_TIMEOUT, resolvers: { dnsaddr: dnsaddrResolver - } + }, + addressSorter: publicAddressesFirst }, host: { agentVersion: AGENT_VERSION diff --git a/src/dialer/index.js b/src/dialer/index.js index 49d77e467d..3ee3ada6c8 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -9,6 +9,7 @@ const log = debug('libp2p:dialer') log.error = debug('libp2p:dialer:error') const { DialRequest } = require('./dial-request') +const { publicAddressesFirst } = require('libp2p-utils/src/address-sort') const getPeer = require('../get-peer') const { codes } = require('../errors') @@ -24,6 +25,7 @@ class Dialer { * @param {object} options * @param {TransportManager} options.transportManager * @param {Peerstore} options.peerStore + * @param {(addresses: Array Array
} [options.addressSorter = publicAddressesFirst] - Sort the known addresses of a peer before trying to dial. * @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. @@ -32,6 +34,7 @@ class Dialer { constructor ({ transportManager, peerStore, + addressSorter = publicAddressesFirst, concurrency = MAX_PARALLEL_DIALS, timeout = DIAL_TIMEOUT, perPeerLimit = MAX_PER_PEER_DIALS, @@ -39,6 +42,7 @@ class Dialer { }) { this.transportManager = transportManager this.peerStore = peerStore + this.addressSorter = addressSorter this.concurrency = concurrency this.timeout = timeout this.perPeerLimit = perPeerLimit @@ -120,7 +124,7 @@ class Dialer { this.peerStore.addressBook.add(id, multiaddrs) } - let knownAddrs = this.peerStore.addressBook.getMultiaddrsForPeer(id) || [] + let knownAddrs = this.peerStore.addressBook.getMultiaddrsForPeer(id, this.addressSorter) || [] // 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. diff --git a/src/index.js b/src/index.js index e2950f1d4f..23ecae3d19 100644 --- a/src/index.js +++ b/src/index.js @@ -136,7 +136,8 @@ class Libp2p extends EventEmitter { concurrency: this._options.dialer.maxParallelDials, perPeerLimit: this._options.dialer.maxDialsPerPeer, timeout: this._options.dialer.dialTimeout, - resolvers: this._options.dialer.resolvers + resolvers: this._options.dialer.resolvers, + addressSorter: this._options.dialer.addressSorter }) this._modules.transport.forEach((Transport) => { diff --git a/src/peer-store/address-book.js b/src/peer-store/address-book.js index 88ed327b3d..07d8af59b2 100644 --- a/src/peer-store/address-book.js +++ b/src/peer-store/address-book.js @@ -319,20 +319,22 @@ class AddressBook extends Book { * Returns `undefined` if there are no known multiaddrs for the given peer. * * @param {PeerId} peerId + * @param {(addresses: Array Array
} [addressSorter] * @returns {Array|undefined} */ - getMultiaddrsForPeer (peerId) { + getMultiaddrsForPeer (peerId, addressSorter = (ms) => ms) { if (!PeerId.isPeerId(peerId)) { throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) } const entry = this.data.get(peerId.toB58String()) - if (!entry || !entry.addresses) { return undefined } - return entry.addresses.map((address) => { + return addressSorter( + entry.addresses || [] + ).map((address) => { const multiaddr = address.multiaddr const idString = multiaddr.getPeerId() diff --git a/test/dialing/direct.spec.js b/test/dialing/direct.spec.js index f80e587465..c67f3ab8f2 100644 --- a/test/dialing/direct.spec.js +++ b/test/dialing/direct.spec.js @@ -16,6 +16,7 @@ const { AbortError } = require('libp2p-interfaces/src/transport/errors') const { codes: ErrorCodes } = require('../../src/errors') const Constants = require('../../src/constants') const Dialer = require('../../src/dialer') +const addressSort = require('libp2p-utils/src/address-sort') const PeerStore = require('../../src/peer-store') const TransportManager = require('../../src/transport-manager') const Libp2p = require('../../src') @@ -44,6 +45,7 @@ describe('Dialing (direct, WebSockets)', () => { }) afterEach(() => { + peerStore.delete(peerId) sinon.restore() }) @@ -176,6 +178,37 @@ describe('Dialing (direct, WebSockets)', () => { .and.to.have.property('code', ErrorCodes.ERR_TIMEOUT) }) + it('should sort addresses on dial', async () => { + const peerMultiaddrs = [ + multiaddr('/ip4/127.0.0.1/tcp/15001/ws'), + multiaddr('/ip4/20.0.0.1/tcp/15001/ws'), + multiaddr('/ip4/30.0.0.1/tcp/15001/ws') + ] + + sinon.spy(addressSort, 'publicAddressesFirst') + sinon.stub(localTM, 'dial').callsFake(createMockConnection) + + const dialer = new Dialer({ + transportManager: localTM, + addressSorter: addressSort.publicAddressesFirst, + concurrency: 3, + peerStore + }) + + // Inject data in the AddressBook + peerStore.addressBook.add(peerId, peerMultiaddrs) + + // Perform 3 multiaddr dials + await dialer.connectToPeer(peerId) + + expect(addressSort.publicAddressesFirst.callCount).to.eql(1) + + const sortedAddresses = addressSort.publicAddressesFirst(peerMultiaddrs.map((m) => ({ multiaddr: m }))) + expect(localTM.dial.getCall(0).args[0].equals(sortedAddresses[0].multiaddr)) + expect(localTM.dial.getCall(1).args[0].equals(sortedAddresses[1].multiaddr)) + expect(localTM.dial.getCall(2).args[0].equals(sortedAddresses[2].multiaddr)) + }) + it('should dial to the max concurrency', async () => { const dialer = new Dialer({ transportManager: localTM, diff --git a/test/peer-store/address-book.spec.js b/test/peer-store/address-book.spec.js index f6b8f28089..0adae21d61 100644 --- a/test/peer-store/address-book.spec.js +++ b/test/peer-store/address-book.spec.js @@ -6,6 +6,7 @@ const { expect } = require('aegir/utils/chai') const { Buffer } = require('buffer') const multiaddr = require('multiaddr') const arrayEquals = require('libp2p-utils/src/array-equals') +const addressSort = require('libp2p-utils/src/address-sort') const PeerId = require('peer-id') const pDefer = require('p-defer') @@ -19,7 +20,7 @@ const { } = require('../../src/errors') const addr1 = multiaddr('/ip4/127.0.0.1/tcp/8000') -const addr2 = multiaddr('/ip4/127.0.0.1/tcp/8001') +const addr2 = multiaddr('/ip4/20.0.0.1/tcp/8001') const addr3 = multiaddr('/ip4/127.0.0.1/tcp/8002') describe('addressBook', () => { @@ -340,6 +341,18 @@ describe('addressBook', () => { expect(m.getPeerId()).to.equal(peerId.toB58String()) }) }) + + it('can sort multiaddrs providing a sorter', () => { + const supportedMultiaddrs = [addr1, addr2] + ab.set(peerId, supportedMultiaddrs) + + const multiaddrs = ab.getMultiaddrsForPeer(peerId, addressSort.publicAddressesFirst) + const sortedAddresses = addressSort.publicAddressesFirst(supportedMultiaddrs.map((m) => ({ multiaddr: m }))) + + multiaddrs.forEach((m, index) => { + expect(m.equals(sortedAddresses[index].multiaddr)) + }) + }) }) describe('addressBook.delete', () => { From 4448de84328d5d51cc2df24311d5167f18db5773 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 20 Nov 2020 15:18:15 +0100 Subject: [PATCH 062/447] docs: auto relay example (#795) * chore: auto relay example * chore: update examples to use process arguments * chore: add test setup for node tests and test for auto-relay * chore: apply suggestions from code review * chore: do not use promise for multiaddrs event on example --- .travis.yml | 8 ++ examples/auto-relay/README.md | 192 ++++++++++++++++++++++++++++++++ examples/auto-relay/dialer.js | 29 +++++ examples/auto-relay/listener.js | 47 ++++++++ examples/auto-relay/relay.js | 40 +++++++ examples/auto-relay/test.js | 94 ++++++++++++++++ examples/package.json | 16 +++ examples/test-all.js | 33 ++++++ examples/test.js | 95 ++++++++++++++++ examples/utils.js | 61 ++++++++++ package.json | 1 + 11 files changed, 616 insertions(+) create mode 100644 examples/auto-relay/README.md create mode 100644 examples/auto-relay/dialer.js create mode 100644 examples/auto-relay/listener.js create mode 100644 examples/auto-relay/relay.js create mode 100644 examples/auto-relay/test.js create mode 100644 examples/package.json create mode 100644 examples/test-all.js create mode 100644 examples/test.js create mode 100644 examples/utils.js diff --git a/.travis.yml b/.travis.yml index bb21bd4028..47f1fcb07a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,5 +46,13 @@ jobs: - npm install - LIBP2P_JS=${TRAVIS_BUILD_DIR}/src/index.js npx aegir test -t node --bail + - stage: test + if: type = pull_request + name: example - auto-relay + script: + - cd examples + - npm install + - npm run test -- auto-relay + notifications: email: false \ No newline at end of file diff --git a/examples/auto-relay/README.md b/examples/auto-relay/README.md new file mode 100644 index 0000000000..fbb3b7a046 --- /dev/null +++ b/examples/auto-relay/README.md @@ -0,0 +1,192 @@ +# Auto relay + +Auto Relay enables libp2p nodes to dynamically find and bind to relays on the network. Once binding (listening) is done, the node can and should advertise its addresses on the network, allowing any other node to dial it over its bound relay(s). +While direct connections to nodes are preferable, it's not always possible to do so due to NATs or browser limitations. + +## 0. Setup the example + +Before moving into the examples, you should run `npm install` on the top level `js-libp2p` folder, in order to install all the dependencies needed for this example. Once the install finishes, you should move into the example folder with `cd examples/auto-relay`. + +This example comes with 3 main files. A `relay.js` file to be used in the first step, a `listener.js` file to be used in the second step and a `dialer.js` file to be used on the third step. All of these scripts will run their own libp2p node, which will interact with the previous ones. All nodes must be running in order for you to proceed. + +## 1. Set up a relay node + +In the first step of this example, we need to configure and run a relay node in order for our target node to bind to for accepting inbound connections. + +The relay node will need to have its relay subsystem enabled, as well as its HOP capability. It can be configured as follows: + +```js +const Libp2p = require('libp2p') +const Websockets = require('libp2p-websockets') +const { NOISE } = require('libp2p-noise') +const MPLEX = require('libp2p-mplex') + +const node = await Libp2p.create({ + modules: { + transport: [Websockets], + connEncryption: [NOISE], + streamMuxer: [MPLEX] + }, + addresses: { + listen: ['/ip4/0.0.0.0/tcp/0/ws'] + // TODO check "What is next?" section + // announce: ['/dns4/auto-relay.libp2p.io/tcp/443/wss/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3'] + }, + config: { + relay: { + enabled: true, + hop: { + enabled: true + }, + advertise: { + enabled: true, + } + } + } +}) + +await node.start() + +console.log(`Node started with id ${node.peerId.toB58String()}`) +console.log('Listening on:') +node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`)) +``` + +The Relay HOP advertise functionality is **NOT** required to be enabled. However, if you are interested in advertising on the network that this node is available to be used as a HOP Relay you can enable it. A content router module or Rendezvous needs to be configured to leverage this option. + +You should now run the following to start the relay node: + +```sh +node relay.js +``` + +This should print out something similar to the following: + +```sh +Node started with id QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3 +Listening on: +/ip4/127.0.0.1/tcp/61592/ws/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3 +/ip4/192.168.1.120/tcp/61592/ws/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3 +``` + +## 2. Set up a listener node with Auto Relay Enabled + +One of the typical use cases for Auto Relay is nodes behind a NAT or browser nodes due to their inability to expose a public address. For running a libp2p node that automatically binds itself to connected HOP relays, you can see the following: + +```js +const Libp2p = require('libp2p') +const Websockets = require('libp2p-websockets') +const { NOISE } = require('libp2p-noise') +const MPLEX = require('libp2p-mplex') + +const relayAddr = process.argv[2] +if (!relayAddr) { + throw new Error('the relay address needs to be specified as a parameter') +} + +const node = await Libp2p.create({ + modules: { + transport: [Websockets], + connEncryption: [NOISE], + streamMuxer: [MPLEX] + }, + config: { + relay: { + enabled: true, + autoRelay: { + enabled: true, + maxListeners: 2 + } + } + } +}) + +await node.start() +console.log(`Node started with id ${node.peerId.toB58String()}`) + +const conn = await node.dial(relayAddr) + +// Wait for connection and relay to be bind for the example purpose +await new Promise((resolve) => { + node.peerStore.on('change:multiaddrs', ({ peerId }) => { + // Updated self multiaddrs? + if (peerId.equals(node.peerId)) { + resolve() + } + }) +}) + +console.log(`Connected to the HOP relay ${conn.remotePeer.toString()}`) +console.log(`Advertising with a relay address of ${node.multiaddrs[0].toString()}/p2p/${node.peerId.toB58String()}`) +``` + +As you can see in the code, we need to provide the relay address, `relayAddr`, as a process argument. This node will dial the provided relay address and automatically bind to it. + +You should now run the following to start the node running Auto Relay: + +```sh +node listener.js /ip4/192.168.1.120/tcp/58941/ws/p2p/QmQKCBm87HQMbFqy14oqC85pMmnRrj6iD46ggM6reqNpsd +``` + +This should print out something similar to the following: + +```sh +Node started with id QmerrWofKF358JE6gv3z74cEAyL7z1KqhuUoVfGEynqjRm +Connected to the HOP relay QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3 +Advertising with a relay address of /ip4/192.168.1.120/tcp/61592/ws/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3/p2p-circuit/p2p/QmerrWofKF358JE6gv3z74cEAyL7z1KqhuUoVfGEynqjRm +``` + +Per the address, it is possible to verify that the auto relay node is listening on the circuit relay node address. + +Instead of dialing this relay manually, you could set up this node with the Bootstrap module and provide it in the bootstrap list. Moreover, you can use other `peer-discovery` modules to discover peers in the network and the node will automatically bind to the relays that support HOP until reaching the maximum number of listeners. + +## 3. Set up a dialer node for testing connectivity + +Now that you have a relay node and a node bound to that relay, you can test connecting to the auto relay node via the relay. + +```js +const Libp2p = require('libp2p') +const Websockets = require('libp2p-websockets') +const { NOISE } = require('libp2p-noise') +const MPLEX = require('libp2p-mplex') + +const autoRelayNodeAddr = process.argv[2] +if (!autoRelayNodeAddr) { + throw new Error('the auto relay node address needs to be specified') +} + +const node = await Libp2p.create({ + modules: { + transport: [Websockets], + connEncryption: [NOISE], + streamMuxer: [MPLEX] + } +}) + +await node.start() +console.log(`Node started with id ${node.peerId.toB58String()}`) + +const conn = await node.dial(autoRelayNodeAddr) +console.log(`Connected to the auto relay node via ${conn.remoteAddr.toString()}`) +``` + +You should now run the following to start the relay node using the listen address from step 2: + +```sh +node dialer.js /ip4/192.168.1.120/tcp/58941/ws/p2p/QmQKCBm87HQMbFqy14oqC85pMmnRrj6iD46ggM6reqNpsd +``` + +Once you start your test node, it should print out something similar to the following: + +```sh +Node started: Qme7iEzDxFoFhhkrsrkHkMnM11aPYjysaehP4NZeUfVMKG +Connected to the auto relay node via /ip4/192.168.1.120/tcp/61592/ws/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3/p2p-circuit/p2p/QmerrWofKF358JE6gv3z74cEAyL7z1KqhuUoVfGEynqjRm +``` + +As you can see from the output, the remote address of the established connection uses the relayed connection. + +## 4. What is next? + +Before moving into production, there are a few things that you should take into account. + +A relay node should not advertise its private address in a real world scenario, as the node would not be reachable by others. You should provide an array of public addresses in the libp2p `addresses.announce` option. If you are using websockets, bear in mind that due to browser’s security policies you cannot establish unencrypted connection from secure context. The simplest solution is to setup SSL with nginx and proxy to the node and setup a domain name for the certificate. diff --git a/examples/auto-relay/dialer.js b/examples/auto-relay/dialer.js new file mode 100644 index 0000000000..ebf5fd1249 --- /dev/null +++ b/examples/auto-relay/dialer.js @@ -0,0 +1,29 @@ +'use strict' + +const Libp2p = require('libp2p') +const Websockets = require('libp2p-websockets') +const { NOISE } = require('libp2p-noise') +const MPLEX = require('libp2p-mplex') + +async function main () { + const autoRelayNodeAddr = process.argv[2] + if (!autoRelayNodeAddr) { + throw new Error('the auto relay node address needs to be specified') + } + + const node = await Libp2p.create({ + modules: { + transport: [Websockets], + connEncryption: [NOISE], + streamMuxer: [MPLEX] + } + }) + + await node.start() + console.log(`Node started with id ${node.peerId.toB58String()}`) + + const conn = await node.dial(autoRelayNodeAddr) + console.log(`Connected to the auto relay node via ${conn.remoteAddr.toString()}`) +} + +main() diff --git a/examples/auto-relay/listener.js b/examples/auto-relay/listener.js new file mode 100644 index 0000000000..0f94374067 --- /dev/null +++ b/examples/auto-relay/listener.js @@ -0,0 +1,47 @@ +'use strict' + +const Libp2p = require('libp2p') +const Websockets = require('libp2p-websockets') +const { NOISE } = require('libp2p-noise') +const MPLEX = require('libp2p-mplex') + +async function main () { + const relayAddr = process.argv[2] + if (!relayAddr) { + throw new Error('the relay address needs to be specified as a parameter') + } + + const node = await Libp2p.create({ + modules: { + transport: [Websockets], + connEncryption: [NOISE], + streamMuxer: [MPLEX] + }, + config: { + relay: { + enabled: true, + autoRelay: { + enabled: true, + maxListeners: 2 + } + } + } + }) + + await node.start() + console.log(`Node started with id ${node.peerId.toB58String()}`) + + const conn = await node.dial(relayAddr) + + console.log(`Connected to the HOP relay ${conn.remotePeer.toString()}`) + + // Wait for connection and relay to be bind for the example purpose + node.peerStore.on('change:multiaddrs', ({ peerId }) => { + // Updated self multiaddrs? + if (peerId.equals(node.peerId)) { + console.log(`Advertising with a relay address of ${node.multiaddrs[0].toString()}/p2p/${node.peerId.toB58String()}`) + } + }) +} + +main() diff --git a/examples/auto-relay/relay.js b/examples/auto-relay/relay.js new file mode 100644 index 0000000000..2a18c3a769 --- /dev/null +++ b/examples/auto-relay/relay.js @@ -0,0 +1,40 @@ +'use strict' + +const Libp2p = require('libp2p') +const Websockets = require('libp2p-websockets') +const { NOISE } = require('libp2p-noise') +const MPLEX = require('libp2p-mplex') + +async function main () { + const node = await Libp2p.create({ + modules: { + transport: [Websockets], + connEncryption: [NOISE], + streamMuxer: [MPLEX] + }, + addresses: { + listen: ['/ip4/0.0.0.0/tcp/0/ws'] + // TODO check "What is next?" section + // announce: ['/dns4/auto-relay.libp2p.io/tcp/443/wss/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3'] + }, + config: { + relay: { + enabled: true, + hop: { + enabled: true + }, + advertise: { + enabled: true, + } + } + } + }) + + await node.start() + + console.log(`Node started with id ${node.peerId.toB58String()}`) + console.log('Listening on:') + node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`)) +} + +main() diff --git a/examples/auto-relay/test.js b/examples/auto-relay/test.js new file mode 100644 index 0000000000..0eaf8cc3ae --- /dev/null +++ b/examples/auto-relay/test.js @@ -0,0 +1,94 @@ +'use strict' + +const path = require('path') +const execa = require('execa') +const pDefer = require('p-defer') +const uint8ArrayToString = require('uint8arrays/to-string') + +function startProcess (name, args = []) { + return execa('node', [path.join(__dirname, name), ...args], { + cwd: path.resolve(__dirname), + all: true + }) +} + +async function test () { + let output1 = '' + let output2 = '' + let output3 = '' + let relayAddr + let autoRelayAddr + + const proc1Ready = pDefer() + const proc2Ready = pDefer() + + // Step 1 process + process.stdout.write('relay.js\n') + + const proc1 = startProcess('relay.js') + proc1.all.on('data', async (data) => { + process.stdout.write(data) + + output1 += uint8ArrayToString(data) + + if (output1.includes('Listening on:') && output1.includes('/p2p/')) { + relayAddr = output1.trim().split('Listening on:\n')[1].split('\n')[0] + proc1Ready.resolve() + } + }) + + await proc1Ready.promise + process.stdout.write('==================================================================\n') + + // Step 2 process + process.stdout.write('listener.js\n') + + const proc2 = startProcess('listener.js', [relayAddr]) + proc2.all.on('data', async (data) => { + process.stdout.write(data) + + output2 += uint8ArrayToString(data) + + if (output2.includes('Advertising with a relay address of') && output2.includes('/p2p/')) { + autoRelayAddr = output2.trim().split('Advertising with a relay address of ')[1] + proc2Ready.resolve() + } + }) + + await proc2Ready.promise + process.stdout.write('==================================================================\n') + + // Step 3 process + process.stdout.write('dialer.js\n') + + const proc3 = startProcess('dialer.js', [autoRelayAddr]) + proc3.all.on('data', async (data) => { + process.stdout.write(data) + + output3 += uint8ArrayToString(data) + + if (output3.includes('Connected to the auto relay node via')) { + const remoteAddr = output3.trim().split('Connected to the auto relay node via ')[1] + + if (remoteAddr === autoRelayAddr) { + proc3.kill() + proc2.kill() + proc1.kill() + } else { + throw new Error('dialer did not dial through the relay') + } + } + }) + + await Promise.all([ + proc1, + proc2, + proc3 + ]).catch((err) => { + if (err.signal !== 'SIGTERM') { + throw err + } + }) +} + +module.exports = test \ No newline at end of file diff --git a/examples/package.json b/examples/package.json new file mode 100644 index 0000000000..feaf656d1b --- /dev/null +++ b/examples/package.json @@ -0,0 +1,16 @@ +{ + "name": "libp2p-examples", + "version": "1.0.0", + "description": "Examples of how to use libp2p", + "scripts": { + "test": "node ./test.js", + "test:all": "node ./test-all.js" + }, + "license": "MIT", + "dependencies": { + "execa": "^2.1.0", + "fs-extra": "^8.1.0", + "p-defer": "^3.0.0", + "which": "^2.0.1" + } +} diff --git a/examples/test-all.js b/examples/test-all.js new file mode 100644 index 0000000000..3ee99e45fa --- /dev/null +++ b/examples/test-all.js @@ -0,0 +1,33 @@ +'use strict' + +process.on('unhandedRejection', (err) => { + console.error(err) + + process.exit(1) +}) + +const path = require('path') +const fs = require('fs') +const { + waitForOutput +} = require('./utils') + +async function testAll () { + for (const dir of fs.readdirSync(__dirname)) { + if (dir === 'node_modules' || dir === 'tests_output') { + continue + } + + const stats = fs.statSync(path.join(__dirname, dir)) + + if (!stats.isDirectory()) { + continue + } + + await waitForOutput('npm info ok', 'npm', ['test', '--', dir], { + cwd: __dirname + }) + } +} + +testAll() diff --git a/examples/test.js b/examples/test.js new file mode 100644 index 0000000000..3da6eccdb9 --- /dev/null +++ b/examples/test.js @@ -0,0 +1,95 @@ +'use strict' + +process.env.NODE_ENV = 'test' +process.env.CI = true // needed for some "clever" build tools + +const fs = require('fs-extra') +const path = require('path') +const execa = require('execa') +const dir = path.join(__dirname, process.argv[2]) + +testExample(dir) + .then(() => {}, (err) => { + if (err.exitCode) { + process.exit(err.exitCode) + } + + console.error(err) + process.exit(1) + }) + +async function testExample (dir) { + await installDeps(dir) + await build(dir) + await runTest(dir) + // TODO: add browser test setup +} + +async function installDeps (dir) { + if (!fs.existsSync(path.join(dir, 'package.json'))) { + console.info('Nothing to install in', dir) + return + } + + if (fs.existsSync(path.join(dir, 'node_modules'))) { + console.info('Dependencies already installed in', dir) + return + } + + const proc = execa.command('npm install', { + cwd: dir + }) + proc.all.on('data', (data) => { + process.stdout.write(data) + }) + + await proc +} + +async function build (dir) { + const pkgJson = path.join(dir, 'package.json') + + if (!fs.existsSync(pkgJson)) { + console.info('Nothing to build in', dir) + return + } + + const pkg = require(pkgJson) + let build + + if (pkg.scripts.bundle) { + build = 'bundle' + } + + if (pkg.scripts.build) { + build = 'build' + } + + if (!build) { + console.info('No "build" or "bundle" script in', pkgJson) + return + } + + const proc = execa('npm', ['run', build], { + cwd: dir + }) + proc.all.on('data', (data) => { + process.stdout.write(data) + }) + + await proc +} + +async function runTest (dir) { + console.info('Running node tests in', dir) + const testFile = path.join(dir, 'test.js') + + if (!fs.existsSync(testFile)) { + console.info('Nothing to test in', dir) + return + } + + const runTest = require(testFile) + + await runTest() +} \ No newline at end of file diff --git a/examples/utils.js b/examples/utils.js new file mode 100644 index 0000000000..aec6df5418 --- /dev/null +++ b/examples/utils.js @@ -0,0 +1,61 @@ +'use strict' + +const execa = require('execa') +const fs = require('fs-extra') +const which = require('which') + +async function isExecutable (command) { + try { + await fs.access(command, fs.constants.X_OK) + + return true + } catch (err) { + if (err.code === 'ENOENT') { + return isExecutable(await which(command)) + } + + if (err.code === 'EACCES') { + return false + } + + throw err + } +} + +async function waitForOutput (expectedOutput, command, args = [], opts = {}) { + if (!await isExecutable(command)) { + args.unshift(command) + command = 'node' + } + + const proc = execa(command, args, opts) + let output = '' + let time = 120000 + + let timeout = setTimeout(() => { + throw new Error(`Did not see "${expectedOutput}" in output from "${[command].concat(args).join(' ')}" after ${time/1000}s`) + }, time) + + proc.all.on('data', (data) => { + process.stdout.write(data) + + output += data.toString('utf8') + + if (output.includes(expectedOutput)) { + clearTimeout(timeout) + proc.kill() + } + }) + + try { + await proc + } catch (err) { + if (!err.killed) { + throw err + } + } +} + +module.exports = { + waitForOutput +} diff --git a/package.json b/package.json index 38bbd7ff07..9873226459 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "test": "npm run test:node && npm run test:browser", "test:node": "aegir test -t node -f \"./test/**/*.{node,spec}.js\"", "test:browser": "aegir test -t browser", + "test:examples": "cd examples && npm run test:all", "release": "aegir release -t node -t browser", "release-minor": "aegir release --type minor -t node -t browser", "release-major": "aegir release --type major -t node -t browser", From 4ebcdb085cc798d3d67c9bb20056e1f6ee788898 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 24 Nov 2020 14:39:22 +0100 Subject: [PATCH 063/447] chore: update websockets (#806) * chore: update websockets --- package.json | 2 +- test/dialing/direct.spec.js | 53 ++++++++++++++++++++++- test/dialing/resolver.spec.js | 3 +- test/transports/transport-manager.spec.js | 9 ++-- test/utils/base-options.browser.js | 8 ++++ 5 files changed, 68 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 9873226459..fedbb1687f 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "libp2p-secio": "^0.13.1", "libp2p-tcp": "^0.15.1", "libp2p-webrtc-star": "^0.20.0", - "libp2p-websockets": "^0.14.0", + "libp2p-websockets": "^0.15.0", "multihashes": "^3.0.1", "nock": "^13.0.3", "p-defer": "^3.0.0", diff --git a/test/dialing/direct.spec.js b/test/dialing/direct.spec.js index c67f3ab8f2..5ccd008ef7 100644 --- a/test/dialing/direct.spec.js +++ b/test/dialing/direct.spec.js @@ -7,6 +7,7 @@ const pDefer = require('p-defer') const pWaitFor = require('p-wait-for') const delay = require('delay') const Transport = require('libp2p-websockets') +const filters = require('libp2p-websockets/src/filters') const Muxer = require('libp2p-mplex') const { NOISE: Crypto } = require('libp2p-noise') const multiaddr = require('multiaddr') @@ -41,7 +42,7 @@ describe('Dialing (direct, WebSockets)', () => { upgrader: mockUpgrader, onConnection: () => {} }) - localTM.add(Transport.prototype[Symbol.toStringTag], Transport) + localTM.add(Transport.prototype[Symbol.toStringTag], Transport, { filter: filters.all }) }) afterEach(() => { @@ -292,6 +293,7 @@ describe('Dialing (direct, WebSockets)', () => { }) describe('libp2p.dialer', () => { + const transportKey = Transport.prototype[Symbol.toStringTag] let libp2p afterEach(async () => { @@ -307,6 +309,13 @@ describe('Dialing (direct, WebSockets)', () => { transport: [Transport], streamMuxer: [Muxer], connEncryption: [Crypto] + }, + config: { + transport: { + [transportKey]: { + filter: filters.all + } + } } }) @@ -330,6 +339,13 @@ describe('Dialing (direct, WebSockets)', () => { maxParallelDials: 10, maxDialsPerPeer: 1, dialTimeout: 1e3 // 30 second dial timeout per peer + }, + config: { + transport: { + [transportKey]: { + filter: filters.all + } + } } } libp2p = await Libp2p.create(config) @@ -347,6 +363,13 @@ describe('Dialing (direct, WebSockets)', () => { transport: [Transport], streamMuxer: [Muxer], connEncryption: [Crypto] + }, + config: { + transport: { + [transportKey]: { + filter: filters.all + } + } } }) @@ -370,6 +393,13 @@ describe('Dialing (direct, WebSockets)', () => { transport: [Transport], streamMuxer: [Muxer], connEncryption: [Crypto] + }, + config: { + transport: { + [transportKey]: { + filter: filters.all + } + } } }) @@ -397,6 +427,13 @@ describe('Dialing (direct, WebSockets)', () => { transport: [Transport], streamMuxer: [Muxer], connEncryption: [Crypto] + }, + config: { + transport: { + [transportKey]: { + filter: filters.all + } + } } }) @@ -414,6 +451,13 @@ describe('Dialing (direct, WebSockets)', () => { transport: [Transport], streamMuxer: [Muxer], connEncryption: [Crypto] + }, + config: { + transport: { + [transportKey]: { + filter: filters.all + } + } } }) @@ -427,6 +471,13 @@ describe('Dialing (direct, WebSockets)', () => { transport: [Transport], streamMuxer: [Muxer], connEncryption: [Crypto] + }, + config: { + transport: { + [transportKey]: { + filter: filters.all + } + } } }) diff --git a/test/dialing/resolver.spec.js b/test/dialing/resolver.spec.js index ba81a5c8b2..094f657a6d 100644 --- a/test/dialing/resolver.spec.js +++ b/test/dialing/resolver.spec.js @@ -37,11 +37,12 @@ describe('Dialing (resolvable addresses)', () => { [libp2p, remoteLibp2p] = await peerUtils.createPeer({ number: 2, config: { - modules: baseOptions.modules, + ...baseOptions, addresses: { listen: [multiaddr(`${relayAddr}/p2p-circuit`)] }, config: { + ...baseOptions.config, peerDiscovery: { autoDial: false } diff --git a/test/transports/transport-manager.spec.js b/test/transports/transport-manager.spec.js index cbdef79b0a..e1b13982b2 100644 --- a/test/transports/transport-manager.spec.js +++ b/test/transports/transport-manager.spec.js @@ -6,6 +6,7 @@ const sinon = require('sinon') const multiaddr = require('multiaddr') const Transport = require('libp2p-websockets') +const filters = require('libp2p-websockets/src/filters') const { NOISE: Crypto } = require('libp2p-noise') const AddressManager = require('../../src/address-manager') const TransportManager = require('../../src/transport-manager') @@ -39,7 +40,7 @@ describe('Transport Manager (WebSockets)', () => { }) it('should be able to add and remove a transport', async () => { - tm.add(Transport.prototype[Symbol.toStringTag], Transport) + tm.add(Transport.prototype[Symbol.toStringTag], Transport, { filter: filters.all }) expect(tm._transports.size).to.equal(1) await tm.remove(Transport.prototype[Symbol.toStringTag]) }) @@ -66,7 +67,7 @@ describe('Transport Manager (WebSockets)', () => { }) it('should be able to dial', async () => { - tm.add(Transport.prototype[Symbol.toStringTag], Transport) + tm.add(Transport.prototype[Symbol.toStringTag], Transport, { filter: filters.all }) const addr = MULTIADDRS_WEBSOCKETS[0] const connection = await tm.dial(addr) expect(connection).to.exist() @@ -74,7 +75,7 @@ describe('Transport Manager (WebSockets)', () => { }) it('should fail to dial an unsupported address', async () => { - tm.add(Transport.prototype[Symbol.toStringTag], Transport) + tm.add(Transport.prototype[Symbol.toStringTag], Transport, { filter: filters.all }) const addr = multiaddr('/ip4/127.0.0.1/tcp/0') await expect(tm.dial(addr)) .to.eventually.be.rejected() @@ -82,7 +83,7 @@ describe('Transport Manager (WebSockets)', () => { }) it('should fail to listen with no valid address', async () => { - tm.add(Transport.prototype[Symbol.toStringTag], Transport) + tm.add(Transport.prototype[Symbol.toStringTag], Transport, { filter: filters.all }) await expect(tm.listen([listenAddr])) .to.eventually.be.rejected() diff --git a/test/utils/base-options.browser.js b/test/utils/base-options.browser.js index c9c570ed8b..d033a4c945 100644 --- a/test/utils/base-options.browser.js +++ b/test/utils/base-options.browser.js @@ -1,9 +1,12 @@ 'use strict' const Transport = require('libp2p-websockets') +const filters = require('libp2p-websockets/src/filters') const Muxer = require('libp2p-mplex') const { NOISE: Crypto } = require('libp2p-noise') +const transportKey = Transport.prototype[Symbol.toStringTag] + module.exports = { modules: { transport: [Transport], @@ -16,6 +19,11 @@ module.exports = { hop: { enabled: false } + }, + transport: { + [transportKey]: { + filter: filters.all + } } } } From baedf3fe5ab946e938db1415d1662452cdfc0cc1 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 25 Nov 2020 18:50:23 +0100 Subject: [PATCH 064/447] feat: discover and connect to closest peers (#798) --- doc/API.md | 32 ++++ doc/CONFIGURATION.md | 9 +- package.json | 2 + src/config.js | 7 + src/index.js | 8 +- src/peer-routing.js | 132 +++++++++++--- test/peer-routing/peer-routing.node.js | 238 +++++++++++++++++++++++++ 7 files changed, 401 insertions(+), 27 deletions(-) diff --git a/doc/API.md b/doc/API.md index b199d55687..222ef660eb 100644 --- a/doc/API.md +++ b/doc/API.md @@ -20,6 +20,7 @@ * [`contentRouting.get`](#contentroutingget) * [`contentRouting.getMany`](#contentroutinggetmany) * [`peerRouting.findPeer`](#peerroutingfindpeer) + * [`peerRouting.getClosestPeers`](#peerroutinggetclosestpeers) * [`peerStore.addressBook.add`](#peerstoreaddressbookadd) * [`peerStore.addressBook.delete`](#peerstoreaddressbookdelete) * [`peerStore.addressBook.get`](#peerstoreaddressbookget) @@ -100,6 +101,7 @@ Creates an instance of Libp2p. | [options.keychain] | [`object`](./CONFIGURATION.md#setup-with-keychain) | keychain [configuration](./CONFIGURATION.md#setup-with-keychain) | | [options.metrics] | [`object`](./CONFIGURATION.md#configuring-metrics) | libp2p Metrics [configuration](./CONFIGURATION.md#configuring-metrics) | | [options.peerId] | [`PeerId`][peer-id] | peerId instance (it will be created if not provided) | +| [options.peerRouting] | [`object`](./CONFIGURATION.md#setup-with-content-and-peer-routing) | libp2p Peer routing service [configuration](./CONFIGURATION.md#setup-with-content-and-peer-routing) | | [options.peerStore] | [`object`](./CONFIGURATION.md#configuring-peerstore) | libp2p PeerStore [configuration](./CONFIGURATION.md#configuring-peerstore) | For Libp2p configurations and modules details read the [Configuration Document](./CONFIGURATION.md). @@ -707,6 +709,36 @@ Iterates over all peer routers in series to find the given peer. If the DHT is e const peer = await libp2p.peerRouting.findPeer(peerId, options) ``` +### peerRouting.getClosestPeers + +Iterates over all content routers in series to get the closest peers of the given key. +Once a content router succeeds, the iteration will stop. If the DHT is enabled, it will be queried first. + +`libp2p.peerRouting.getClosestPeers(cid, options)` + +#### Parameters + +| Name | Type | Description | +|------|------|-------------| +| key | `Uint8Array` | A CID like key | +| options | `object` | operation options | +| options.timeout | `number` | How long the query can take (ms). | + +#### Returns + +| Type | Description | +|------|-------------| +| `AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }` | Async iterator for peer data | + +#### Example + +```js +// Iterate over the closest peers found for the given key +for await (const peer of libp2p.peerRouting.getClosestPeers(key)) { + console.log(peer.id, peer.multiaddrs) +} +``` + ### peerStore.addressBook.add Adds known `multiaddrs` of a given peer. If the peer is not known, it will be set with the provided multiaddrs. diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 917ec7cfb7..06cc8faed3 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -397,7 +397,14 @@ const node = await Libp2p.create({ new DelegatedPeerRouter() ], }, - peerId + peerId, + peerRouting: { // Peer routing configuration + refreshManager: { // Refresh known and connected closest peers + enabled: true, // Should find the closest peers. + interval: 6e5, // Interval for getting the new for closest peers of 10min + bootDelay: 10e3 // Delay for the initial query for closest peers + } + } }) ``` diff --git a/package.json b/package.json index fedbb1687f..e6fee11aad 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "protons": "^2.0.0", "retimer": "^2.0.0", "sanitize-filename": "^1.6.3", + "set-delayed-interval": "^1.0.0", "streaming-iterables": "^5.0.2", "timeout-abort-controller": "^1.1.1", "varint": "^5.0.0", @@ -92,6 +93,7 @@ "chai-string": "^1.5.0", "delay": "^4.3.0", "interop-libp2p": "^0.3.0", + "into-stream": "^6.0.0", "ipfs-http-client": "^47.0.1", "it-concat": "^1.0.0", "it-pair": "^1.0.0", diff --git a/src/config.js b/src/config.js index 1134050c14..a2334f52e7 100644 --- a/src/config.js +++ b/src/config.js @@ -41,6 +41,13 @@ const DefaultConfig = { persistence: false, threshold: 5 }, + peerRouting: { + refreshManager: { + enabled: true, + interval: 6e5, + bootDelay: 10e3 + } + }, config: { dht: { enabled: false, diff --git a/src/index.js b/src/index.js index 23ecae3d19..d2e2a7cb67 100644 --- a/src/index.js +++ b/src/index.js @@ -9,7 +9,7 @@ log.error = debug('libp2p:error') const errCode = require('err-code') const PeerId = require('peer-id') -const peerRouting = require('./peer-routing') +const PeerRouting = require('./peer-routing') const contentRouting = require('./content-routing') const getPeer = require('./get-peer') const { validate: validateConfig } = require('./config') @@ -193,7 +193,7 @@ class Libp2p extends EventEmitter { // Attach remaining APIs // peer and content routing will automatically get modules from _modules and _dht - this.peerRouting = peerRouting(this) + this.peerRouting = new PeerRouting(this) this.contentRouting = contentRouting(this) // Mount default protocols @@ -250,8 +250,8 @@ class Libp2p extends EventEmitter { try { this._isStarted = false - // Relay this.relay && this.relay.stop() + this.peerRouting.stop() for (const service of this._discovery.values()) { service.removeListener('peer', this._onDiscoveryPeer) @@ -501,6 +501,8 @@ class Libp2p extends EventEmitter { // Relay this.relay && this.relay.start() + + this.peerRouting.start() } /** diff --git a/src/peer-routing.js b/src/peer-routing.js index 7594f15d77..e783c82f8b 100644 --- a/src/peer-routing.js +++ b/src/peer-routing.js @@ -1,40 +1,126 @@ 'use strict' const errCode = require('err-code') +const debug = require('debug') +const log = debug('libp2p:peer-routing') +log.error = debug('libp2p:peer-routing:error') + +const all = require('it-all') const pAny = require('p-any') +const { + setDelayedInterval, + clearDelayedInterval +} = require('set-delayed-interval') + +/** + * Responsible for managing the usage of the available Peer Routing modules. + */ +class PeerRouting { + /** + * @class + * @param {Libp2p} libp2p + */ + constructor (libp2p) { + this._peerId = libp2p.peerId + this._peerStore = libp2p.peerStore + this._routers = libp2p._modules.peerRouting || [] + + // If we have the dht, make it first + if (libp2p._dht) { + this._routers.unshift(libp2p._dht) + } + + this._refreshManagerOptions = libp2p._options.peerRouting.refreshManager + + this._findClosestPeersTask = this._findClosestPeersTask.bind(this) + } + + /** + * Start peer routing service. + */ + start () { + if (!this._routers.length || this._timeoutId || !this._refreshManagerOptions.enabled) { + return + } + + this._timeoutId = setDelayedInterval( + this._findClosestPeersTask, this._refreshManagerOptions.interval, this._refreshManagerOptions.bootDelay + ) + } -module.exports = (node) => { - const routers = node._modules.peerRouting || [] + /** + * Recurrent task to find closest peers and add their addresses to the Address Book. + */ + async _findClosestPeersTask () { + try { + for await (const { id, multiaddrs } of this.getClosestPeers(this._peerId.id)) { + this._peerStore.addressBook.add(id, multiaddrs) + } + } catch (err) { + log.error(err) + } + } - // If we have the dht, make it first - if (node._dht) { - routers.unshift(node._dht) + /** + * Stop peer routing service. + */ + stop () { + clearDelayedInterval(this._timeoutId) } - return { - /** - * Iterates over all peer routers in series to find the given peer. - * - * @param {string} id - The id of the peer to find - * @param {object} [options] - * @param {number} [options.timeout] - How long the query should run - * @returns {Promise<{ id: PeerId, multiaddrs: Multiaddr[] }>} - */ - findPeer: async (id, options) => { // eslint-disable-line require-await - if (!routers.length) { - throw errCode(new Error('No peer routers available'), 'NO_ROUTERS_AVAILABLE') + /** + * Iterates over all peer routers in series to find the given peer. + * + * @param {string} id - The id of the peer to find + * @param {object} [options] + * @param {number} [options.timeout] - How long the query should run + * @returns {Promise<{ id: PeerId, multiaddrs: Multiaddr[] }>} + */ + async findPeer (id, options) { // eslint-disable-line require-await + if (!this._routers.length) { + throw errCode(new Error('No peer routers available'), 'NO_ROUTERS_AVAILABLE') + } + + return pAny(this._routers.map(async (router) => { + const result = await router.findPeer(id, options) + + // If we don't have a result, we need to provide an error to keep trying + if (!result || Object.keys(result).length === 0) { + throw errCode(new Error('not found'), 'NOT_FOUND') } - return pAny(routers.map(async (router) => { - const result = await router.findPeer(id, options) + return result + })) + } + + /** + * Attempt to find the closest peers on the network to the given key. + * + * @param {Uint8Array} key - A CID like key + * @param {Object} [options] + * @param {number} [options.timeout=30e3] - How long the query can take. + * @returns {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} + */ + async * getClosestPeers (key, options = { timeout: 30e3 }) { + if (!this._routers.length) { + throw errCode(new Error('No peer routers available'), 'NO_ROUTERS_AVAILABLE') + } - // If we don't have a result, we need to provide an error to keep trying - if (!result || Object.keys(result).length === 0) { + const result = await pAny( + this._routers.map(async (router) => { + const peers = await all(router.getClosestPeers(key, options)) + + if (!peers || !peers.length) { throw errCode(new Error('not found'), 'NOT_FOUND') } + return peers + }) + ) - return result - })) + for (const peer of result) { + yield peer } } } + +module.exports = PeerRouting diff --git a/test/peer-routing/peer-routing.node.js b/test/peer-routing/peer-routing.node.js index cdce080c1e..74cc6393e6 100644 --- a/test/peer-routing/peer-routing.node.js +++ b/test/peer-routing/peer-routing.node.js @@ -4,12 +4,17 @@ const { expect } = require('aegir/utils/chai') const nock = require('nock') const sinon = require('sinon') +const intoStream = require('into-stream') +const delay = require('delay') const pDefer = require('p-defer') +const pWaitFor = require('p-wait-for') const mergeOptions = require('merge-options') const ipfsHttpClient = require('ipfs-http-client') const DelegatedPeerRouter = require('libp2p-delegated-peer-routing') +const multiaddr = require('multiaddr') +const PeerId = require('peer-id') const peerUtils = require('../utils/creators/peer') const { baseOptions, routingOptions } = require('./utils') @@ -29,6 +34,16 @@ describe('peer-routing', () => { .to.eventually.be.rejected() .and.to.have.property('code', 'NO_ROUTERS_AVAILABLE') }) + + it('.getClosestPeers should return an error', async () => { + try { + for await (const _ of node.peerRouting.getClosestPeers('a cid')) { } // eslint-disable-line + throw new Error('.getClosestPeers should return an error') + } catch (err) { + expect(err).to.exist() + expect(err.code).to.equal('NO_ROUTERS_AVAILABLE') + } + }) }) describe('via dht router', () => { @@ -64,6 +79,19 @@ describe('peer-routing', () => { nodes[0].peerRouting.findPeer() return deferred.promise }) + + it('should use the nodes dht to get the closest peers', async () => { + const deferred = pDefer() + + sinon.stub(nodes[0]._dht, 'getClosestPeers').callsFake(function * () { + deferred.resolve() + yield + }) + + await nodes[0].peerRouting.getClosestPeers().next() + + return deferred.promise + }) }) describe('via delegate router', () => { @@ -110,6 +138,19 @@ describe('peer-routing', () => { return deferred.promise }) + it('should use the delegate router to get the closest peers', async () => { + const deferred = pDefer() + + sinon.stub(delegate, 'getClosestPeers').callsFake(function * () { + deferred.resolve() + yield + }) + + await node.peerRouting.getClosestPeers().next() + + return deferred.promise + }) + it('should be able to find a peer', async () => { const peerKey = 'QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL' const mockApi = nock('http://0.0.0.0:60197') @@ -154,6 +195,60 @@ describe('peer-routing', () => { expect(mockApi.isDone()).to.equal(true) }) + + it('should be able to get the closest peers', async () => { + const peerId = await PeerId.create({ keyType: 'ed25519' }) + + const closest1 = '12D3KooWLewYMMdGWAtuX852n4rgCWkK7EBn4CWbwwBzhsVoKxk3' + const closest2 = '12D3KooWDtoQbpKhtnWddfj72QmpFvvLDTsBLTFkjvgQm6cde2AK' + + const mockApi = nock('http://0.0.0.0:60197') + .post('/api/v0/dht/query') + .query(true) + .reply(200, + () => intoStream([ + `{"extra":"","id":"${closest1}","responses":[{"ID":"${closest1}","Addrs":["/ip4/127.0.0.1/tcp/63930","/ip4/127.0.0.1/tcp/63930"]}],"type":1}\n`, + `{"extra":"","id":"${closest2}","responses":[{"ID":"${closest2}","Addrs":["/ip4/127.0.0.1/tcp/63506","/ip4/127.0.0.1/tcp/63506"]}],"type":1}\n`, + `{"Extra":"","ID":"${closest2}","Responses":[],"Type":2}\n`, + `{"Extra":"","ID":"${closest1}","Responses":[],"Type":2}\n` + ]), + [ + 'Content-Type', 'application/json', + 'X-Chunked-Output', '1' + ]) + + const closestPeers = [] + for await (const peer of node.peerRouting.getClosestPeers(peerId.id, { timeout: 1000 })) { + closestPeers.push(peer) + } + + expect(closestPeers).to.have.length(2) + expect(closestPeers[0].id.toB58String()).to.equal(closest2) + expect(closestPeers[0].multiaddrs).to.have.lengthOf(2) + expect(closestPeers[1].id.toB58String()).to.equal(closest1) + expect(closestPeers[1].multiaddrs).to.have.lengthOf(2) + expect(mockApi.isDone()).to.equal(true) + }) + + it('should handle errors when getting the closest peers', async () => { + const peerId = await PeerId.create({ keyType: 'ed25519' }) + + const mockApi = nock('http://0.0.0.0:60197') + .post('/api/v0/dht/query') + .query(true) + .reply(502, 'Bad Gateway', [ + 'X-Chunked-Output', '1' + ]) + + try { + for await (const _ of node.peerRouting.getClosestPeers(peerId.id)) { } // eslint-disable-line + throw new Error('should handle errors when getting the closest peers') + } catch (err) { + expect(err).to.exist() + } + + expect(mockApi.isDone()).to.equal(true) + }) }) describe('via dht and delegate routers', () => { @@ -208,5 +303,148 @@ describe('peer-routing', () => { const peer = await node.peerRouting.findPeer('a peer id') expect(peer).to.eql(results) }) + + it('should only use the dht if it gets the closest peers', async () => { + const results = [true] + + sinon.stub(node._dht, 'getClosestPeers').callsFake(function * () { + yield results[0] + }) + + sinon.stub(delegate, 'getClosestPeers').callsFake(function * () { // eslint-disable-line require-yield + throw new Error('the delegate should not have been called') + }) + + const closest = [] + for await (const peer of node.peerRouting.getClosestPeers('a cid')) { + closest.push(peer) + } + + expect(closest).to.have.length.above(0) + expect(closest).to.eql(results) + }) + + it('should use the delegate if the dht fails to get the closest peer', async () => { + const results = [true] + + sinon.stub(node._dht, 'getClosestPeers').callsFake(function * () { }) + + sinon.stub(delegate, 'getClosestPeers').callsFake(function * () { + yield results[0] + }) + + const closest = [] + for await (const peer of node.peerRouting.getClosestPeers('a cid')) { + closest.push(peer) + } + + expect(closest).to.have.length.above(0) + expect(closest).to.eql(results) + }) + }) + + describe('peer routing refresh manager service', () => { + let node + let peerIds + + before(async () => { + peerIds = await peerUtils.createPeerId({ number: 2 }) + }) + + afterEach(() => { + sinon.restore() + + return node && node.stop() + }) + + it('should be enabled and start by default', async () => { + const results = [ + { id: peerIds[0], multiaddrs: [multiaddr('/ip4/30.0.0.1/tcp/2000')] }, + { id: peerIds[1], multiaddrs: [multiaddr('/ip4/32.0.0.1/tcp/2000')] } + ] + + ;[node] = await peerUtils.createPeer({ + config: mergeOptions(routingOptions, { + peerRouting: { + refreshManager: { + bootDelay: 100 + } + } + }), + started: false + }) + + sinon.spy(node.peerStore.addressBook, 'add') + sinon.stub(node._dht, 'getClosestPeers').callsFake(function * () { + yield results[0] + yield results[1] + }) + + await node.start() + + await pWaitFor(() => node._dht.getClosestPeers.callCount === 1) + await pWaitFor(() => node.peerStore.addressBook.add.callCount === results.length) + + const call0 = node.peerStore.addressBook.add.getCall(0) + expect(call0.args[0].equals(results[0].id)) + call0.args[1].forEach((m, index) => { + expect(m.equals(results[0].multiaddrs[index])) + }) + + const call1 = node.peerStore.addressBook.add.getCall(1) + expect(call1.args[0].equals(results[1].id)) + call0.args[1].forEach((m, index) => { + expect(m.equals(results[1].multiaddrs[index])) + }) + }) + + it('should support being disabled', async () => { + [node] = await peerUtils.createPeer({ + config: mergeOptions(routingOptions, { + peerRouting: { + refreshManager: { + bootDelay: 100, + enabled: false + } + } + }), + started: false + }) + + sinon.stub(node._dht, 'getClosestPeers').callsFake(function * () { + yield + throw new Error('should not be called') + }) + + await node.start() + await delay(100) + + expect(node._dht.getClosestPeers.callCount === 0) + }) + + it('should start and run recurrently on interval', async () => { + [node] = await peerUtils.createPeer({ + config: mergeOptions(routingOptions, { + peerRouting: { + refreshManager: { + interval: 500, + bootDelay: 200 + } + } + }), + started: false + }) + + sinon.stub(node._dht, 'getClosestPeers').callsFake(function * () { + yield { id: peerIds[0], multiaddrs: [multiaddr('/ip4/30.0.0.1/tcp/2000')] } + }) + + await node.start() + + await delay(300) + expect(node._dht.getClosestPeers.callCount).to.eql(1) + await delay(500) + expect(node._dht.getClosestPeers.callCount).to.eql(2) + }) }) }) From b538ebdc0a68481b56feb41f2a4f655472104773 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 26 Nov 2020 12:01:53 +0100 Subject: [PATCH 065/447] chore: use set-delayed-interval module on circuit (#809) --- src/circuit/index.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/circuit/index.js b/src/circuit/index.js index d12d882432..9e556f7f18 100644 --- a/src/circuit/index.js +++ b/src/circuit/index.js @@ -4,6 +4,11 @@ const debug = require('debug') const log = debug('libp2p:relay') log.error = debug('libp2p:relay:error') +const { + setDelayedInterval, + clearDelayedInterval +} = require('set-delayed-interval') + const AutoRelay = require('./auto-relay') const { namespaceToCid } = require('./utils') const { @@ -33,6 +38,8 @@ class Relay { // Create autoRelay if enabled this._autoRelay = this._options.autoRelay.enabled && new AutoRelay({ libp2p, ...this._options.autoRelay }) + + this._advertiseService = this._advertiseService.bind(this) } /** @@ -45,9 +52,9 @@ class Relay { const canHop = this._options.hop.enabled if (canHop && this._options.advertise.enabled) { - this._timeout = setTimeout(() => { - this._advertiseService() - }, this._options.advertise.bootDelay) + this._timeout = setDelayedInterval( + this._advertiseService, this._options.advertise.ttl, this._options.advertise.bootDelay + ) } } @@ -57,7 +64,7 @@ class Relay { * @returns {void} */ stop () { - clearTimeout(this._timeout) + clearDelayedInterval(this._timeout) } /** @@ -77,14 +84,7 @@ class Relay { } else { log.error(err) } - - return } - - // Restart timeout - this._timeout = setTimeout(() => { - this._advertiseService() - }, this._options.advertise.ttl) } } From 7d76ba13671babadd123f97f51859a62da7a76fe Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 26 Nov 2020 14:29:13 +0100 Subject: [PATCH 066/447] docs: migration 0.29 to 0.30 (#808) --- doc/migrations/v0.29-v0.30.md | 185 ++++++++++++++++++++++++++++++++++ package.json | 4 +- 2 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 doc/migrations/v0.29-v0.30.md diff --git a/doc/migrations/v0.29-v0.30.md b/doc/migrations/v0.29-v0.30.md new file mode 100644 index 0000000000..ffec86f926 --- /dev/null +++ b/doc/migrations/v0.29-v0.30.md @@ -0,0 +1,185 @@ + +# Migrating to libp2p@30 + +A migration guide for refactoring your application code from libp2p v0.29.x to v0.30.0. + +## Table of Contents + +- [API](#api) +- [Development and Testing](#development-and-testing) +- [Module Updates](#module-updates) + +## API + +### Pubsub + +`js-libp2p` nodes prior to this version were emitting to self on publish by default. +This default value was changed on the pubsub router layer in the past, but we kept it overwritten in libp2p to avoid an upstream breaking change. +Now `js-libp2p` does not overwrite the pubsub router options anymore. Upstream projects that want this feature should enable it on their libp2p configuration. + +**Before** + +```js +const Gossipsub = require('libp2p-gossipsub') +const Libp2p = require('libp2p') + +const libp2p = await Libp2p.create({ + modules: { + // ... Add required modules according to the Configuration docs + pubsub: Gossipsub + } +}) +``` + +**After** + +```js +const Gossipsub = require('libp2p-gossipsub') +const Libp2p = require('libp2p') + +const libp2p = await Libp2p.create({ + modules: { + // ... Add required modules according to the Configuration docs + pubsub: Gossipsub + }, + config: { + pubsub: { + emitSelf: true + } + } +}) +``` + +The [Pubsub interface](https://github.com/libp2p/js-libp2p-interfaces/tree/master/src/pubsub) was updated on its message signing properties, taking into account the Gossipsub spec updates on [libp2p/specs#294](https://github.com/libp2p/specs/pull/294) and [libp2p/specs#299](https://github.com/libp2p/specs/pull/299) + +The signing property is now based on a `globalSignaturePolicy` option instead of the previous `signMessages` and `strictSigning` options. The default to strict signing pubsub messages was kept, but if you would like to disable it, the properties should be changed as follows: + +**Before** + +```js +const Gossipsub = require('libp2p-gossipsub') +const Libp2p = require('libp2p') + +const libp2p = await Libp2p.create({ + modules: { + // ... Add required modules according to the Configuration docs + pubsub: Gossipsub + }, + config: { + pubsub: { + signMessages: false, + strictSigning: false + } + } +}) +``` + +**After** + +```js +const Gossipsub = require('libp2p-gossipsub') +const { SignaturePolicy } = require('libp2p-interfaces/src/pubsub/signature-policy') +const Libp2p = require('libp2p') + +const libp2p = await Libp2p.create({ + modules: { + // ... Add required modules according to the Configuration docs + pubsub: Gossipsub + }, + config: { + pubsub: { + globalSignaturePolicy: SignaturePolicy.StrictNoSign + } + } +}) +``` + +### Addresses + +Libp2p has supported `noAnnounce` addresses configuration for some time now. However, it did not provide the best developer experience. In this release, we dropped the `noAnnounce` configuration property in favor of an `announceFilter` property function. + +**Before** + +```js +const Libp2p = require('libp2p') + +const libp2p = await Libp2p.create({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/8000/ws'], + noAnnounce: ['/ip4/127.0.0.1/tcp/8000/ws'], + }, + // ... additional configuration per the Configuration docs +}) +``` + +**After** + +```js +const Libp2p = require('libp2p') + +// Libp2p utils has several multiaddr utils you can leverage +const isPrivate = require('libp2p-utils/src/multiaddr/is-private') + +const libp2p = await Libp2p.create({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/8000/ws'], + // Filter function: (ma: Array) => Array + announceFilter: (multiaddrs) => multiaddrs.filter(m => !isPrivate(m)) + }, + // ... additional configuration per the Configuration docs +}) +``` + +It is important pointing out another change regarding address advertising. This is not an API breaking change, but it might have influence on your libp2p setup. +Previously, when using the addresses `announce` property, its multiaddrs were concatenated with the `listen` multiaddrs and then they were filtered out by the `noAnnounce` multiaddrs, in order to create the list of multiaddrs to advertise. +In `libp2p@0.30` the logic now operates as follows: + +- If `announce` addresses are provided, only they will be announced (no filters are applied) +- If `announce` is not provided, the transport addresses will be filtered (if a filter is provided) + - if the `announceFilter` is provide it will be passed the transport addresses + +## Development and Testing + +While this is not an API breaking change, there was a behavioral breaking change on the Websockets transport when in a browser environment. This change might create issues on local test setups. +`libp2p-websockets` has allowed `TCP` and `DNS` addresses, both with `ws` or `wss` to be used for dial purposes. Taking into account security (and browser policies), we are now restricting addresses to `DNS` + `wss` in the browser +With this new behavior, if you need to use non DNS addresses, you can configure your libp2p node as follows: + +```js +const Websockets = require('libp2p-websockets') +const filters = require('libp2p-websockets/src/filters') +const Libp2p = require('libp2p') + +const transportKey = Websockets.prototype[Symbol.toStringTag] +const libp2p = await Libp2p.create({ + modules: { + transport: [Websockets] + // ... Add required modules according to the Configuration docs + }, + config: { + transport: { + [transportKey]: { + filter: filters.all + } + } + } +}) +``` + +## Module Updates + +With this release you should update the following libp2p modules if you are relying on them: + + + +```json +"libp2p-delegated-content-routing": "^0.8.0", +"libp2p-delegated-peer-routing": "^0.8.0", +"libp2p-floodsub": "^0.24.0", +"libp2p-gossipsub": "^0.7.0", +"libp2p-websockets": "^0.15.0", +``` + +Note that some of them do not need to be updated for this libp2p version to work as expected, but we suggest you to keep them updated as part of this release. diff --git a/package.json b/package.json index e6fee11aad..deafed519e 100644 --- a/package.json +++ b/package.json @@ -100,8 +100,8 @@ "it-pushable": "^1.4.0", "libp2p": ".", "libp2p-bootstrap": "^0.12.0", - "libp2p-delegated-content-routing": "^0.7.0", - "libp2p-delegated-peer-routing": "^0.7.0", + "libp2p-delegated-content-routing": "^0.8.0", + "libp2p-delegated-peer-routing": "^0.8.0", "libp2p-floodsub": "^0.24.0", "libp2p-gossipsub": "^0.7.0", "libp2p-kad-dht": "^0.20.0", From f7e1426b9ef7901ffecf0e7290e1154657849930 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 4 Dec 2020 14:53:05 +0100 Subject: [PATCH 067/447] chore: update pubsub example by disabled emit self (#823) --- examples/pubsub/1.js | 1 + examples/pubsub/README.md | 19 ++++++++++++++----- examples/pubsub/message-filtering/1.js | 1 + examples/pubsub/message-filtering/README.md | 3 --- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/examples/pubsub/1.js b/examples/pubsub/1.js index 8c6fdfdb91..419838960b 100644 --- a/examples/pubsub/1.js +++ b/examples/pubsub/1.js @@ -43,6 +43,7 @@ const createNode = async () => { }) await node1.pubsub.subscribe(topic) + // Will not receive own published messages by default node2.pubsub.on(topic, (msg) => { console.log(`node2 received: ${uint8ArrayToString(msg.data)}`) }) diff --git a/examples/pubsub/README.md b/examples/pubsub/README.md index 17a896f3a6..2016b2c813 100644 --- a/examples/pubsub/README.md +++ b/examples/pubsub/README.md @@ -44,7 +44,6 @@ const node2 = nodes[1] // Add node's 2 data to the PeerStore node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) - await node1.dial(node2.peerId) node1.pubsub.on(topic, (msg) => { @@ -52,6 +51,7 @@ node1.pubsub.on(topic, (msg) => { }) await node1.pubsub.subscribe(topic) +// Will not receive own published messages by default node2.pubsub.on(topic, (msg) => { console.log(`node2 received: ${uint8ArrayToString(msg.data)}`) }) @@ -68,25 +68,34 @@ The output of the program should look like: ``` > node 1.js connected to QmWpvkKm6qHLhoxpWrTswY6UMNWDyn8hN265Qp9ZYvgS82 -node2 received: Bird bird bird, bird is the word! node1 received: Bird bird bird, bird is the word! -node2 received: Bird bird bird, bird is the word! node1 received: Bird bird bird, bird is the word! ``` -You can change the pubsub `emitSelf` option if you don't want the publishing node to receive its own messages. +You can change the pubsub `emitSelf` option if you want the publishing node to receive its own messages. ```JavaScript const defaults = { config: { pubsub: { enabled: true, - emitSelf: false + emitSelf: true } } } ``` +The output of the program should look like: + +``` +> node 1.js +connected to QmWpvkKm6qHLhoxpWrTswY6UMNWDyn8hN265Qp9ZYvgS82 +node1 received: Bird bird bird, bird is the word! +node2 received: Bird bird bird, bird is the word! +node1 received: Bird bird bird, bird is the word! +node2 received: Bird bird bird, bird is the word! +``` + ## 2. Future work libp2p/IPFS PubSub is enabling a whole set of Distributed Real Time applications using CRDT (Conflict-Free Replicated Data Types). It is still going through heavy research (and hacking) and we invite you to join the conversation at [research-CRDT](https://github.com/ipfs/research-CRDT). Here is a list of some of the exciting examples: diff --git a/examples/pubsub/message-filtering/1.js b/examples/pubsub/message-filtering/1.js index 85d7bcf8c4..4d8a2c1803 100644 --- a/examples/pubsub/message-filtering/1.js +++ b/examples/pubsub/message-filtering/1.js @@ -44,6 +44,7 @@ const createNode = async () => { //subscribe node1.pubsub.on(topic, (msg) => { + // Will not receive own published messages by default console.log(`node1 received: ${uint8ArrayToString(msg.data)}`) }) await node1.pubsub.subscribe(topic) diff --git a/examples/pubsub/message-filtering/README.md b/examples/pubsub/message-filtering/README.md index a9c0dad26d..df99043051 100644 --- a/examples/pubsub/message-filtering/README.md +++ b/examples/pubsub/message-filtering/README.md @@ -97,15 +97,12 @@ 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 From 7809e6444e9ea605071a080b35356966a921b677 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 9 Dec 2020 16:27:42 +0100 Subject: [PATCH 068/447] chore: auto relay configuration example with noise (#828) --- doc/CONFIGURATION.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 06cc8faed3..79c20c8767 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -445,13 +445,13 @@ 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 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) From 169bb806a7d7133950d2f25706775d21cbea4759 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 10 Dec 2020 14:48:14 +0100 Subject: [PATCH 069/447] chore: add typedefs (#802) --- .github/workflows/main.yml | 67 ++++++ .travis.yml | 58 ----- package.json | 15 +- src/address-manager/index.js | 28 ++- src/circuit/auto-relay.js | 29 ++- src/circuit/circuit/hop.js | 55 ++++- src/circuit/circuit/stop.js | 28 ++- src/circuit/circuit/stream-handler.js | 25 +- src/circuit/circuit/utils.js | 7 +- src/circuit/index.js | 22 +- src/circuit/listener.js | 52 ++-- src/circuit/protocol/index.js | 2 + src/circuit/transport.js | 62 +++-- src/circuit/utils.js | 2 + src/connection-manager/index.js | 81 ++++--- src/connection-manager/latency-monitor.js | 19 +- .../visibility-change-emitter.js | 12 +- src/content-routing.js | 223 ++++++++++-------- src/dialer/dial-request.js | 39 +-- src/dialer/index.js | 79 ++++--- src/get-peer.js | 6 +- src/identify/consts.js | 1 + src/identify/index.js | 98 ++++---- src/index.js | 145 +++++++++--- src/insecure/plaintext.js | 18 +- src/keychain/cms.js | 7 +- src/keychain/index.js | 4 +- src/keychain/util.js | 1 + src/metrics/index.js | 37 ++- src/metrics/old-peers.js | 3 +- src/metrics/stats.js | 25 +- src/peer-routing.js | 12 +- src/peer-store/address-book.js | 118 +++++---- src/peer-store/book.js | 22 +- src/peer-store/index.js | 21 +- src/peer-store/key-book.js | 20 +- src/peer-store/metadata-book.js | 24 +- src/peer-store/persistent/index.js | 23 +- src/peer-store/proto-book.js | 24 +- src/ping/index.js | 21 +- src/pnet/crypto.js | 9 +- src/pnet/index.js | 22 +- src/pnet/key-generator.js | 2 + src/pubsub-adapter.js | 78 +++--- src/record/envelope/envelope.proto.js | 7 +- src/record/envelope/index.js | 24 +- src/record/peer-record/index.js | 32 ++- src/record/peer-record/peer-record.proto.js | 7 +- src/record/utils.js | 6 +- src/registrar.js | 22 +- src/transport-manager.js | 36 ++- src/types.ts | 84 +++++++ src/upgrader.js | 85 +++---- test/dialing/dial-request.spec.js | 2 +- test/identify/index.spec.js | 30 ++- test/record/envelope.spec.js | 6 +- test/transports/transport-manager.node.js | 2 - test/upgrading/upgrader.spec.js | 22 -- tsconfig.json | 9 + 59 files changed, 1255 insertions(+), 765 deletions(-) create mode 100644 .github/workflows/main.yml delete mode 100644 .travis.yml create mode 100644 src/types.ts create mode 100644 tsconfig.json diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000000..ade1dcbcc0 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,67 @@ +name: ci +on: + push: + branches: + - master + pull_request: + branches: + - '**' + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: yarn lint + - uses: gozala/typescript-error-reporter-action@v1.0.8 + - run: yarn build + - run: yarn aegir dep-check + - uses: ipfs/aegir/actions/bundle-size@master + name: size + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + test-node: + needs: check + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + node: [12, 14] + fail-fast: true + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node }} + - run: yarn + - run: npx nyc --reporter=lcov aegir test -t node -- --bail + - uses: codecov/codecov-action@v1 + test-chrome: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: npx aegir test -t browser -t webworker --bail + test-firefox: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: npx aegir test -t browser -t webworker --bail -- --browsers FirefoxHeadless + test-interop: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: cd node_modules/interop-libp2p && yarn && LIBP2P_JS=${GITHUB_WORKSPACE}/src/index.js npx aegir test -t node --bail + test-auto-relay-example: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: cd examples && yarn && npm run test -- auto-relay diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 47f1fcb07a..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,58 +0,0 @@ -language: node_js -cache: npm -stages: - - check - - test - - cov - -node_js: - - 'lts/*' - - '14' - -os: - - linux - - osx - -script: npx nyc -s npm run test:node -- --bail -after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov - -jobs: - include: - - stage: check - script: - - npx aegir build --bundlesize - # Remove pull libs once ping is async - - npx aegir dep-check -- -i pull-handshake -i pull-stream - - npm run lint - - - stage: test - name: chrome - addons: - chrome: stable - script: - - npx aegir test -t browser -t webworker - - - stage: test - name: firefox - addons: - firefox: latest - script: - - npx aegir test -t browser -t webworker -- --browsers FirefoxHeadless - - - stage: test - name: interop - script: - - cd node_modules/interop-libp2p - - npm install - - LIBP2P_JS=${TRAVIS_BUILD_DIR}/src/index.js npx aegir test -t node --bail - - - stage: test - if: type = pull_request - name: example - auto-relay - script: - - cd examples - - npm install - - npm run test -- auto-relay - -notifications: - email: false \ No newline at end of file diff --git a/package.json b/package.json index deafed519e..5275138497 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,15 @@ "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", + "types": "dist/src/index.d.ts", + "typesVersions": { + "*": { + "src/*": [ + "dist/src/*", + "dist/src/*/index" + ] + } + }, "files": [ "dist", "src" @@ -53,7 +62,7 @@ "events": "^3.1.0", "hashlru": "^2.3.0", "interface-datastore": "^2.0.0", - "ipfs-utils": "^2.2.0", + "ipfs-utils": "^5.0.1", "it-all": "^1.0.1", "it-buffer": "^0.1.2", "it-handshake": "^1.0.1", @@ -61,7 +70,7 @@ "it-pipe": "^1.1.0", "it-protocol-buffers": "^0.2.0", "libp2p-crypto": "^0.18.0", - "libp2p-interfaces": "^0.7.2", + "libp2p-interfaces": "^0.8.0", "libp2p-utils": "^0.2.2", "mafmt": "^8.0.0", "merge-options": "^2.0.0", @@ -88,7 +97,7 @@ "devDependencies": { "@nodeutils/defaults-deep": "^1.1.0", "abortable-iterator": "^3.0.0", - "aegir": "^27.0.0", + "aegir": "^29.2.0", "chai-bytes": "^0.1.2", "chai-string": "^1.5.0", "delay": "^4.3.0", diff --git a/src/address-manager/index.js b/src/address-manager/index.js index 314f0a1ae6..5c9874af33 100644 --- a/src/address-manager/index.js +++ b/src/address-manager/index.js @@ -1,23 +1,25 @@ 'use strict' -const debug = require('debug') -const log = debug('libp2p:addresses') -log.error = debug('libp2p:addresses:error') - const multiaddr = require('multiaddr') /** - * Responsible for managing the peer addresses. - * Peers can specify their listen and announce addresses. - * The listen addresses will be used by the libp2p transports to listen for new connections, - * while the announce addresses will be used for the peer addresses' to other peers in the network. + * @typedef {import('multiaddr')} Multiaddr + */ + +/** + * @typedef {Object} AddressManagerOptions + * @property {string[]} [listen = []] - list of multiaddrs string representation to listen. + * @property {string[]} [announce = []] - list of multiaddrs string representation to announce. */ class AddressManager { /** + * Responsible for managing the peer addresses. + * Peers can specify their listen and announce addresses. + * The listen addresses will be used by the libp2p transports to listen for new connections, + * while the announce addresses will be used for the peer addresses' to other peers in the network. + * * @class - * @param {object} [options] - * @param {Array} [options.listen = []] - list of multiaddrs string representation to listen. - * @param {Array} [options.announce = []] - list of multiaddrs string representation to announce. + * @param {AddressManagerOptions} [options] */ constructor ({ listen = [], announce = [] } = {}) { this.listen = new Set(listen) @@ -27,7 +29,7 @@ class AddressManager { /** * Get peer listen multiaddrs. * - * @returns {Array} + * @returns {Multiaddr[]} */ getListenAddrs () { return Array.from(this.listen).map((a) => multiaddr(a)) @@ -36,7 +38,7 @@ class AddressManager { /** * Get peer announcing multiaddrs. * - * @returns {Array} + * @returns {Multiaddr[]} */ getAnnounceAddrs () { return Array.from(this.announce).map((a) => multiaddr(a)) diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js index 6b9a40392d..122ac979fb 100644 --- a/src/circuit/auto-relay.js +++ b/src/circuit/auto-relay.js @@ -1,8 +1,9 @@ 'use strict' const debug = require('debug') -const log = debug('libp2p:auto-relay') -log.error = debug('libp2p:auto-relay:error') +const log = Object.assign(debug('libp2p:auto-relay'), { + error: debug('libp2p:auto-relay:err') +}) const uint8ArrayFromString = require('uint8arrays/from-string') const uint8ArrayToString = require('uint8arrays/to-string') @@ -19,14 +20,25 @@ const { RELAY_RENDEZVOUS_NS } = require('./constants') +/** + * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection + * @typedef {import('../peer-store/address-book').Address} Address + */ + +/** + * @typedef {Object} AutoRelayProperties + * @property {import('../')} libp2p + * + * @typedef {Object} AutoRelayOptions + * @property {number} [maxListeners = 1] - maximum number of relays to listen. + */ + 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. + * @param {AutoRelayProperties & AutoRelayOptions} props */ constructor ({ libp2p, maxListeners = 1 }) { this._libp2p = libp2p @@ -58,7 +70,7 @@ class AutoRelay { * * @param {Object} props * @param {PeerId} props.peerId - * @param {Array} props.protocols + * @param {string[]} props.protocols * @returns {Promise} */ async _onProtocolChange ({ peerId, protocols }) { @@ -78,6 +90,9 @@ class AutoRelay { // If protocol, check if can hop, store info in the metadataBook and listen on it try { const connection = this._connectionManager.get(peerId) + if (!connection) { + return + } // Do not hop on a relayed connection if (connection.remoteAddr.protoCodes().includes(CIRCUIT_PROTO_CODE)) { @@ -171,7 +186,7 @@ class AutoRelay { * 2. Dial and try to listen on the peers we know that support hop but are not connected. * 3. Search the network. * - * @param {Array} [peersToIgnore] + * @param {string[]} [peersToIgnore] * @returns {Promise} */ async _listenOnAvailableHopRelays (peersToIgnore = []) { diff --git a/src/circuit/circuit/hop.js b/src/circuit/circuit/hop.js index c653a7c9ae..8e23e6a894 100644 --- a/src/circuit/circuit/hop.js +++ b/src/circuit/circuit/hop.js @@ -1,22 +1,42 @@ 'use strict' const debug = require('debug') -const log = debug('libp2p:circuit:hop') -log.error = debug('libp2p:circuit:hop:error') +const log = Object.assign(debug('libp2p:circuit:hop'), { + error: debug('libp2p:circuit:hop:err') +}) +const errCode = require('err-code') const PeerId = require('peer-id') const { validateAddrs } = require('./utils') const StreamHandler = require('./stream-handler') const { CircuitRelay: CircuitPB } = require('../protocol') -const pipe = require('it-pipe') -const errCode = require('err-code') +const { pipe } = require('it-pipe') const { codes: Errors } = require('../../errors') const { stop } = require('./stop') const multicodec = require('./../multicodec') -module.exports.handleHop = async function handleHop ({ +/** + * @typedef {import('../../types').CircuitRequest} CircuitRequest + * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection + * @typedef {import('./stream-handler')} StreamHandlerT + * @typedef {import('../transport')} Transport + */ + +/** + * @typedef {Object} HopRequest + * @property {Connection} connection + * @property {CircuitRequest} request + * @property {StreamHandlerT} streamHandler + * @property {Transport} circuit + */ + +/** + * @param {HopRequest} options + * @returns {Promise} + */ +async function handleHop ({ connection, request, streamHandler, @@ -51,6 +71,9 @@ module.exports.handleHop = async function handleHop ({ } // TODO: Handle being an active relay + if (!destinationConnection) { + return + } // Handle the incoming HOP request by performing a STOP request const stopRequest = { @@ -63,8 +86,7 @@ module.exports.handleHop = async function handleHop ({ try { destinationStream = await stop({ connection: destinationConnection, - request: stopRequest, - circuit + request: stopRequest }) } catch (err) { return log.error(err) @@ -91,10 +113,10 @@ module.exports.handleHop = async function handleHop ({ * * @param {object} options * @param {Connection} options.connection - Connection to the relay - * @param {*} options.request + * @param {CircuitRequest} options.request * @returns {Promise} */ -module.exports.hop = async function hop ({ +async function hop ({ connection, request }) { @@ -123,7 +145,7 @@ module.exports.hop = async function hop ({ * @param {Connection} options.connection - Connection to the relay * @returns {Promise} */ -module.exports.canHop = async function canHop ({ +async function canHop ({ connection }) { // Create a new stream to the relay @@ -149,11 +171,11 @@ module.exports.canHop = async function canHop ({ * * @param {Object} options * @param {Connection} options.connection - * @param {StreamHandler} options.streamHandler - * @param {Circuit} options.circuit + * @param {StreamHandlerT} options.streamHandler + * @param {Transport} options.circuit * @private */ -module.exports.handleCanHop = function handleCanHop ({ +function handleCanHop ({ connection, streamHandler, circuit @@ -165,3 +187,10 @@ module.exports.handleCanHop = function handleCanHop ({ code: canHop ? CircuitPB.Status.SUCCESS : CircuitPB.Status.HOP_CANT_SPEAK_RELAY }) } + +module.exports = { + handleHop, + hop, + canHop, + handleCanHop +} diff --git a/src/circuit/circuit/stop.js b/src/circuit/circuit/stop.js index 77eaa1fcc2..111b811dc2 100644 --- a/src/circuit/circuit/stop.js +++ b/src/circuit/circuit/stop.js @@ -1,23 +1,31 @@ 'use strict' +const debug = require('debug') +const log = Object.assign(debug('libp2p:circuit:stop'), { + error: debug('libp2p:circuit:stop:err') +}) + const { CircuitRelay: CircuitPB } = require('../protocol') const multicodec = require('../multicodec') const StreamHandler = require('./stream-handler') const { validateAddrs } = require('./utils') -const debug = require('debug') -const log = debug('libp2p:circuit:stop') -log.error = debug('libp2p:circuit:stop:error') +/** + * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection + * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream + * @typedef {import('../../types').CircuitRequest} CircuitRequest + * @typedef {import('./stream-handler')} StreamHandlerT + */ /** * Handles incoming STOP requests * * @private - * @param {*} options + * @param {Object} options * @param {Connection} options.connection - * @param {*} options.request - The CircuitRelay protobuf request (unencoded) - * @param {StreamHandler} options.streamHandler - * @returns {Promise<*>} Resolves a duplex iterable + * @param {CircuitRequest} options.request - The CircuitRelay protobuf request (unencoded) + * @param {StreamHandlerT} options.streamHandler + * @returns {Promise|void} Resolves a duplex iterable */ module.exports.handleStop = function handleStop ({ connection, @@ -44,10 +52,10 @@ module.exports.handleStop = function handleStop ({ * Creates a STOP request * * @private - * @param {*} options + * @param {Object} options * @param {Connection} options.connection - * @param {*} options.request - The CircuitRelay protobuf request (unencoded) - * @returns {Promise<*>} Resolves a duplex iterable + * @param {CircuitRequest} options.request - The CircuitRelay protobuf request (unencoded) + * @returns {Promise} Resolves a duplex iterable */ module.exports.stop = async function stop ({ connection, diff --git a/src/circuit/circuit/stream-handler.js b/src/circuit/circuit/stream-handler.js index 8b8ecf89bc..5be2c6edf5 100644 --- a/src/circuit/circuit/stream-handler.js +++ b/src/circuit/circuit/stream-handler.js @@ -1,20 +1,29 @@ 'use strict' +const debug = require('debug') +const log = Object.assign(debug('libp2p:circuit:stream-handler'), { + error: debug('libp2p:circuit:stream-handler:err') +}) + const lp = require('it-length-prefixed') const handshake = require('it-handshake') const { CircuitRelay: CircuitPB } = require('../protocol') -const debug = require('debug') -const log = debug('libp2p:circuit:stream-handler') -log.error = debug('libp2p:circuit:stream-handler:error') +/** + * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream + */ +/** + * @template T + */ class StreamHandler { /** * Create a stream handler for connection * + * @class * @param {object} options - * @param {*} options.stream - A duplex iterable - * @param {number} options.maxLength - max bytes length of message + * @param {MuxedStream} options.stream - A duplex iterable + * @param {number} [options.maxLength = 4096] - max bytes length of message */ constructor ({ stream, maxLength = 4096 }) { this.stream = stream @@ -27,7 +36,7 @@ class StreamHandler { * Read and decode message * * @async - * @returns {void} + * @returns {Promise} */ async read () { const msg = await this.decoder.next() @@ -45,10 +54,12 @@ class StreamHandler { /** * Encode and write array of buffers * - * @param {*} msg - An unencoded CircuitRelay protobuf message + * @param {CircuitPB} msg - An unencoded CircuitRelay protobuf message + * @returns {void} */ write (msg) { log('write message type %s', msg.type) + // @ts-ignore lp.encode expects type type 'Buffer | BufferList', not 'Uint8Array' this.shake.write(lp.encode.single(CircuitPB.encode(msg))) } diff --git a/src/circuit/circuit/utils.js b/src/circuit/circuit/utils.js index be7ab35a73..65c5afe47d 100644 --- a/src/circuit/circuit/utils.js +++ b/src/circuit/circuit/utils.js @@ -3,11 +3,16 @@ const multiaddr = require('multiaddr') const { CircuitRelay } = require('../protocol') +/** + * @typedef {import('./stream-handler')} StreamHandler + * @typedef {import('../../types').CircuitStatus} CircuitStatus + */ + /** * Write a response * * @param {StreamHandler} streamHandler - * @param {CircuitRelay.Status} status + * @param {CircuitStatus} status */ function writeResponse (streamHandler, status) { streamHandler.write({ diff --git a/src/circuit/index.js b/src/circuit/index.js index 9e556f7f18..447d829ac5 100644 --- a/src/circuit/index.js +++ b/src/circuit/index.js @@ -1,8 +1,9 @@ 'use strict' const debug = require('debug') -const log = debug('libp2p:relay') -log.error = debug('libp2p:relay:error') +const log = Object.assign(debug('libp2p:relay'), { + error: debug('libp2p:relay:err') +}) const { setDelayedInterval, @@ -17,6 +18,23 @@ const { RELAY_RENDEZVOUS_NS } = require('./constants') +/** + * @typedef {import('../')} Libp2p + * + * @typedef {Object} RelayAdvertiseOptions + * @property {number} [bootDelay = ADVERTISE_BOOT_DELAY] + * @property {boolean} [enabled = true] + * @property {number} [ttl = ADVERTISE_TTL] + * + * @typedef {Object} HopOptions + * @property {boolean} [enabled = false] + * @property {boolean} [active = false] + * + * @typedef {Object} AutoRelayOptions + * @property {number} [maxListeners = 2] - maximum number of relays to listen. + * @property {boolean} [enabled = false] + */ + class Relay { /** * Creates an instance of Relay. diff --git a/src/circuit/listener.js b/src/circuit/listener.js index 02e371fb8b..d19cca5e46 100644 --- a/src/circuit/listener.js +++ b/src/circuit/listener.js @@ -1,37 +1,27 @@ 'use strict' -const EventEmitter = require('events') +const { EventEmitter } = require('events') const multiaddr = require('multiaddr') -const debug = require('debug') -const log = debug('libp2p:circuit:listener') -log.err = debug('libp2p:circuit:error:listener') +/** + * @typedef {import('multiaddr')} Multiaddr + * @typedef {import('libp2p-interfaces/src/transport/types').Listener} Listener + */ /** - * @param {Libp2p} libp2p + * @param {import('../')} libp2p * @returns {Listener} a transport listener */ 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 - * @returns {void} + * @returns {Promise} */ - listener.listen = async (addr) => { + async function listen (addr) { const addrString = String(addr).split('/p2p-circuit').find(a => a !== '') const relayConn = await libp2p.dial(multiaddr(addrString)) @@ -41,13 +31,6 @@ module.exports = (libp2p) => { listener.emit('listening') } - /** - * TODO: Remove the peers from our topology - * - * @returns {void} - */ - listener.close = () => {} - /** * Get fixed up multiaddrs * @@ -64,7 +47,7 @@ module.exports = (libp2p) => { * * @returns {Multiaddr[]} */ - listener.getAddrs = () => { + function getAddrs () { const addrs = [] for (const addr of listeningAddrs.values()) { addrs.push(addr) @@ -72,5 +55,22 @@ module.exports = (libp2p) => { return addrs } + /** @type Listener */ + const listener = Object.assign(new EventEmitter(), { + close: () => Promise.resolve(), + listen, + getAddrs + }) + + // 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') + } + }) + return listener } diff --git a/src/circuit/protocol/index.js b/src/circuit/protocol/index.js index f217cb4262..a9d3e31a6f 100644 --- a/src/circuit/protocol/index.js +++ b/src/circuit/protocol/index.js @@ -1,5 +1,7 @@ 'use strict' const protobuf = require('protons') + +/** @type {{CircuitRelay: import('../../types').CircuitMessageProto}} */ module.exports = protobuf(` message CircuitRelay { diff --git a/src/circuit/transport.js b/src/circuit/transport.js index cc79870564..fc2ddad4f0 100644 --- a/src/circuit/transport.js +++ b/src/circuit/transport.js @@ -1,13 +1,13 @@ 'use strict' const debug = require('debug') -const log = debug('libp2p:circuit') -log.error = debug('libp2p:circuit:error') +const log = Object.assign(debug('libp2p:circuit'), { + error: debug('libp2p:circuit:err') +}) 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') @@ -18,14 +18,23 @@ const { handleCanHop, handleHop, hop } = require('./circuit/hop') const { handleStop } = require('./circuit/stop') const StreamHandler = require('./circuit/stream-handler') +const transportSymbol = Symbol.for('@libp2p/js-libp2p-circuit/circuit') + +/** + * @typedef {import('multiaddr')} Multiaddr + * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection + * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream + * @typedef {import('../types').CircuitRequest} CircuitRequest + */ + class Circuit { /** * Creates an instance of the Circuit Transport. * * @class * @param {object} options - * @param {Libp2p} options.libp2p - * @param {Upgrader} options.upgrader + * @param {import('../')} options.libp2p + * @param {import('../upgrader')} options.upgrader */ constructor ({ libp2p, upgrader }) { this._dialer = libp2p.dialer @@ -39,7 +48,13 @@ class Circuit { this._registrar.handle(multicodec, this._onProtocol.bind(this)) } + /** + * @param {Object} props + * @param {Connection} props.connection + * @param {MuxedStream} props.stream + */ async _onProtocol ({ connection, stream }) { + /** @type {import('./circuit/stream-handler')} */ const streamHandler = new StreamHandler({ stream }) const request = await streamHandler.read() @@ -71,8 +86,7 @@ class Circuit { virtualConnection = await handleStop({ connection, request, - streamHandler, - circuit + streamHandler }) break } @@ -89,7 +103,7 @@ class Circuit { remoteAddr, localAddr }) - const type = CircuitPB.Type === CircuitPB.Type.HOP ? 'relay' : 'inbound' + const type = request.type === CircuitPB.Type.HOP ? 'relay' : 'inbound' log('new %s connection %s', type, maConn.remoteAddr) const conn = await this._upgrader.upgradeInbound(maConn) @@ -101,10 +115,10 @@ class Circuit { /** * Dial a peer over a relay * - * @param {multiaddr} ma - the multiaddr of the peer to dial + * @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 {Promise} - the connection */ async dial (ma, options) { // Check the multiaddr to see if it contains a relay and a destination peer @@ -124,7 +138,6 @@ class Circuit { try { const virtualConnection = await hop({ connection: relayConnection, - circuit: this, request: { type: CircuitPB.Type.HOP, srcPeer: { @@ -159,7 +172,7 @@ class Circuit { * * @param {any} options * @param {Function} handler - * @returns {listener} + * @returns {import('libp2p-interfaces/src/transport/types').Listener} */ createListener (options, handler) { if (typeof options === 'function') { @@ -170,14 +183,14 @@ class Circuit { // Called on successful HOP and STOP requests this.handler = handler - return createListener(this._libp2p, options) + return createListener(this._libp2p) } /** * Filter check for all Multiaddrs that this transport can dial on * - * @param {Array} multiaddrs - * @returns {Array} + * @param {Multiaddr[]} multiaddrs + * @returns {Multiaddr[]} */ filter (multiaddrs) { multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs] @@ -186,9 +199,20 @@ class Circuit { return mafmt.Circuit.matches(ma) }) } + + get [Symbol.toStringTag] () { + return 'Circuit' + } + + /** + * Checks if the given value is a Transport instance. + * + * @param {any} other + * @returns {other is Transport} + */ + static isTransport (other) { + return Boolean(other && other[transportSymbol]) + } } -/** - * @type {Circuit} - */ -module.exports = withIs(Circuit, { className: 'Circuit', symbolName: '@libp2p/js-libp2p-circuit/circuit' }) +module.exports = Circuit diff --git a/src/circuit/utils.js b/src/circuit/utils.js index 18b61eafbb..f75e13386a 100644 --- a/src/circuit/utils.js +++ b/src/circuit/utils.js @@ -3,6 +3,8 @@ const CID = require('cids') const multihashing = require('multihashing-async') +const TextEncoder = require('ipfs-utils/src/text-encoder') + /** * Convert a namespace string into a cid. * diff --git a/src/connection-manager/index.js b/src/connection-manager/index.js index 1b1d807cb5..4add4840e1 100644 --- a/src/connection-manager/index.js +++ b/src/connection-manager/index.js @@ -1,8 +1,9 @@ 'use strict' const debug = require('debug') -const log = debug('libp2p:connection-manager') -log.error = debug('libp2p:connection-manager:error') +const log = Object.assign(debug('libp2p:connection-manager'), { + error: debug('libp2p:connection-manager:err') +}) const errcode = require('err-code') const mergeOptions = require('merge-options') @@ -14,7 +15,7 @@ const { EventEmitter } = require('events') const PeerId = require('peer-id') const { - ERR_INVALID_PARAMETERS + codes: { ERR_INVALID_PARAMETERS } } = require('../errors') const defaultOptions = { @@ -31,29 +32,39 @@ const defaultOptions = { } /** - * Responsible for managing known connections. + * @typedef {import('../')} Libp2p + * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection + */ + +/** + * @typedef {Object} ConnectionManagerOptions + * @property {number} [maxConnections = Infinity] - The maximum number of connections allowed. + * @property {number} [minConnections = 0] - The minimum number of connections to avoid pruning. + * @property {number} [maxData = Infinity] - The max data (in and out), per average interval to allow. + * @property {number} [maxSentData = Infinity] - The max outgoing data, per average interval to allow. + * @property {number} [maxReceivedData = Infinity] - The max incoming data, per average interval to allow. + * @property {number} [maxEventLoopDelay = Infinity] - The upper limit the event loop can take to run. + * @property {number} [pollInterval = 2000] - How often, in milliseconds, metrics and latency should be checked. + * @property {number} [movingAverageInterval = 60000] - How often, in milliseconds, to compute averages. + * @property {number} [defaultPeerValue = 1] - The value of the peer. + * @property {boolean} [autoDial = true] - Should preemptively guarantee connections are above the low watermark. + * @property {number} [autoDialInterval = 10000] - How often, in milliseconds, it should preemptively guarantee connections are above the low watermark. + */ + +/** * * @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 { /** + * Responsible for managing known connections. + * * @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 {ConnectionManagerOptions} options */ - constructor (libp2p, options) { + constructor (libp2p, options = {}) { super() this._libp2p = libp2p @@ -66,8 +77,6 @@ class ConnectionManager extends EventEmitter { log('options: %j', this._options) - this._libp2p = libp2p - /** * Map of peer identifiers to their peer value for pruning connections. * @@ -78,7 +87,7 @@ class ConnectionManager extends EventEmitter { /** * Map of connections per peer * - * @type {Map>} + * @type {Map} */ this.connections = new Map() @@ -159,15 +168,13 @@ class ConnectionManager extends EventEmitter { * * @param {PeerId} peerId * @param {number} value - A number between 0 and 1 + * @returns {void} */ setPeerValue (peerId, value) { if (value < 0 || value > 1) { throw new Error('value should be a number between 0 and 1') } - if (peerId.toB58String) { - peerId = peerId.toB58String() - } - this._peerValues.set(peerId, value) + this._peerValues.set(peerId.toB58String(), value) } /** @@ -177,21 +184,24 @@ class ConnectionManager extends EventEmitter { * @private */ _checkMetrics () { - const movingAverages = this._libp2p.metrics.global.movingAverages - const received = movingAverages.dataReceived[this._options.movingAverageInterval].movingAverage() - this._checkMaxLimit('maxReceivedData', received) - const sent = movingAverages.dataSent[this._options.movingAverageInterval].movingAverage() - this._checkMaxLimit('maxSentData', sent) - const total = received + sent - this._checkMaxLimit('maxData', total) - log('metrics update', total) - this._timer = retimer(this._checkMetrics, this._options.pollInterval) + if (this._libp2p.metrics) { + const movingAverages = this._libp2p.metrics.global.movingAverages + const received = movingAverages.dataReceived[this._options.movingAverageInterval].movingAverage() + this._checkMaxLimit('maxReceivedData', received) + const sent = movingAverages.dataSent[this._options.movingAverageInterval].movingAverage() + this._checkMaxLimit('maxSentData', sent) + const total = received + sent + this._checkMaxLimit('maxData', total) + log('metrics update', total) + this._timer = retimer(this._checkMetrics, this._options.pollInterval) + } } /** * Tracks the incoming connection and check the connection limit * * @param {Connection} connection + * @returns {void} */ onConnect (connection) { const peerId = connection.remotePeer @@ -218,6 +228,7 @@ class ConnectionManager extends EventEmitter { * Removes the connection from tracking * * @param {Connection} connection + * @returns {void} */ onDisconnect (connection) { const peerId = connection.remotePeer.toB58String() @@ -237,7 +248,7 @@ class ConnectionManager extends EventEmitter { * Get a connection with a peer. * * @param {PeerId} peerId - * @returns {Connection} + * @returns {Connection|null} */ get (peerId) { const connections = this.getAll(peerId) @@ -251,7 +262,7 @@ class ConnectionManager extends EventEmitter { * Get all open connections with a peer. * * @param {PeerId} peerId - * @returns {Array} + * @returns {Connection[]} */ getAll (peerId) { if (!PeerId.isPeerId(peerId)) { diff --git a/src/connection-manager/latency-monitor.js b/src/connection-manager/latency-monitor.js index db299890b4..c9301ee142 100644 --- a/src/connection-manager/latency-monitor.js +++ b/src/connection-manager/latency-monitor.js @@ -1,3 +1,4 @@ +// @ts-nocheck 'use strict' /** @@ -6,7 +7,7 @@ /* global window */ const globalThis = require('ipfs-utils/src/globalthis') -const EventEmitter = require('events') +const { EventEmitter } = require('events') const VisibilityChangeEmitter = require('./visibility-change-emitter') const debug = require('debug')('latency-monitor:LatencyMonitor') @@ -17,6 +18,12 @@ const debug = require('debug')('latency-monitor:LatencyMonitor') * @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 + * + * @typedef {Object} LatencyMonitorOptions + * @property {number} [latencyCheckIntervalMs=500] - How often to add a latency check event (ms) + * @property {number} [dataEmitIntervalMs=5000] - How often to summarize latency check events. null or 0 disables event firing + * @property {Function} [asyncTestFn] - What cb-style async function to use + * @property {number} [latencyRandomPercentage=5] - What percent (+/-) of latencyCheckIntervalMs should we randomly use? This helps avoid alignment to other events. */ /** @@ -24,6 +31,8 @@ const debug = require('debug')('latency-monitor:LatencyMonitor') * the asyncTestFn and timing how long it takes the callback to be called. It can also periodically emit stats about this. * This can be disabled and stats can be pulled via setting dataEmitIntervalMs = 0. * + * @extends {EventEmitter} + * * The default implementation is an event loop latency monitor. This works by firing periodic events into the event loop * and timing how long it takes to get back. * @@ -37,11 +46,8 @@ const debug = require('debug')('latency-monitor:LatencyMonitor') */ class LatencyMonitor extends EventEmitter { /** - * @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. + * @class + * @param {LatencyMonitorOptions} [options] */ constructor ({ latencyCheckIntervalMs, dataEmitIntervalMs, asyncTestFn, latencyRandomPercentage } = {}) { super() @@ -91,6 +97,7 @@ class LatencyMonitor extends EventEmitter { // See: http://stackoverflow.com/questions/6032429/chrome-timeouts-interval-suspended-in-background-tabs if (isBrowser()) { that._visibilityChangeEmitter = new VisibilityChangeEmitter() + that._visibilityChangeEmitter.on('visibilityChange', (pageInFocus) => { if (pageInFocus) { that._startTimers() diff --git a/src/connection-manager/visibility-change-emitter.js b/src/connection-manager/visibility-change-emitter.js index baece0ec17..ebe5e7d076 100644 --- a/src/connection-manager/visibility-change-emitter.js +++ b/src/connection-manager/visibility-change-emitter.js @@ -1,10 +1,12 @@ +// @ts-nocheck /* global document */ /** * This code is based on `latency-monitor` (https://github.com/mlucool/latency-monitor) by `mlucool` (https://github.com/mlucool), available under Apache License 2.0 (https://github.com/mlucool/latency-monitor/blob/master/LICENSE) */ 'use strict' -const EventEmitter = require('events') + +const { EventEmitter } = require('events') const debug = require('debug')('latency-monitor:VisibilityChangeEmitter') @@ -29,12 +31,12 @@ const debug = require('debug')('latency-monitor:VisibilityChangeEmitter') * }); * // To access the visibility state directly, call: * console.log('Am I focused now? ' + myVisibilityEmitter.isVisible()); - * - * @class VisibilityChangeEmitter */ -module.exports = class VisibilityChangeEmitter extends EventEmitter { +class VisibilityChangeEmitter extends EventEmitter { /** * Creates a VisibilityChangeEmitter + * + * @class */ constructor () { super() @@ -119,3 +121,5 @@ module.exports = class VisibilityChangeEmitter extends EventEmitter { this.emit('visibilityChange', visible) } } + +module.exports = VisibilityChangeEmitter diff --git a/src/content-routing.js b/src/content-routing.js index d7e160e79e..cba4a38bce 100644 --- a/src/content-routing.js +++ b/src/content-routing.js @@ -6,111 +6,130 @@ const { messages, codes } = require('./errors') const all = require('it-all') const pAny = require('p-any') -module.exports = (node) => { - const routers = node._modules.contentRouting || [] - const dht = node._dht +/** + * @typedef {import('peer-id')} PeerId + * @typedef {import('multiaddr')} Multiaddr + * @typedef {import('cids')} CID + */ + +/** + * @typedef {Object} GetData + * @property {PeerId} from + * @property {Uint8Array} val + */ + +class ContentRouting { + /** + * @class + * @param {import('./')} libp2p + */ + constructor (libp2p) { + this.libp2p = libp2p + this.routers = libp2p._modules.contentRouting || [] + this.dht = libp2p._dht + + // If we have the dht, make it first + if (this.dht) { + this.routers.unshift(this.dht) + } + } + + /** + * 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 {object} [options] + * @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[] }>} + */ + async * findProviders (key, options) { + if (!this.routers.length) { + throw errCode(new Error('No content this.routers available'), 'NO_ROUTERS_AVAILABLE') + } + + const result = await pAny( + this.routers.map(async (router) => { + const provs = await all(router.findProviders(key, options)) + + if (!provs || !provs.length) { + throw errCode(new Error('not found'), 'NOT_FOUND') + } + return provs + }) + ) - // If we have the dht, make it first - if (dht) { - routers.unshift(dht) + for (const peer of result) { + yield peer + } } - return { - /** - * 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 {object} [options] - * @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[] }>} - */ - async * findProviders (key, options) { - if (!routers.length) { - throw errCode(new Error('No content routers available'), 'NO_ROUTERS_AVAILABLE') - } - - const result = await pAny( - routers.map(async (router) => { - const provs = await all(router.findProviders(key, options)) - - if (!provs || !provs.length) { - throw errCode(new Error('not found'), 'NOT_FOUND') - } - return provs - }) - ) - - for (const peer of result) { - yield peer - } - }, - - /** - * Iterates over all content routers in parallel to notify it is - * a provider of the given key. - * - * @param {CID} key - The CID key of the content to find - * @returns {Promise} - */ - async provide (key) { // eslint-disable-line require-await - if (!routers.length) { - throw errCode(new Error('No content routers available'), 'NO_ROUTERS_AVAILABLE') - } - - return Promise.all(routers.map((router) => router.provide(key))) - }, - - /** - * Store the given key/value pair in the DHT. - * - * @param {Uint8Array} key - * @param {Uint8Array} value - * @param {Object} [options] - put options - * @param {number} [options.minPeers] - minimum number of peers required to successfully put - * @returns {Promise} - */ - async put (key, value, options) { // eslint-disable-line require-await - if (!node.isStarted() || !dht.isStarted) { - throw errCode(new Error(messages.NOT_STARTED_YET), codes.DHT_NOT_STARTED) - } - - return dht.put(key, value, options) - }, - - /** - * Get the value to the given key. - * Times out after 1 minute by default. - * - * @param {Uint8Array} key - * @param {Object} [options] - get options - * @param {number} [options.timeout] - optional timeout (default: 60000) - * @returns {Promise<{from: PeerId, val: Uint8Array}>} - */ - async get (key, options) { // eslint-disable-line require-await - if (!node.isStarted() || !dht.isStarted) { - throw errCode(new Error(messages.NOT_STARTED_YET), codes.DHT_NOT_STARTED) - } - - return dht.get(key, options) - }, - - /** - * Get the `n` values to the given key without sorting. - * - * @param {Uint8Array} key - * @param {number} nVals - * @param {Object} [options] - get options - * @param {number} [options.timeout] - optional timeout (default: 60000) - * @returns {Promise>} - */ - async getMany (key, nVals, options) { // eslint-disable-line require-await - if (!node.isStarted() || !dht.isStarted) { - throw errCode(new Error(messages.NOT_STARTED_YET), codes.DHT_NOT_STARTED) - } - - return dht.getMany(key, nVals, options) + /** + * Iterates over all content routers in parallel to notify it is + * a provider of the given key. + * + * @param {CID} key - The CID key of the content to find + * @returns {Promise} + */ + async provide (key) { + if (!this.routers.length) { + throw errCode(new Error('No content routers available'), 'NO_ROUTERS_AVAILABLE') } + + await Promise.all(this.routers.map((router) => router.provide(key))) + } + + /** + * Store the given key/value pair in the DHT. + * + * @param {Uint8Array} key + * @param {Uint8Array} value + * @param {Object} [options] - put options + * @param {number} [options.minPeers] - minimum number of peers required to successfully put + * @returns {Promise} + */ + put (key, value, options) { + if (!this.libp2p.isStarted() || !this.dht.isStarted) { + throw errCode(new Error(messages.NOT_STARTED_YET), codes.DHT_NOT_STARTED) + } + + return this.dht.put(key, value, options) + } + + /** + * 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) + * @returns {Promise} + */ + get (key, options) { + if (!this.libp2p.isStarted() || !this.dht.isStarted) { + throw errCode(new Error(messages.NOT_STARTED_YET), codes.DHT_NOT_STARTED) + } + + return this.dht.get(key, options) + } + + /** + * Get the `n` values to the given key without sorting. + * + * @param {Uint8Array} key + * @param {number} nVals + * @param {Object} [options] - get options + * @param {number} [options.timeout] - optional timeout (default: 60000) + * @returns {Promise} + */ + async getMany (key, nVals, options) { // eslint-disable-line require-await + if (!this.libp2p.isStarted() || !this.dht.isStarted) { + throw errCode(new Error(messages.NOT_STARTED_YET), codes.DHT_NOT_STARTED) + } + + return this.dht.getMany(key, nVals, options) } } + +module.exports = ContentRouting diff --git a/src/dialer/dial-request.js b/src/dialer/dial-request.js index dc427e1bbf..62bab31be6 100644 --- a/src/dialer/dial-request.js +++ b/src/dialer/dial-request.js @@ -1,14 +1,27 @@ 'use strict' -const AbortController = require('abort-controller') -const anySignal = require('any-signal') -const debug = require('debug') const errCode = require('err-code') -const log = debug('libp2p:dialer:request') -log.error = debug('libp2p:dialer:request:error') +const AbortController = require('abort-controller').default +const anySignal = require('any-signal') const FIFO = require('p-fifo') const pAny = require('p-any') +/** + * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection + * @typedef {import('./')} Dialer + * @typedef {import('multiaddr')} Multiaddr + */ + +/** + * @typedef {Object} DialOptions + * @property {AbortSignal} signal + * + * @typedef {Object} DialRequestOptions + * @property {Multiaddr[]} addrs + * @property {(m: Multiaddr, options: DialOptions) => Promise} dialAction + * @property {Dialer} dialer + */ + class DialRequest { /** * Manages running the `dialAction` on multiple provided `addrs` in parallel @@ -17,10 +30,8 @@ class DialRequest { * 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} options.dialAction - * @param {Dialer} options.dialer + * @class + * @param {DialRequestOptions} options */ constructor ({ addrs, @@ -34,11 +45,11 @@ class DialRequest { /** * @async - * @param {object} options - * @param {AbortSignal} options.signal - An AbortController signal - * @returns {Connection} + * @param {object} [options] + * @param {AbortSignal} [options.signal] - An AbortController signal + * @returns {Promise} */ - async run (options) { + async run (options = {}) { const tokens = this.dialer.getTokens(this.addrs.length) // If no tokens are available, throw if (tokens.length < 1) { @@ -78,4 +89,4 @@ class DialRequest { } } -module.exports.DialRequest = DialRequest +module.exports = DialRequest diff --git a/src/dialer/index.js b/src/dialer/index.js index 3ee3ada6c8..09ae2627ad 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -1,14 +1,15 @@ 'use strict' -const multiaddr = require('multiaddr') +const debug = require('debug') +const log = Object.assign(debug('libp2p:dialer'), { + error: debug('libp2p:dialer:err') +}) const errCode = require('err-code') +const multiaddr = require('multiaddr') const TimeoutController = require('timeout-abort-controller') const anySignal = require('any-signal') -const debug = require('debug') -const log = debug('libp2p:dialer') -log.error = debug('libp2p:dialer:error') -const { DialRequest } = require('./dial-request') +const DialRequest = require('./dial-request') const { publicAddressesFirst } = require('libp2p-utils/src/address-sort') const getPeer = require('../get-peer') @@ -19,17 +20,44 @@ const { MAX_PER_PEER_DIALS } = require('../constants') +/** + * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection + * @typedef {import('multiaddr')} Multiaddr + * @typedef {import('peer-id')} PeerId + * @typedef {import('../peer-store')} PeerStore + * @typedef {import('../peer-store/address-book').Address} Address + * @typedef {import('../transport-manager')} TransportManager + */ + +/** + * @typedef {Object} DialerProperties + * @property {PeerStore} peerStore + * @property {TransportManager} transportManager + * + * @typedef {(addr:Multiaddr) => Promise} Resolver + * + * @typedef {Object} DialerOptions + * @property {(addresses: Address[]) => Address[]} [options.addressSorter = publicAddressesFirst] - Sort the known addresses of a peer before trying to dial. + * @property {number} [concurrency = MAX_PARALLEL_DIALS] - Number of max concurrent dials. + * @property {number} [perPeerLimit = MAX_PER_PEER_DIALS] - Number of max concurrent dials per peer. + * @property {number} [timeout = DIAL_TIMEOUT] - How long a dial attempt is allowed to take. + * @property {Record} [resolvers = {}] - multiaddr resolvers to use when dialing + * + * @typedef DialTarget + * @property {string} id + * @property {Multiaddr[]} addrs + * + * @typedef PendingDial + * @property {DialRequest} dialRequest + * @property {TimeoutController} controller + * @property {Promise} promise + * @property {function():void} destroy + */ + class Dialer { /** * @class - * @param {object} options - * @param {TransportManager} options.transportManager - * @param {Peerstore} options.peerStore - * @param {(addresses: Array Array
} [options.addressSorter = publicAddressesFirst] - Sort the known addresses of a peer before trying to dial. - * @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 + * @param {DialerProperties & DialerOptions} options */ constructor ({ transportManager, @@ -102,12 +130,6 @@ class Dialer { } } - /** - * @typedef DialTarget - * @property {string} id - * @property {Multiaddr[]} addrs - */ - /** * Creates a DialTarget. The DialTarget is used to create and track * the DialRequest to a given peer. @@ -145,14 +167,6 @@ class Dialer { } } - /** - * @typedef PendingDial - * @property {DialRequest} dialRequest - * @property {TimeoutController} controller - * @property {Promise} promise - * @property {function():void} destroy - */ - /** * Creates a PendingDial that wraps the underlying DialRequest * @@ -162,7 +176,7 @@ class Dialer { * @param {AbortSignal} [options.signal] - An AbortController signal * @returns {PendingDial} */ - _createPendingDial (dialTarget, options) { + _createPendingDial (dialTarget, options = {}) { const dialAction = (addr, options) => { if (options.signal.aborted) throw errCode(new Error('already aborted'), codes.ERR_ALREADY_ABORTED) return this.transportManager.dial(addr, options) @@ -211,7 +225,7 @@ class Dialer { * Resolve multiaddr recursively. * * @param {Multiaddr} ma - * @returns {Promise>} + * @returns {Promise} */ async _resolve (ma) { // TODO: recursive logic should live in multiaddr once dns4/dns6 support is in place @@ -228,19 +242,20 @@ class Dialer { return this._resolve(nm) })) - return recursiveMultiaddrs.flat().reduce((array, newM) => { + const addrs = recursiveMultiaddrs.flat() + return addrs.reduce((array, newM) => { if (!array.find(m => m.equals(newM))) { array.push(newM) } return array - }, []) // Unique addresses + }, /** @type {Multiaddr[]} */([])) } /** * Resolve a given multiaddr. If this fails, an empty array will be returned * * @param {Multiaddr} ma - * @returns {Promise>} + * @returns {Promise} */ async _resolveRecord (ma) { try { diff --git a/src/get-peer.js b/src/get-peer.js index bc36b04cc9..807c333384 100644 --- a/src/get-peer.js +++ b/src/get-peer.js @@ -6,12 +6,16 @@ const errCode = require('err-code') const { codes } = require('./errors') +/** + * @typedef {import('multiaddr')} Multiaddr + */ + /** * Converts the given `peer` to a `Peer` object. * If a multiaddr is received, the addressBook is updated. * * @param {PeerId|Multiaddr|string} peer - * @returns {{ id: PeerId, multiaddrs: Array }} + * @returns {{ id: PeerId, multiaddrs: Multiaddr[]|undefined }} */ function getPeer (peer) { if (typeof peer === 'string') { diff --git a/src/identify/consts.js b/src/identify/consts.js index 58ec077faa..1f697e5dc5 100644 --- a/src/identify/consts.js +++ b/src/identify/consts.js @@ -1,5 +1,6 @@ 'use strict' +// @ts-ignore file not listed within the file list of projects const libp2pVersion = require('../../package.json').version module.exports.PROTOCOL_VERSION = 'ipfs/0.1.0' diff --git a/src/identify/index.js b/src/identify/index.js index 97673c12db..d118872c2f 100644 --- a/src/identify/index.js +++ b/src/identify/index.js @@ -1,13 +1,13 @@ 'use strict' const debug = require('debug') -const log = debug('libp2p:identify') -log.error = debug('libp2p:identify:error') - +const log = Object.assign(debug('libp2p:identify'), { + error: debug('libp2p:identify:err') +}) const errCode = require('err-code') const pb = require('it-protocol-buffers') const lp = require('it-length-prefixed') -const pipe = require('it-pipe') +const { pipe } = require('it-pipe') const { collect, take, consume } = require('streaming-iterables') const uint8ArrayFromString = require('uint8arrays/from-string') @@ -29,50 +29,23 @@ const { const { codes } = require('../errors') -class IdentifyService { - /** - * Takes the `addr` and converts it to a Multiaddr if possible - * - * @param {Uint8Array | string} addr - * @returns {Multiaddr|null} - */ - static getCleanMultiaddr (addr) { - if (addr && addr.length > 0) { - try { - return multiaddr(addr) - } catch (_) { - return null - } - } - return null - } +/** + * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection + * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream + */ +class IdentifyService { /** * @class - * @param {object} options - * @param {Libp2p} options.libp2p + * @param {Object} options + * @param {import('../')} options.libp2p */ constructor ({ libp2p }) { - /** - * @property {PeerStore} - */ + this._libp2p = libp2p this.peerStore = libp2p.peerStore - - /** - * @property {ConnectionManager} - */ this.connectionManager = libp2p.connectionManager - - /** - * @property {PeerId} - */ this.peerId = libp2p.peerId - /** - * @property {AddressManager} - */ - this._libp2p = libp2p - this.handleMessage = this.handleMessage.bind(this) // Store self host metadata @@ -84,6 +57,7 @@ class IdentifyService { this.peerStore.metadataBook.set(this.peerId, 'AgentVersion', uint8ArrayFromString(this._host.agentVersion)) this.peerStore.metadataBook.set(this.peerId, 'ProtocolVersion', uint8ArrayFromString(this._host.protocolVersion)) + // When a new connection happens, trigger identify this.connectionManager.on('peer:connect', (connection) => { this.identify(connection).catch(log.error) }) @@ -106,8 +80,8 @@ class IdentifyService { /** * Send an Identify Push update to the list of connections * - * @param {Array} connections - * @returns {Promise} + * @param {Connection[]} connections + * @returns {Promise} */ async push (connections) { const signedPeerRecord = await this.peerStore.addressBook.getRawEnvelope(this.peerId) @@ -234,11 +208,11 @@ class IdentifyService { /** * A handler to register with Libp2p to process identify messages. * - * @param {object} options - * @param {string} options.protocol - * @param {*} options.stream + * @param {Object} options * @param {Connection} options.connection - * @returns {Promise} + * @param {MuxedStream} options.stream + * @param {string} options.protocol + * @returns {Promise|undefined} */ handleMessage ({ connection, stream, protocol }) { switch (protocol) { @@ -256,9 +230,10 @@ class IdentifyService { * to the requesting peer over the given `connection` * * @private - * @param {object} options - * @param {*} options.stream + * @param {Object} options + * @param {MuxedStream} options.stream * @param {Connection} options.connection + * @returns {Promise} */ async _handleIdentify ({ connection, stream }) { let publicKey = new Uint8Array(0) @@ -296,8 +271,9 @@ class IdentifyService { * * @private * @param {object} options - * @param {*} options.stream + * @param {MuxedStream} options.stream * @param {Connection} options.connection + * @returns {Promise} */ async _handlePush ({ connection, stream }) { let message @@ -337,16 +313,36 @@ class IdentifyService { // Update the protocols this.peerStore.protoBook.set(id, message.protocols) } + + /** + * Takes the `addr` and converts it to a Multiaddr if possible + * + * @param {Uint8Array | string} addr + * @returns {multiaddr|null} + */ + static getCleanMultiaddr (addr) { + if (addr && addr.length > 0) { + try { + return multiaddr(addr) + } catch (_) { + return null + } + } + return null + } } -module.exports.IdentifyService = IdentifyService /** * The protocols the IdentifyService supports * * @property multicodecs */ -module.exports.multicodecs = { +const multicodecs = { IDENTIFY: MULTICODEC_IDENTIFY, IDENTIFY_PUSH: MULTICODEC_IDENTIFY_PUSH } -module.exports.Message = Message + +IdentifyService.multicodecs = multicodecs +IdentifyService.Messsage = Message + +module.exports = IdentifyService diff --git a/src/index.js b/src/index.js index d2e2a7cb67..8393d3faa5 100644 --- a/src/index.js +++ b/src/index.js @@ -1,16 +1,17 @@ 'use strict' -const { EventEmitter } = require('events') const debug = require('debug') +const log = Object.assign(debug('libp2p'), { + error: debug('libp2p:err') +}) +const { EventEmitter } = require('events') const globalThis = require('ipfs-utils/src/globalthis') -const log = debug('libp2p') -log.error = debug('libp2p:error') const errCode = require('err-code') const PeerId = require('peer-id') const PeerRouting = require('./peer-routing') -const contentRouting = require('./content-routing') +const ContentRouting = require('./content-routing') const getPeer = require('./get-peer') const { validate: validateConfig } = require('./config') const { codes, messages } = require('./errors') @@ -29,22 +30,95 @@ const PubsubAdapter = require('./pubsub-adapter') const PersistentPeerStore = require('./peer-store/persistent') const Registrar = require('./registrar') const ping = require('./ping') -const { - IdentifyService, - multicodecs: IDENTIFY_PROTOCOLS -} = require('./identify') +const IdentifyService = require('./identify') +const IDENTIFY_PROTOCOLS = IdentifyService.multicodecs + +/** + * @typedef {import('multiaddr')} Multiaddr + * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection + * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream + * @typedef {import('libp2p-interfaces/src/transport/types').TransportFactory} TransportFactory + * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxerFactory} MuxerFactory + * @typedef {import('libp2p-interfaces/src/crypto/types').Crypto} Crypto + * @typedef {import('libp2p-interfaces/src/pubsub')} Pubsub + */ /** + * @typedef {Object} PeerStoreOptions + * @property {boolean} persistence + * + * @typedef {Object} PeerDiscoveryOptions + * @property {boolean} autoDial + * + * @typedef {Object} RelayOptions + * @property {boolean} enabled + * @property {import('./circuit').RelayAdvertiseOptions} advertise + * @property {import('./circuit').HopOptions} hop + * @property {import('./circuit').AutoRelayOptions} autoRelay + * + * @typedef {Object} Libp2pConfig + * @property {Object} [dht] dht module options + * @property {PeerDiscoveryOptions} [peerDiscovery] + * @property {Pubsub} [pubsub] pubsub module options + * @property {RelayOptions} [relay] + * @property {Record} [transport] transport options indexed by transport key + * + * @typedef {Object} Libp2pModules + * @property {TransportFactory[]} transport + * @property {MuxerFactory[]} streamMuxer + * @property {Crypto[]} connEncryption + * + * @typedef {Object} Libp2pOptions + * @property {Libp2pModules} modules libp2p modules to use + * @property {import('./address-manager').AddressManagerOptions} [addresses] + * @property {import('./connection-manager').ConnectionManagerOptions} [connectionManager] + * @property {import('./dialer').DialerOptions} [dialer] + * @property {import('./metrics').MetricsOptions} [metrics] + * @property {Object} [keychain] + * @property {import('./transport-manager').TransportManagerOptions} [transportManager] + * @property {PeerStoreOptions & import('./peer-store/persistent').PersistentPeerStoreOptions} [peerStore] + * @property {Libp2pConfig} [config] + * @property {PeerId} peerId + * + * @typedef {Object} CreateOptions + * @property {PeerId} peerId + * + * @extends {EventEmitter} * @fires Libp2p#error Emitted when an error occurs * @fires Libp2p#peer:discovery Emitted when a peer is discovered */ class Libp2p extends EventEmitter { + /** + * Like `new Libp2p(options)` except it will create a `PeerId` + * instance if one is not provided in options. + * + * @param {Libp2pOptions & CreateOptions} options - Libp2p configuration options + * @returns {Promise} + */ + static async create (options) { + if (options.peerId) { + return new Libp2p(options) + } + + const peerId = await PeerId.create() + + options.peerId = peerId + return new Libp2p(options) + } + + /** + * Libp2p node. + * + * @class + * @param {Libp2pOptions} _options + */ constructor (_options) { super() // validateConfig will ensure the config is correct, // and add default values where appropriate this._options = validateConfig(_options) + /** @type {PeerId} */ this.peerId = this._options.peerId this.datastore = this._options.datastore @@ -147,6 +221,7 @@ class Libp2p extends EventEmitter { }) if (this._config.relay.enabled) { + // @ts-ignore Circuit prototype this.transportManager.add(Circuit.prototype[Symbol.toStringTag], Circuit) this.relay = new Relay(this) } @@ -188,13 +263,14 @@ class Libp2p extends EventEmitter { if (this._modules.pubsub) { const Pubsub = this._modules.pubsub // using pubsub adapter with *DEPRECATED* handlers functionality + /** @type {Pubsub} */ this.pubsub = PubsubAdapter(Pubsub, this, this._config.pubsub) } // Attach remaining APIs // peer and content routing will automatically get modules from _modules and _dht this.peerRouting = new PeerRouting(this) - this.contentRouting = contentRouting(this) + this.contentRouting = new ContentRouting(this) // Mount default protocols ping.mount(this) @@ -208,13 +284,16 @@ class Libp2p extends EventEmitter { * * @param {string} eventName * @param {...any} args - * @returns {void} + * @returns {boolean} */ emit (eventName, ...args) { + // TODO: do we still need this? + // @ts-ignore _events does not exist in libp2p if (eventName === 'error' && !this._events.error) { - log.error(...args) + log.error(args) + return false } else { - super.emit(eventName, ...args) + return super.emit(eventName, ...args) } } @@ -288,9 +367,13 @@ class Libp2p extends EventEmitter { * Imports the private key as 'self', if needed. * * @async - * @returns {void} + * @returns {Promise} */ async loadKeychain () { + if (!this.keychain) { + return + } + try { await this.keychain.findKeyByName('self') } catch (err) { @@ -317,12 +400,12 @@ class Libp2p extends EventEmitter { * peer will be added to the nodes `peerStore` * * @param {PeerId|Multiaddr|string} peer - The peer to dial - * @param {object} options + * @param {object} [options] * @param {AbortSignal} [options.signal] * @returns {Promise} */ dial (peer, options) { - return this.dialProtocol(peer, null, options) + return this.dialProtocol(peer, [], options) } /** @@ -333,7 +416,7 @@ class Libp2p extends EventEmitter { * @async * @param {PeerId|Multiaddr|string} peer - The peer to dial * @param {string[]|string} protocols - * @param {object} options + * @param {object} [options] * @param {AbortSignal} [options.signal] * @returns {Promise} */ @@ -353,7 +436,7 @@ class Libp2p extends EventEmitter { } // If a protocol was provided, create a new stream - if (protocols) { + if (protocols && protocols.length) { return connection.newStream(protocols) } @@ -365,7 +448,7 @@ class Libp2p extends EventEmitter { * by transports to listen with the announce addresses. * Duplicated addresses and noAnnounce addresses are filtered out. * - * @returns {Array} + * @returns {Multiaddr[]} */ get multiaddrs () { const announceAddrs = this.addressManager.getAnnounceAddrs() @@ -382,7 +465,7 @@ 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} */ async hangUp (peer) { @@ -422,7 +505,7 @@ class Libp2p extends EventEmitter { * Registers the `handler` for each protocol * * @param {string[]|string} protocols - * @param {function({ connection:*, stream:*, protocol:string })} handler + * @param {({ connection: Connection, stream: MuxedStream, protocol: string }) => void} handler */ handle (protocols, handler) { protocols = Array.isArray(protocols) ? protocols : [protocols] @@ -510,7 +593,7 @@ class Libp2p extends EventEmitter { * Known peers may be emitted. * * @private - * @param {{ id: PeerId, multiaddrs: Array, protocols: Array }} peer + * @param {{ id: PeerId, multiaddrs: Multiaddr[], protocols: string[] }} peer */ _onDiscoveryPeer (peer) { if (peer.id.toB58String() === this.peerId.toB58String()) { @@ -588,7 +671,9 @@ class Libp2p extends EventEmitter { // Transport modules with discovery for (const Transport of this.transportManager.getTransports()) { + // @ts-ignore Transport interface does not include discovery if (Transport.discovery) { + // @ts-ignore Transport interface does not include discovery setupService(Transport.discovery) } } @@ -597,22 +682,4 @@ 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 - * @returns {Promise} - */ -Libp2p.create = async function create (options = {}) { - if (options.peerId) { - return new Libp2p(options) - } - - const peerId = await PeerId.create() - - options.peerId = peerId - return new Libp2p(options) -} - module.exports = Libp2p diff --git a/src/insecure/plaintext.js b/src/insecure/plaintext.js index 83e1ba463b..07efe9f758 100644 --- a/src/insecure/plaintext.js +++ b/src/insecure/plaintext.js @@ -1,21 +1,33 @@ 'use strict' +const debug = require('debug') +const log = Object.assign(debug('libp2p:plaintext'), { + error: debug('libp2p:plaintext:err') +}) const handshake = require('it-handshake') const lp = require('it-length-prefixed') const PeerId = require('peer-id') -const debug = require('debug') -const log = debug('libp2p:plaintext') -log.error = debug('libp2p:plaintext:error') const { UnexpectedPeerError, InvalidCryptoExchangeError } = require('libp2p-interfaces/src/crypto/errors') const { Exchange, KeyType } = require('./proto') const protocol = '/plaintext/2.0.0' +/** + * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection + */ + function lpEncodeExchange (exchange) { const pb = Exchange.encode(exchange) return lp.encode.single(pb) } +/** + * Encrypt connection. + * + * @param {PeerId} localId + * @param {Connection} conn + * @param {PeerId} [remoteId] + */ async function encrypt (localId, conn, remoteId) { const shake = handshake(conn) diff --git a/src/keychain/cms.js b/src/keychain/cms.js index 60bfd323f7..3ba99cfb5b 100644 --- a/src/keychain/cms.js +++ b/src/keychain/cms.js @@ -1,3 +1,4 @@ +// @ts-nocheck 'use strict' require('node-forge/lib/pkcs7') @@ -21,7 +22,7 @@ class CMS { /** * Creates a new instance with a keychain * - * @param {Keychain} keychain - the available keys + * @param {import('./index')} keychain - the available keys */ constructor (keychain) { if (!keychain) { @@ -38,7 +39,7 @@ class CMS { * * @param {string} name - The local key name. * @param {Uint8Array} plain - The data to encrypt. - * @returns {undefined} + * @returns {Promise} */ async encrypt (name, plain) { if (!(plain instanceof Uint8Array)) { @@ -68,7 +69,7 @@ class CMS { * exists, an Error is returned with the property 'missingKeys'. It is array of key ids. * * @param {Uint8Array} cmsData - The CMS encrypted data to decrypt. - * @returns {undefined} + * @returns {Promise} */ async decrypt (cmsData) { if (!(cmsData instanceof Uint8Array)) { diff --git a/src/keychain/index.js b/src/keychain/index.js index c823eb3e46..440a2913a5 100644 --- a/src/keychain/index.js +++ b/src/keychain/index.js @@ -1,3 +1,4 @@ +// @ts-nocheck /* eslint max-nested-callbacks: ["error", 5] */ 'use strict' @@ -101,7 +102,8 @@ class Keychain { * Creates a new instance of a key chain. * * @param {DS} store - where the key are. - * @param {object} options - ??? + * @param {object} options + * @class */ constructor (store, options) { if (!store) { diff --git a/src/keychain/util.js b/src/keychain/util.js index 56386fe488..6a332c9ceb 100644 --- a/src/keychain/util.js +++ b/src/keychain/util.js @@ -1,3 +1,4 @@ +// @ts-nocheck 'use strict' require('node-forge/lib/x509') diff --git a/src/metrics/index.js b/src/metrics/index.js index 9d0f436041..8d94861d81 100644 --- a/src/metrics/index.js +++ b/src/metrics/index.js @@ -1,7 +1,8 @@ +// @ts-nocheck 'use strict' const mergeOptions = require('merge-options') -const pipe = require('it-pipe') +const { pipe } = require('it-pipe') const { tap } = require('streaming-iterables') const oldPeerLRU = require('./old-peers') const { METRICS: defaultOptions } = require('../constants') @@ -17,15 +18,26 @@ const directionToEvent = { out: 'dataSent' } +/** + * @typedef {import('peer-id')} PeerId + * @typedef {import('libp2p-interfaces/src/transport/types').MultiaddrConnection} MultiaddrConnection + */ + +/** + * @typedef MetricsProperties + * @property {import('../connection-manager')} connectionManager + * + * @typedef MetricsOptions + * @property {number} [computeThrottleMaxQueueSize = defaultOptions.computeThrottleMaxQueueSize] + * @property {number} [computeThrottleTimeout = defaultOptions.computeThrottleTimeout] + * @property {number[]} [movingAverageIntervals = defaultOptions.movingAverageIntervals] + * @property {number} [maxOldPeersRetention = defaultOptions.maxOldPeersRetention] + */ + class Metrics { /** - * - * @param {object} options - * @param {ConnectionManager} options.connectionManager - * @param {number} options.computeThrottleMaxQueueSize - * @param {number} options.computeThrottleTimeout - * @param {Array} options.movingAverageIntervals - * @param {number} options.maxOldPeersRetention + * @class + * @param {MetricsProperties & MetricsOptions} options */ constructor (options) { this._options = mergeOptions(defaultOptions, options) @@ -76,7 +88,7 @@ class Metrics { /** * Returns a list of `PeerId` strings currently being tracked * - * @returns {Array} + * @returns {string[]} */ get peers () { return Array.from(this._peerStats.keys()) @@ -97,7 +109,7 @@ class Metrics { /** * Returns a list of all protocol strings currently being tracked. * - * @returns {Array} + * @returns {string[]} */ get protocols () { return Array.from(this._protocolStats.keys()) @@ -176,6 +188,7 @@ class Metrics { * * @param {PeerId} placeholder - A peerId string * @param {PeerId} peerId + * @returns {void} */ updatePlaceholder (placeholder, peerId) { if (!this._running) return @@ -205,10 +218,10 @@ 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 {MultiaddrConnection} 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 + * @returns {MultiaddrConnection} The peerId string or placeholder string */ trackStream ({ stream, remotePeer, protocol }) { const metrics = this diff --git a/src/metrics/old-peers.js b/src/metrics/old-peers.js index 08d317dc09..753bdf5fa1 100644 --- a/src/metrics/old-peers.js +++ b/src/metrics/old-peers.js @@ -6,9 +6,10 @@ const LRU = require('hashlru') * Creates and returns a Least Recently Used Cache * * @param {number} maxSize - * @returns {LRUCache} + * @returns {any} */ module.exports = (maxSize) => { + // @ts-ignore LRU expression is not callable const patched = LRU(maxSize) patched.delete = patched.remove return patched diff --git a/src/metrics/stats.js b/src/metrics/stats.js index 3517766309..e35ab311fc 100644 --- a/src/metrics/stats.js +++ b/src/metrics/stats.js @@ -1,17 +1,19 @@ +// @ts-nocheck 'use strict' -const EventEmitter = require('events') +const { EventEmitter } = require('events') const Big = require('bignumber.js') const MovingAverage = require('moving-average') const retimer = require('retimer') -/** - * A queue based manager for stat processing - * - * @param {Array} initialCounters - * @param {any} options - */ class Stats extends EventEmitter { + /** + * A queue based manager for stat processing + * + * @class + * @param {string[]} initialCounters + * @param {any} options + */ constructor (initialCounters, options) { super() @@ -21,6 +23,7 @@ class Stats extends EventEmitter { this._frequencyLastTime = Date.now() this._frequencyAccumulators = {} + this._movingAverages = {} this._update = this._update.bind(this) @@ -68,7 +71,7 @@ class Stats extends EventEmitter { /** * Returns a clone of the current stats. * - * @returns {Map} + * @returns {Object} */ get snapshot () { return Object.assign({}, this._stats) @@ -77,7 +80,7 @@ class Stats extends EventEmitter { /** * Returns a clone of the internal movingAverages * - * @returns {Array} + * @returns {MovingAverage} */ get movingAverages () { return Object.assign({}, this._movingAverages) @@ -229,7 +232,7 @@ class Stats extends EventEmitter { * will be updated or initialized if they don't already exist. * * @private - * @param {Array} op + * @param {{string, number}[]} op * @throws {InvalidNumber} * @returns {void} */ @@ -238,7 +241,7 @@ class Stats extends EventEmitter { const inc = op[1] if (typeof inc !== 'number') { - throw new Error('invalid increment number:', inc) + throw new Error(`invalid increment number: ${inc}`) } let n diff --git a/src/peer-routing.js b/src/peer-routing.js index e783c82f8b..26fe9625b4 100644 --- a/src/peer-routing.js +++ b/src/peer-routing.js @@ -1,9 +1,10 @@ 'use strict' -const errCode = require('err-code') const debug = require('debug') -const log = debug('libp2p:peer-routing') -log.error = debug('libp2p:peer-routing:error') +const log = Object.assign(debug('libp2p:peer-routing'), { + error: debug('libp2p:peer-routing:err') +}) +const errCode = require('err-code') const all = require('it-all') const pAny = require('p-any') @@ -13,12 +14,13 @@ const { } = require('set-delayed-interval') /** - * Responsible for managing the usage of the available Peer Routing modules. + * @typedef {import('peer-id')} PeerId + * @typedef {import('multiaddr')} Multiaddr */ class PeerRouting { /** * @class - * @param {Libp2p} libp2p + * @param {import('./')} libp2p */ constructor (libp2p) { this._peerId = libp2p.peerId diff --git a/src/peer-store/address-book.js b/src/peer-store/address-book.js index 07d8af59b2..74b6049a5c 100644 --- a/src/peer-store/address-book.js +++ b/src/peer-store/address-book.js @@ -1,9 +1,10 @@ 'use strict' -const errcode = require('err-code') const debug = require('debug') -const log = debug('libp2p:peer-store:address-book') -log.error = debug('libp2p:peer-store:address-book:error') +const log = Object.assign(debug('libp2p:peer-store:address-book'), { + error: debug('libp2p:peer-store:address-book:err') +}) +const errcode = require('err-code') const multiaddr = require('multiaddr') const PeerId = require('peer-id') @@ -17,35 +18,31 @@ const { const Envelope = require('../record/envelope') /** - * The AddressBook is responsible for keeping the known multiaddrs - * of a peer. + * @typedef {import('multiaddr')} Multiaddr + * @typedef {import('./')} PeerStore */ -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. - */ +/** + * @typedef {Object} Address + * @property {Multiaddr} multiaddr peer multiaddr. + * @property {boolean} isCertified obtained from a signed peer record. + * + * @typedef {Object} CertifiedRecord + * @property {Uint8Array} raw raw envelope. + * @property {number} seqNumber seq counter. + * + * @typedef {Object} Entry + * @property {Address[]} addresses peer Addresses. + * @property {CertifiedRecord} record certified peer record. + */ +/** + * @extends {Book} + */ +class AddressBook extends Book { /** - * Entry object for the addressBook + * The AddressBook is responsible for keeping the known multiaddrs of a peer. * - * @typedef {Object} Entry - * @property {Array
} addresses peer Addresses. - * @property {CertifiedRecord} record certified peer record. - */ - - /** * @class * @param {PeerStore} peerStore */ @@ -70,7 +67,7 @@ class AddressBook extends Book { /** * Map known peers to their known Address Entries. * - * @type {Map>} + * @type {Map} */ this.data = new Map() } @@ -105,7 +102,7 @@ class AddressBook extends Book { const peerId = peerRecord.peerId const id = peerId.toB58String() - const entry = this.data.get(id) || {} + const entry = this.data.get(id) || { record: undefined } const storedRecord = entry.record // ensure seq is greater than, or equal to, the last received @@ -151,7 +148,7 @@ class AddressBook extends Book { * Returns undefined if no record exists. * * @param {PeerId} peerId - * @returns {Promise} + * @returns {Promise|undefined} */ getPeerRecord (peerId) { const raw = this.getRawEnvelope(peerId) @@ -171,7 +168,7 @@ class AddressBook extends Book { * * @override * @param {PeerId} peerId - * @param {Array} multiaddrs + * @param {Multiaddr[]} multiaddrs * @returns {AddressBook} */ set (peerId, multiaddrs) { @@ -181,22 +178,22 @@ class AddressBook extends Book { } const addresses = this._toAddresses(multiaddrs) - const id = peerId.toB58String() - const entry = this.data.get(id) || {} - const rec = entry.addresses // Not replace multiaddrs if (!addresses.length) { return this } + const id = peerId.toB58String() + const entry = this.data.get(id) + // Already knows the peer - if (rec && rec.length === addresses.length) { - const intersection = rec.filter((addr) => addresses.some((newAddr) => addr.multiaddr.equals(newAddr.multiaddr))) + if (entry && entry.addresses && entry.addresses.length === addresses.length) { + const intersection = entry.addresses.filter((addr) => addresses.some((newAddr) => addr.multiaddr.equals(newAddr.multiaddr))) // Are new addresses equal to the old ones? // If yes, no changes needed! - if (intersection.length === rec.length) { + if (intersection.length === entry.addresses.length) { log(`the addresses provided to store are equal to the already stored for ${id}`) return this } @@ -204,12 +201,12 @@ class AddressBook extends Book { this._setData(peerId, { addresses, - record: entry.record + record: entry && entry.record }) log(`stored provided multiaddrs for ${id}`) // Notify the existance of a new peer - if (!rec) { + if (!entry) { this._ps.emit('peer', peerId) } @@ -221,7 +218,7 @@ class AddressBook extends Book { * If the peer is not known, it is set with the given addresses. * * @param {PeerId} peerId - * @param {Array} multiaddrs + * @param {Multiaddr[]} multiaddrs * @returns {AddressBook} */ add (peerId, multiaddrs) { @@ -233,32 +230,33 @@ class AddressBook extends Book { const addresses = this._toAddresses(multiaddrs) const id = peerId.toB58String() - const entry = this.data.get(id) || {} - const rec = entry.addresses || [] + const entry = this.data.get(id) - // Add recorded uniquely to the new array (Union) - rec.forEach((addr) => { - if (!addresses.find(r => r.multiaddr.equals(addr.multiaddr))) { - addresses.push(addr) - } - }) + if (entry && entry.addresses) { + // Add recorded uniquely to the new array (Union) + entry.addresses.forEach((addr) => { + if (!addresses.find(r => r.multiaddr.equals(addr.multiaddr))) { + addresses.push(addr) + } + }) - // If the recorded length is equal to the new after the unique union - // The content is the same, no need to update. - if (rec && rec.length === addresses.length) { - log(`the addresses provided to store are already stored for ${id}`) - return this + // If the recorded length is equal to the new after the unique union + // The content is the same, no need to update. + if (entry.addresses.length === addresses.length) { + log(`the addresses provided to store are already stored for ${id}`) + return this + } } this._setData(peerId, { addresses, - record: entry.record + record: entry && entry.record }) log(`added provided multiaddrs for ${id}`) // Notify the existance of a new peer - if (!entry.addresses) { + if (!(entry && entry.addresses)) { this._ps.emit('peer', peerId) } @@ -270,7 +268,7 @@ class AddressBook extends Book { * * @override * @param {PeerId} peerId - * @returns {Array
|undefined} + * @returns {Address[]|undefined} */ get (peerId) { if (!PeerId.isPeerId(peerId)) { @@ -286,9 +284,9 @@ class AddressBook extends Book { * Transforms received multiaddrs into Address. * * @private - * @param {Array} multiaddrs + * @param {Multiaddr[]} multiaddrs * @param {boolean} [isCertified] - * @returns {Array
} + * @returns {Address[]} */ _toAddresses (multiaddrs, isCertified = false) { if (!multiaddrs) { @@ -319,8 +317,8 @@ class AddressBook extends Book { * Returns `undefined` if there are no known multiaddrs for the given peer. * * @param {PeerId} peerId - * @param {(addresses: Array Array
} [addressSorter] - * @returns {Array|undefined} + * @param {(addresses: Address[]) => Address[]} [addressSorter] + * @returns {Multiaddr[]|undefined} */ getMultiaddrsForPeer (peerId, addressSorter = (ms) => ms) { if (!PeerId.isPeerId(peerId)) { diff --git a/src/peer-store/book.js b/src/peer-store/book.js index f0a830f97a..48855c157b 100644 --- a/src/peer-store/book.js +++ b/src/peer-store/book.js @@ -10,16 +10,19 @@ const { const passthrough = data => data /** - * The Book is the skeleton for the PeerStore books. + * @typedef {import('./')} PeerStore */ + class Book { /** + * The Book is the skeleton for the PeerStore books. + * * @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 {(data: any) => any[]} [properties.eventTransformer] - Transformer function of the provided data for being emitted. */ constructor ({ peerStore, eventName, eventProperty, eventTransformer = passthrough }) { this._ps = peerStore @@ -30,7 +33,7 @@ class Book { /** * Map known peers to their data. * - * @type {Map} + * @type {Map} */ this.data = new Map() } @@ -39,7 +42,7 @@ class Book { * Set known data of a provided peer. * * @param {PeerId} peerId - * @param {Array|Data} data + * @param {any[]|any} data */ set (peerId, data) { throw errcode(new Error('set must be implemented by the subclass'), 'ERR_NOT_IMPLEMENTED') @@ -48,9 +51,9 @@ class Book { /** * Set data into the datastructure, persistence and emit it using the provided transformers. * - * @private + * @protected * @param {PeerId} peerId - peerId of the data to store - * @param {*} data - data to store. + * @param {any} data - data to store. * @param {Object} [options] - storing options. * @param {boolean} [options.emit = true] - emit the provided data. * @returns {void} @@ -68,9 +71,9 @@ class Book { /** * Emit data. * - * @private + * @protected * @param {PeerId} peerId - * @param {*} data + * @param {any} [data] */ _emit (peerId, data) { this._ps.emit(this.eventName, { @@ -84,7 +87,7 @@ class Book { * Returns `undefined` if there is no available data for the given peer. * * @param {PeerId} peerId - * @returns {Array|undefined} + * @returns {any[]|any|undefined} */ get (peerId) { if (!PeerId.isPeerId(peerId)) { @@ -93,6 +96,7 @@ class Book { const rec = this.data.get(peerId.toB58String()) + // @ts-ignore return rec ? [...rec] : undefined } diff --git a/src/peer-store/index.js b/src/peer-store/index.js index 69a1f15a57..b3df1bbb94 100644 --- a/src/peer-store/index.js +++ b/src/peer-store/index.js @@ -1,9 +1,6 @@ 'use strict' const errcode = require('err-code') -const debug = require('debug') -const log = debug('libp2p:peer-store') -log.error = debug('libp2p:peer-store:error') const { EventEmitter } = require('events') const PeerId = require('peer-id') @@ -14,11 +11,15 @@ const MetadataBook = require('./metadata-book') const ProtoBook = require('./proto-book') const { - ERR_INVALID_PARAMETERS + codes: { ERR_INVALID_PARAMETERS } } = require('../errors') /** - * Responsible for managing known peers, as well as their addresses, protocols and metadata. + * @typedef {import('./address-book').Address} Address + */ + +/** + * @extends {EventEmitter} * * @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. @@ -32,12 +33,14 @@ class PeerStore extends EventEmitter { * * @typedef {Object} Peer * @property {PeerId} id peer's peer-id instance. - * @property {Array
} addresses peer's addresses containing its multiaddrs and metadata. - * @property {Array} protocols peer's supported protocols. - * @property {Map} metadata peer's metadata map. + * @property {Address[]} addresses peer's addresses containing its multiaddrs and metadata. + * @property {string[]} protocols peer's supported protocols. + * @property {Map|undefined} metadata peer's metadata map. */ /** + * Responsible for managing known peers, as well as their addresses, protocols and metadata. + * * @param {object} options * @param {PeerId} options.peerId * @class @@ -121,7 +124,7 @@ class PeerStore extends EventEmitter { * Get the stored information of a given peer. * * @param {PeerId} peerId - * @returns {Peer} + * @returns {Peer|undefined} */ get (peerId) { if (!PeerId.isPeerId(peerId)) { diff --git a/src/peer-store/key-book.js b/src/peer-store/key-book.js index 607d12795f..356c81866e 100644 --- a/src/peer-store/key-book.js +++ b/src/peer-store/key-book.js @@ -1,9 +1,10 @@ 'use strict' -const errcode = require('err-code') const debug = require('debug') -const log = debug('libp2p:peer-store:key-book') -log.error = debug('libp2p:peer-store:key-book:error') +const log = Object.assign(debug('libp2p:peer-store:key-book'), { + error: debug('libp2p:peer-store:key-book:err') +}) +const errcode = require('err-code') const PeerId = require('peer-id') @@ -14,10 +15,17 @@ const { } = require('../errors') /** - * The KeyBook is responsible for keeping the known public keys of a peer. + * @typedef {import('./')} PeerStore + * @typedef {import('libp2p-crypto').PublicKey} PublicKey + */ + +/** + * @extends {Book} */ class KeyBook extends Book { /** + * The KeyBook is responsible for keeping the known public keys of a peer. + * * @class * @param {PeerStore} peerStore */ @@ -42,7 +50,7 @@ class KeyBook extends Book { * * @override * @param {PeerId} peerId - * @param {RsaPublicKey|Ed25519PublicKey|Secp256k1PublicKey} publicKey + * @param {PublicKey} publicKey * @returns {KeyBook} */ set (peerId, publicKey) { @@ -72,7 +80,7 @@ class KeyBook extends Book { * * @override * @param {PeerId} peerId - * @returns {RsaPublicKey|Ed25519PublicKey|Secp256k1PublicKey} + * @returns {PublicKey | undefined} */ get (peerId) { if (!PeerId.isPeerId(peerId)) { diff --git a/src/peer-store/metadata-book.js b/src/peer-store/metadata-book.js index 490ef02b09..d497bb2f04 100644 --- a/src/peer-store/metadata-book.js +++ b/src/peer-store/metadata-book.js @@ -1,9 +1,10 @@ 'use strict' -const errcode = require('err-code') const debug = require('debug') -const log = debug('libp2p:peer-store:proto-book') -log.error = debug('libp2p:peer-store:proto-book:error') +const log = Object.assign(debug('libp2p:peer-store:proto-book'), { + error: debug('libp2p:peer-store:proto-book:err') +}) +const errcode = require('err-code') const uint8ArrayEquals = require('uint8arrays/equals') const PeerId = require('peer-id') @@ -15,13 +16,19 @@ const { } = require('../errors') /** - * The MetadataBook is responsible for keeping the known supported - * protocols of a peer. + * @typedef {import('./')} PeerStore + */ + +/** + * @extends {Book} * * @fires MetadataBook#change:metadata */ class MetadataBook extends Book { /** + * The MetadataBook is responsible for keeping the known supported + * protocols of a peer. + * * @class * @param {PeerStore} peerStore */ @@ -51,8 +58,9 @@ class MetadataBook extends Book { * @param {PeerId} peerId * @param {string} key - metadata key * @param {Uint8Array} value - metadata value - * @returns {ProtoBook} + * @returns {MetadataBook} */ + // @ts-ignore override with more then the parameters expected in Book set (peerId, key, value) { if (!PeerId.isPeerId(peerId)) { log.error('peerId must be an instance of peer-id to store data') @@ -95,7 +103,7 @@ class MetadataBook extends Book { * Get the known data of a provided peer. * * @param {PeerId} peerId - * @returns {Map} + * @returns {Map|undefined} */ get (peerId) { if (!PeerId.isPeerId(peerId)) { @@ -110,7 +118,7 @@ class MetadataBook extends Book { * * @param {PeerId} peerId * @param {string} key - * @returns {Uint8Array} + * @returns {Uint8Array | undefined} */ getValue (peerId, key) { if (!PeerId.isPeerId(peerId)) { diff --git a/src/peer-store/persistent/index.js b/src/peer-store/persistent/index.js index 70a417f4f7..bbab49657e 100644 --- a/src/peer-store/persistent/index.js +++ b/src/peer-store/persistent/index.js @@ -1,9 +1,9 @@ 'use strict' const debug = require('debug') -const log = debug('libp2p:persistent-peer-store') -log.error = debug('libp2p:persistent-peer-store:error') - +const log = Object.assign(debug('libp2p:persistent-peer-store'), { + error: debug('libp2p:persistent-peer-store:err') +}) const { Key } = require('interface-datastore') const multiaddr = require('multiaddr') const PeerId = require('peer-id') @@ -21,16 +21,22 @@ const { const Addresses = require('./pb/address-book.proto') const Protocols = require('./pb/proto-book.proto') +/** + * @typedef {Object} PersistentPeerStoreProperties + * @property {PeerId} peerId + * @property {any} datastore + * + * @typedef {Object} PersistentPeerStoreOptions + * @property {number} [threshold = 5] - Number of dirty peers allowed before commit data. + */ + /** * Responsible for managing the persistence of data in the PeerStore. */ class PersistentPeerStore extends PeerStore { /** * @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 {PersistentPeerStoreProperties & PersistentPeerStoreOptions} properties */ constructor ({ peerId, datastore, threshold = 5 }) { super({ peerId }) @@ -340,6 +346,7 @@ class PersistentPeerStore extends PeerStore { case 'addrs': decoded = Addresses.decode(value) + // @ts-ignore protected function this.addressBook._setData( peerId, { @@ -357,6 +364,7 @@ class PersistentPeerStore extends PeerStore { case 'keys': decoded = await PeerId.createFromPubKey(value) + // @ts-ignore protected function this.keyBook._setData( decoded, decoded, @@ -372,6 +380,7 @@ class PersistentPeerStore extends PeerStore { case 'protos': decoded = Protocols.decode(value) + // @ts-ignore protected function this.protoBook._setData( peerId, new Set(decoded.protocols), diff --git a/src/peer-store/proto-book.js b/src/peer-store/proto-book.js index a08f5a284d..5c17b1371a 100644 --- a/src/peer-store/proto-book.js +++ b/src/peer-store/proto-book.js @@ -1,10 +1,10 @@ 'use strict' -const errcode = require('err-code') const debug = require('debug') -const log = debug('libp2p:peer-store:proto-book') -log.error = debug('libp2p:peer-store:proto-book:error') - +const log = Object.assign(debug('libp2p:peer-store:proto-book'), { + error: debug('libp2p:peer-store:proto-book:err') +}) +const errcode = require('err-code') const PeerId = require('peer-id') const Book = require('./book') @@ -14,13 +14,19 @@ const { } = require('../errors') /** - * The ProtoBook is responsible for keeping the known supported - * protocols of a peer. + * @typedef {import('./')} PeerStore + */ + +/** + * @extends {Book} * * @fires ProtoBook#change:protocols */ class ProtoBook extends Book { /** + * The ProtoBook is responsible for keeping the known supported + * protocols of a peer. + * * @class * @param {PeerStore} peerStore */ @@ -50,7 +56,7 @@ class ProtoBook extends Book { * * @override * @param {PeerId} peerId - * @param {Array} protocols + * @param {string[]} protocols * @returns {ProtoBook} */ set (peerId, protocols) { @@ -88,7 +94,7 @@ class ProtoBook extends Book { * If the peer was not known before, it will be added. * * @param {PeerId} peerId - * @param {Array} protocols + * @param {string[]} protocols * @returns {ProtoBook} */ add (peerId, protocols) { @@ -123,7 +129,7 @@ class ProtoBook extends Book { * If the protocols did not exist before, nothing will be done. * * @param {PeerId} peerId - * @param {Array} protocols + * @param {string[]} protocols * @returns {ProtoBook} */ remove (peerId, protocols) { diff --git a/src/ping/index.js b/src/ping/index.js index e882f81f4c..eb8d7b96e9 100644 --- a/src/ping/index.js +++ b/src/ping/index.js @@ -1,30 +1,39 @@ 'use strict' const debug = require('debug') -const log = debug('libp2p-ping') -log.error = debug('libp2p-ping:error') +const log = Object.assign(debug('libp2p:ping'), { + error: debug('libp2p:ping:err') +}) const errCode = require('err-code') const crypto = require('libp2p-crypto') -const pipe = require('it-pipe') +const { pipe } = require('it-pipe') const { toBuffer } = require('it-buffer') const { collect, take } = require('streaming-iterables') +const equals = require('uint8arrays/equals') const { PROTOCOL, PING_LENGTH } = require('./constants') +/** + * @typedef {import('../')} Libp2p + * @typedef {import('multiaddr')} Multiaddr + * @typedef {import('peer-id')} PeerId + */ + /** * Ping a given peer and wait for its response, getting the operation latency. * * @param {Libp2p} node - * @param {PeerId|multiaddr} peer + * @param {PeerId|Multiaddr} peer * @returns {Promise} */ async function ping (node, peer) { + // @ts-ignore multiaddr might not have toB58String log('dialing %s to %s', PROTOCOL, peer.toB58String ? peer.toB58String() : peer) const { stream } = await node.dialProtocol(peer, PROTOCOL) - const start = new Date() + const start = Date.now() const data = crypto.randomBytes(PING_LENGTH) const [result] = await pipe( @@ -36,7 +45,7 @@ async function ping (node, peer) { ) const end = Date.now() - if (!data.equals(result)) { + if (!equals(data, result)) { throw errCode(new Error('Received wrong ping ack'), 'ERR_WRONG_PING_ACK') } diff --git a/src/pnet/crypto.js b/src/pnet/crypto.js index ae824bfcfb..9cfcbc8e6f 100644 --- a/src/pnet/crypto.js +++ b/src/pnet/crypto.js @@ -1,16 +1,17 @@ 'use strict' const debug = require('debug') +const log = Object.assign(debug('libp2p:pnet'), { + trace: debug('libp2p:pnet:trace'), + error: debug('libp2p:pnet:err') +}) + const Errors = require('./errors') const xsalsa20 = require('xsalsa20') const KEY_LENGTH = require('./key-generator').KEY_LENGTH const uint8ArrayFromString = require('uint8arrays/from-string') const uint8ArrayToString = require('uint8arrays/to-string') -const log = debug('libp2p:pnet') -log.trace = debug('libp2p:pnet:trace') -log.error = debug('libp2p:pnet:err') - /** * Creates a stream iterable to encrypt messages in a private network * diff --git a/src/pnet/index.js b/src/pnet/index.js index e5f82331a2..194a5005ec 100644 --- a/src/pnet/index.js +++ b/src/pnet/index.js @@ -1,12 +1,16 @@ 'use strict' -const pipe = require('it-pipe') +const debug = require('debug') +const log = Object.assign(debug('libp2p:pnet'), { + error: debug('libp2p:pnet:err') +}) +const { pipe } = require('it-pipe') const errcode = require('err-code') const duplexPair = require('it-pair/duplex') const crypto = require('libp2p-crypto') const Errors = require('./errors') const { - ERR_INVALID_PARAMETERS + codes: { ERR_INVALID_PARAMETERS } } = require('../errors') const { createBoxStream, @@ -15,16 +19,16 @@ const { } = require('./crypto') const handshake = require('it-handshake') const { NONCE_LENGTH } = require('./key-generator') -const debug = require('debug') -const log = debug('libp2p:pnet') -log.error = debug('libp2p:pnet:err') /** - * Takes a Private Shared Key (psk) and provides a `protect` method - * for wrapping existing connections in a private encryption stream + * @typedef {import('libp2p-interfaces/src/transport/types').MultiaddrConnection} MultiaddrConnection */ + class Protector { /** + * Takes a Private Shared Key (psk) and provides a `protect` method + * for wrapping existing connections in a private encryption stream. + * * @param {Uint8Array} keyBuffer - The private shared key buffer * @class */ @@ -39,8 +43,8 @@ class Protector { * between its two peers from the PSK the Protector instance was * created with. * - * @param {Connection} connection - The connection to protect - * @returns {*} A protected duplex iterable + * @param {MultiaddrConnection} connection - The connection to protect + * @returns {Promise} A protected duplex iterable */ async protect (connection) { if (!connection) { diff --git a/src/pnet/key-generator.js b/src/pnet/key-generator.js index b3676dcccf..8a7a1ef5a2 100644 --- a/src/pnet/key-generator.js +++ b/src/pnet/key-generator.js @@ -22,6 +22,8 @@ module.exports = generate module.exports.NONCE_LENGTH = 24 module.exports.KEY_LENGTH = KEY_LENGTH +// @ts-ignore This condition will always return 'false' since the types 'Module | undefined' if (require.main === module) { + // @ts-ignore generate(process.stdout) } diff --git a/src/pubsub-adapter.js b/src/pubsub-adapter.js index 1f42cc7d15..7d7af8df2f 100644 --- a/src/pubsub-adapter.js +++ b/src/pubsub-adapter.js @@ -1,42 +1,54 @@ 'use strict' +/** + * @typedef {import('libp2p-interfaces/src/pubsub').InMessage} InMessage + * @typedef {import('libp2p-interfaces/src/pubsub')} PubsubRouter + */ + // Pubsub adapter to keep API with handlers while not removed. -module.exports = (PubsubRouter, libp2p, options) => { - class Pubsub extends PubsubRouter { - /** - * Subscribes to a given topic. - * - * @override - * @param {string} topic - * @param {function(msg: InMessage)} [handler] - * @returns {void} - */ - subscribe (topic, handler) { - // Bind provided handler - handler && this.on(topic, handler) - super.subscribe(topic) +function pubsubAdapter (PubsubRouter, libp2p, options) { + const pubsub = new PubsubRouter(libp2p, options) + pubsub._subscribeAdapter = pubsub.subscribe + pubsub._unsubscribeAdapter = pubsub.unsubscribe + + /** + * Subscribes to a given topic. + * + * @override + * @param {string} topic + * @param {(msg: InMessage) => void} [handler] + * @returns {void} + */ + function subscribe (topic, handler) { + // Bind provided handler + handler && pubsub.on(topic, handler) + pubsub._subscribeAdapter(topic) + } + + /** + * Unsubscribe from the given topic. + * + * @override + * @param {string} topic + * @param {(msg: InMessage) => void} [handler] + * @returns {void} + */ + function unsubscribe (topic, handler) { + if (!handler) { + pubsub.removeAllListeners(topic) + } else { + pubsub.removeListener(topic, handler) } - /** - * Unsubscribe from the given topic. - * - * @override - * @param {string} topic - * @param {function(msg: InMessage)} [handler] - * @returns {void} - */ - unsubscribe (topic, handler) { - if (!handler) { - this.removeAllListeners(topic) - } else { - this.removeListener(topic, handler) - } - - if (this.listenerCount(topic) === 0) { - super.unsubscribe(topic) - } + if (pubsub.listenerCount(topic) === 0) { + pubsub._unsubscribeAdapter(topic) } } - return new Pubsub(libp2p, options) + pubsub.subscribe = subscribe + pubsub.unsubscribe = unsubscribe + + return pubsub } + +module.exports = pubsubAdapter diff --git a/src/record/envelope/envelope.proto.js b/src/record/envelope/envelope.proto.js index ca0074961a..c8907debda 100644 --- a/src/record/envelope/envelope.proto.js +++ b/src/record/envelope/envelope.proto.js @@ -2,7 +2,8 @@ const protons = require('protons') -const message = ` +/** @type {{Envelope: import('../../types').MessageProto}} */ +module.exports = protons(` message Envelope { // public_key is the public key of the keypair the enclosed payload was // signed with. @@ -20,6 +21,4 @@ message Envelope { // additional security. bytes signature = 5; } -` - -module.exports = protons(message).Envelope +`) diff --git a/src/record/envelope/index.js b/src/record/envelope/index.js index 6a73914f2a..46f9c3ccf6 100644 --- a/src/record/envelope/index.js +++ b/src/record/envelope/index.js @@ -1,8 +1,5 @@ 'use strict' -const debug = require('debug') -const log = debug('libp2p:envelope') -log.error = debug('libp2p:envelope:error') const errCode = require('err-code') const uint8arraysConcat = require('uint8arrays/concat') const uint8arraysFromString = require('uint8arrays/from-string') @@ -15,11 +12,14 @@ const { codes } = require('../../errors') const Protobuf = require('./envelope.proto') /** - * The Envelope is responsible for keeping an arbitrary signed record - * by a libp2p peer. + * @typedef {import('libp2p-interfaces/src/record/types').Record} Record */ + class Envelope { /** + * The Envelope is responsible for keeping an arbitrary signed record + * by a libp2p peer. + * * @class * @param {object} params * @param {PeerId} params.peerId @@ -49,7 +49,7 @@ class Envelope { const publicKey = cryptoKeys.marshalPublicKey(this.peerId.pubKey) - this._marshal = Protobuf.encode({ + this._marshal = Protobuf.Envelope.encode({ public_key: publicKey, payload_type: this.payloadType, payload: this.payload, @@ -102,14 +102,14 @@ const formatSignaturePayload = (domain, payloadType, payload) => { // - The length of the payload field in bytes // - The value of the payload field - domain = uint8arraysFromString(domain) - const domainLength = varint.encode(domain.byteLength) + const domainUint8Array = uint8arraysFromString(domain) + const domainLength = varint.encode(domainUint8Array.byteLength) const payloadTypeLength = varint.encode(payloadType.length) const payloadLength = varint.encode(payload.length) return uint8arraysConcat([ new Uint8Array(domainLength), - domain, + domainUint8Array, new Uint8Array(payloadTypeLength), payloadType, new Uint8Array(payloadLength), @@ -124,7 +124,7 @@ const formatSignaturePayload = (domain, payloadType, payload) => { * @returns {Promise} */ Envelope.createFromProtobuf = async (data) => { - const envelopeData = Protobuf.decode(data) + const envelopeData = Protobuf.Envelope.decode(data) const peerId = await PeerId.createFromPubKey(envelopeData.public_key) return new Envelope({ @@ -142,7 +142,7 @@ Envelope.createFromProtobuf = async (data) => { * @async * @param {Record} record * @param {PeerId} peerId - * @returns {Envelope} + * @returns {Promise} */ Envelope.seal = async (record, peerId) => { const domain = record.domain @@ -166,7 +166,7 @@ Envelope.seal = async (record, peerId) => { * * @param {Uint8Array} data * @param {string} domain - * @returns {Envelope} + * @returns {Promise} */ Envelope.openAndCertify = async (data, domain) => { const envelope = await Envelope.createFromProtobuf(data) diff --git a/src/record/peer-record/index.js b/src/record/peer-record/index.js index 51c43a7970..32d018abc5 100644 --- a/src/record/peer-record/index.js +++ b/src/record/peer-record/index.js @@ -2,7 +2,6 @@ const multiaddr = require('multiaddr') const PeerId = require('peer-id') -const Record = require('libp2p-interfaces/src/record') const arrayEquals = require('libp2p-utils/src/array-equals') const Protobuf = require('./peer-record.proto') @@ -12,19 +11,28 @@ const { } = require('./consts') /** - * The PeerRecord is used for distributing peer routing records across the network. - * It contains the peer's reachable listen addresses. + * @typedef {import('peer-id')} PeerId + * @typedef {import('multiaddr')} Multiaddr + * @typedef {import('libp2p-interfaces/src/record/types').Record} Record */ -class PeerRecord extends Record { + +/** + * @implements {Record} + */ +class PeerRecord { /** + * The PeerRecord is used for distributing peer routing records across the network. + * It contains the peer's reachable listen addresses. + * * @class - * @param {object} params + * @param {Object} params * @param {PeerId} params.peerId - * @param {Array} params.multiaddrs - addresses of the associated peer. + * @param {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) + this.domain = ENVELOPE_DOMAIN_PEER_RECORD + this.codec = ENVELOPE_PAYLOAD_TYPE_PEER_RECORD this.peerId = peerId this.multiaddrs = multiaddrs @@ -44,7 +52,7 @@ class PeerRecord extends Record { return this._marshal } - this._marshal = Protobuf.encode({ + this._marshal = Protobuf.PeerRecord.encode({ peer_id: this.peerId.toBytes(), seq: this.seqNumber, addresses: this.multiaddrs.map((m) => ({ @@ -58,10 +66,14 @@ class PeerRecord extends Record { /** * Returns true if `this` record equals the `other`. * - * @param {Record} other + * @param {unknown} other * @returns {boolean} */ equals (other) { + if (!(other instanceof PeerRecord)) { + return false + } + // Validate PeerId if (!this.peerId.equals(other.peerId)) { return false @@ -89,7 +101,7 @@ class PeerRecord extends Record { */ PeerRecord.createFromProtobuf = (buf) => { // Decode - const peerRecord = Protobuf.decode(buf) + const peerRecord = Protobuf.PeerRecord.decode(buf) const peerId = PeerId.createFromBytes(peerRecord.peer_id) const multiaddrs = (peerRecord.addresses || []).map((a) => multiaddr(a.multiaddr)) diff --git a/src/record/peer-record/peer-record.proto.js b/src/record/peer-record/peer-record.proto.js index 9da916ca87..0ebb3b90d0 100644 --- a/src/record/peer-record/peer-record.proto.js +++ b/src/record/peer-record/peer-record.proto.js @@ -7,7 +7,8 @@ const protons = require('protons') // is expected to expand to include other information in the future. // PeerRecords are designed to be serialized to bytes and placed inside of // SignedEnvelopes before sharing with other peers. -const message = ` +/** @type {{PeerRecord: import('../../types').MessageProto}} */ +module.exports = protons(` message PeerRecord { // AddressInfo is a wrapper around a binary multiaddr. It is defined as a // separate message to allow us to add per-address metadata in the future. @@ -24,6 +25,4 @@ message PeerRecord { // addresses is a list of public listen addresses for the peer. repeated AddressInfo addresses = 3; } -` - -module.exports = protons(message).PeerRecord +`) diff --git a/src/record/utils.js b/src/record/utils.js index 65696156b8..0a92ade177 100644 --- a/src/record/utils.js +++ b/src/record/utils.js @@ -3,10 +3,14 @@ const Envelope = require('./envelope') const PeerRecord = require('./peer-record') +/** + * @typedef {import('../')} Libp2p + */ + /** * Create (or update if existing) self peer record and store it in the AddressBook. * - * @param {libp2p} libp2p + * @param {Libp2p} libp2p * @returns {Promise} */ async function updateSelfPeerRecord (libp2p) { diff --git a/src/registrar.js b/src/registrar.js index 5130a02fcb..367f110c80 100644 --- a/src/registrar.js +++ b/src/registrar.js @@ -1,15 +1,24 @@ 'use strict' const debug = require('debug') +const log = Object.assign(debug('libp2p:peer-store'), { + error: debug('libp2p:peer-store:err') +}) const errcode = require('err-code') -const log = debug('libp2p:peer-store') -log.error = debug('libp2p:peer-store:error') const { - ERR_INVALID_PARAMETERS + codes: { ERR_INVALID_PARAMETERS } } = require('./errors') const Topology = require('libp2p-interfaces/src/topology') +/** + * @typedef {import('peer-id')} PeerId + * @typedef {import('./peer-store')} PeerStore + * @typedef {import('./connection-manager')} ConnectionManager + * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection + * @typedef {import('libp2p-interfaces/src/topology')} Topology + */ + /** * Responsible for notifying registered protocols of events in the network. */ @@ -17,7 +26,7 @@ class Registrar { /** * @param {Object} props * @param {PeerStore} props.peerStore - * @param {connectionManager} props.connectionManager + * @param {ConnectionManager} props.connectionManager * @class */ constructor ({ peerStore, connectionManager }) { @@ -51,7 +60,7 @@ class Registrar { * Get a connection with a peer. * * @param {PeerId} peerId - * @returns {Connection} + * @returns {Connection | null} */ getConnection (peerId) { return this.connectionManager.get(peerId) @@ -65,11 +74,12 @@ class Registrar { */ register (topology) { if (!Topology.isTopology(topology)) { + log.error('topology must be an instance of interfaces/topology') throw errcode(new Error('topology must be an instance of interfaces/topology'), ERR_INVALID_PARAMETERS) } // Create topology - const id = (parseInt(Math.random() * 1e9)).toString(36) + Date.now() + const id = (Math.random() * 1e9).toString(36) + Date.now() this.topologies.set(id, topology) diff --git a/src/transport-manager.js b/src/transport-manager.js index ab07f2addc..173e5f5de5 100644 --- a/src/transport-manager.js +++ b/src/transport-manager.js @@ -1,25 +1,39 @@ 'use strict' +const debug = require('debug') +const log = Object.assign(debug('libp2p:transports'), { + error: debug('libp2p:transports:err') +}) + const pSettle = require('p-settle') const { codes } = require('./errors') const errCode = require('err-code') -const debug = require('debug') -const log = debug('libp2p:transports') -log.error = debug('libp2p:transports:error') const { updateSelfPeerRecord } = require('./record/utils') +/** + * @typedef {import('multiaddr')} Multiaddr + * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection + * @typedef {import('libp2p-interfaces/src/transport/types').TransportFactory} TransportFactory + * @typedef {import('libp2p-interfaces/src/transport/types').Transport} Transport + * + * @typedef {Object} TransportManagerProperties + * @property {import('./')} libp2p + * @property {import('./upgrader')} upgrader + * + * @typedef {Object} TransportManagerOptions + * @property {number} [faultTolerance = FAULT_TOLERANCE.FATAL_ALL] - Address listen error tolerance. + */ + class TransportManager { /** * @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 {TransportManagerProperties & TransportManagerOptions} options */ constructor ({ libp2p, upgrader, faultTolerance = FAULT_TOLERANCE.FATAL_ALL }) { this.libp2p = libp2p this.upgrader = upgrader + /** @type {Map} */ this._transports = new Map() this._listeners = new Map() this._listenerOptions = new Map() @@ -30,7 +44,7 @@ class TransportManager { * Adds a `Transport` to the manager * * @param {string} key - * @param {Transport} Transport + * @param {TransportFactory} Transport * @param {*} transportOptions - Additional options to pass to the transport * @returns {void} */ @@ -119,7 +133,7 @@ class TransportManager { /** * Returns all the transports instances. * - * @returns {Iterator} + * @returns {IterableIterator} */ getTransports () { return this._transports.values() @@ -143,7 +157,7 @@ class TransportManager { * Starts listeners for each listen Multiaddr. * * @async - * @param {Array} addrs - addresses to attempt to listen on + * @param {Multiaddr[]} addrs - addresses to attempt to listen on */ async listen (addrs) { if (!addrs || addrs.length === 0) { @@ -159,7 +173,7 @@ class TransportManager { // For each supported multiaddr, create a listener for (const addr of supportedAddrs) { log('creating listener for %s on %s', key, addr) - const listener = transport.createListener(this._listenerOptions.get(key), this.onConnection) + const listener = transport.createListener(this._listenerOptions.get(key)) this._listeners.get(key).push(listener) // Track listen/close events diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000000..3e87d7c803 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,84 @@ + +// Insecure Message types +export enum KeyType { + RSA = 0, + Ed25519 = 1, + Secp256k1 = 2, + ECDSA = 3 +} + +// Protobufs +export type MessageProto = { + encode(value: any): Uint8Array + decode(bytes: Uint8Array): any +} + +export type SUCCESS = 100; +export type HOP_SRC_ADDR_TOO_LONG = 220; +export type HOP_DST_ADDR_TOO_LONG = 221; +export type HOP_SRC_MULTIADDR_INVALID = 250; +export type HOP_DST_MULTIADDR_INVALID = 251; +export type HOP_NO_CONN_TO_DST = 260; +export type HOP_CANT_DIAL_DST = 261; +export type HOP_CANT_OPEN_DST_STREAM = 262; +export type HOP_CANT_SPEAK_RELAY = 270; +export type HOP_CANT_RELAY_TO_SELF = 280; +export type STOP_SRC_ADDR_TOO_LONG = 320; +export type STOP_DST_ADDR_TOO_LONG = 321; +export type STOP_SRC_MULTIADDR_INVALID = 350; +export type STOP_DST_MULTIADDR_INVALID = 351; +export type STOP_RELAY_REFUSED = 390; +export type MALFORMED_MESSAGE = 400; + +export type CircuitStatus = SUCCESS | HOP_SRC_ADDR_TOO_LONG | HOP_DST_ADDR_TOO_LONG + | HOP_SRC_MULTIADDR_INVALID | HOP_DST_MULTIADDR_INVALID | HOP_NO_CONN_TO_DST + | HOP_CANT_DIAL_DST | HOP_CANT_OPEN_DST_STREAM | HOP_CANT_SPEAK_RELAY | HOP_CANT_RELAY_TO_SELF + | STOP_SRC_ADDR_TOO_LONG | STOP_DST_ADDR_TOO_LONG | STOP_SRC_MULTIADDR_INVALID + | STOP_DST_MULTIADDR_INVALID | STOP_RELAY_REFUSED | MALFORMED_MESSAGE + +export type HOP = 1; +export type STOP = 2; +export type STATUS = 3; +export type CAN_HOP = 4; + +export type CircuitType = HOP | STOP | STATUS | CAN_HOP + +export type CircuitPeer = { + id: Uint8Array + addrs: Uint8Array[] +} + +export type CircuitRequest = { + type: CircuitType + dstPeer: CircuitPeer + srcPeer: CircuitPeer +} + +export type CircuitMessageProto = { + encode(value: any): Uint8Array + decode(bytes: Uint8Array): any + Status: { + SUCCESS: SUCCESS, + HOP_SRC_ADDR_TOO_LONG: HOP_SRC_ADDR_TOO_LONG, + HOP_DST_ADDR_TOO_LONG: HOP_DST_ADDR_TOO_LONG, + HOP_SRC_MULTIADDR_INVALID: HOP_SRC_MULTIADDR_INVALID, + HOP_DST_MULTIADDR_INVALID: HOP_DST_MULTIADDR_INVALID, + HOP_NO_CONN_TO_DST: HOP_NO_CONN_TO_DST, + HOP_CANT_DIAL_DST: HOP_CANT_DIAL_DST, + HOP_CANT_OPEN_DST_STREAM: HOP_CANT_OPEN_DST_STREAM, + HOP_CANT_SPEAK_RELAY: HOP_CANT_SPEAK_RELAY, + HOP_CANT_RELAY_TO_SELF: HOP_CANT_RELAY_TO_SELF, + STOP_SRC_ADDR_TOO_LONG: STOP_SRC_ADDR_TOO_LONG, + STOP_DST_ADDR_TOO_LONG: STOP_DST_ADDR_TOO_LONG, + STOP_SRC_MULTIADDR_INVALID: STOP_SRC_MULTIADDR_INVALID, + STOP_DST_MULTIADDR_INVALID: STOP_DST_MULTIADDR_INVALID, + STOP_RELAY_REFUSED: STOP_RELAY_REFUSED, + MALFORMED_MESSAGE: MALFORMED_MESSAGE + }, + Type: { + HOP: HOP, + STOP: STOP, + STATUS: STATUS, + CAN_HOP: CAN_HOP + } +} diff --git a/src/upgrader.js b/src/upgrader.js index 92997eb15f..14d0a4e82d 100644 --- a/src/upgrader.js +++ b/src/upgrader.js @@ -1,29 +1,30 @@ 'use strict' const debug = require('debug') -const log = debug('libp2p:upgrader') -log.error = debug('libp2p:upgrader:error') +const log = Object.assign(debug('libp2p:upgrader'), { + error: debug('libp2p:upgrader:err') +}) +const errCode = require('err-code') 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') +const { pipe } = require('it-pipe') const mutableProxy = require('mutable-proxy') const { codes } = require('./errors') /** - * @typedef MultiaddrConnection - * @property {Function} sink - * @property {AsyncIterator} source - * @property {*} conn - * @property {Multiaddr} remoteAddr + * @typedef {import('libp2p-interfaces/src/transport/types').MultiaddrConnection} MultiaddrConnection + * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxerFactory} MuxerFactory + * @typedef {import('libp2p-interfaces/src/stream-muxer/types').Muxer} Muxer + * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream + * @typedef {import('libp2p-interfaces/src/crypto/types').Crypto} Crypto + * @typedef {import('multiaddr')} Multiaddr */ /** * @typedef CryptoResult - * @property {*} conn A duplex iterable + * @property {MultiaddrConnection} conn A duplex iterable * @property {PeerId} remotePeer * @property {string} protocol */ @@ -32,24 +33,24 @@ class Upgrader { /** * @param {object} options * @param {PeerId} options.localPeer - * @param {Metrics} options.metrics - * @param {Map} options.cryptos - * @param {Map} options.muxers - * @param {function(Connection)} options.onConnection - Called when a connection is upgraded - * @param {function(Connection)} options.onConnectionEnd + * @param {import('./metrics')} [options.metrics] + * @param {Map} [options.cryptos] + * @param {Map} [options.muxers] + * @param {(Connection) => void} options.onConnection - Called when a connection is upgraded + * @param {(Connection) => void} options.onConnectionEnd */ constructor ({ localPeer, metrics, - cryptos, - muxers, + cryptos = new Map(), + muxers = new Map(), onConnectionEnd = () => {}, onConnection = () => {} }) { this.localPeer = localPeer this.metrics = metrics - this.cryptos = cryptos || new Map() - this.muxers = muxers || new Map() + this.cryptos = cryptos + this.muxers = muxers this.protector = null this.protocols = new Map() this.onConnection = onConnection @@ -74,7 +75,7 @@ class Upgrader { if (this.metrics) { ({ setTarget: setPeer, proxy: proxyPeer } = mutableProxy()) - const idString = (parseInt(Math.random() * 1e9)).toString(36) + Date.now() + const idString = (Math.random() * 1e9).toString(36) + Date.now() setPeer({ toB58String: () => idString }) maConn = this.metrics.trackStream({ stream: maConn, remotePeer: proxyPeer }) } @@ -132,12 +133,7 @@ class Upgrader { * @returns {Promise} */ async upgradeOutbound (maConn) { - let remotePeerId - try { - remotePeerId = PeerId.createFromB58String(maConn.remoteAddr.getPeerId()) - } catch (err) { - log.error('multiaddr did not contain a valid peer id', err) - } + const remotePeerId = PeerId.createFromB58String(maConn.remoteAddr.getPeerId()) let encryptedConn let remotePeer @@ -149,7 +145,7 @@ class Upgrader { if (this.metrics) { ({ setTarget: setPeer, proxy: proxyPeer } = mutableProxy()) - const idString = (parseInt(Math.random() * 1e9)).toString(36) + Date.now() + const idString = (Math.random() * 1e9).toString(36) + Date.now() setPeer({ toB58String: () => idString }) maConn = this.metrics.trackStream({ stream: maConn, remotePeer: proxyPeer }) } @@ -207,8 +203,8 @@ class Upgrader { * @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 {MuxedStream | MultiaddrConnection} options.upgradedConn - A duplex connection returned from multiplexer and/or crypto selection + * @param {MuxerFactory} [options.Muxer] - The muxer to be used for muxing * @param {PeerId} options.remotePeer - The peer the connection is with * @returns {Connection} */ @@ -272,7 +268,7 @@ class Upgrader { // Wait for close to finish before notifying of the closure (async () => { try { - if (connection.stat.status === ConnectionStatus.OPEN) { + if (connection.stat.status === 'open') { await connection.close() } } catch (err) { @@ -300,6 +296,7 @@ class Upgrader { remotePeer: remotePeer, stat: { direction, + // @ts-ignore timeline: maConn.timeline, multiplexer: Muxer && Muxer.multicodec, encryption: cryptoProtocol @@ -326,7 +323,7 @@ class Upgrader { * @private * @param {object} options * @param {Connection} options.connection - The connection the stream belongs to - * @param {Stream} options.stream + * @param {MuxedStream} options.stream * @param {string} options.protocol */ _onStream ({ connection, stream, protocol }) { @@ -342,7 +339,7 @@ class Upgrader { * @param {PeerId} localPeer - The initiators PeerId * @param {*} connection * @param {Map} cryptos - * @returns {CryptoResult} An encrypted connection, remote peer `PeerId` and the protocol of the `Crypto` used + * @returns {Promise} An encrypted connection, remote peer `PeerId` and the protocol of the `Crypto` used */ async _encryptInbound (localPeer, connection, cryptos) { const mss = new Multistream.Listener(connection) @@ -354,6 +351,10 @@ class Upgrader { const crypto = cryptos.get(protocol) log('encrypting inbound connection...') + if (!crypto) { + throw new Error(`no crypto module found for ${protocol}`) + } + return { ...await crypto.secureInbound(localPeer, stream), protocol @@ -373,7 +374,7 @@ class Upgrader { * @param {*} connection * @param {PeerId} remotePeerId * @param {Map} cryptos - * @returns {CryptoResult} An encrypted connection, remote peer `PeerId` and the protocol of the `Crypto` used + * @returns {Promise} An encrypted connection, remote peer `PeerId` and the protocol of the `Crypto` used */ async _encryptOutbound (localPeer, connection, remotePeerId, cryptos) { const mss = new Multistream.Dialer(connection) @@ -385,6 +386,10 @@ class Upgrader { const crypto = cryptos.get(protocol) log('encrypting outbound connection to %j', remotePeerId) + if (!crypto) { + throw new Error(`no crypto module found for ${protocol}`) + } + return { ...await crypto.secureOutbound(localPeer, stream, remotePeerId), protocol @@ -400,9 +405,9 @@ class Upgrader { * * @private * @async - * @param {*} connection - A basic duplex connection to multiplex - * @param {Map} muxers - The muxers to attempt multiplexing with - * @returns {*} A muxed connection + * @param {MultiaddrConnection} connection - A basic duplex connection to multiplex + * @param {Map} muxers - The muxers to attempt multiplexing with + * @returns {Promise<{ stream: MuxedStream, Muxer?: MuxerFactory}>} A muxed connection */ async _multiplexOutbound (connection, muxers) { const dialer = new Multistream.Dialer(connection) @@ -424,9 +429,9 @@ class Upgrader { * * @private * @async - * @param {*} connection - A basic duplex connection to multiplex - * @param {Map} muxers - The muxers to attempt multiplexing with - * @returns {*} A muxed connection + * @param {MultiaddrConnection} connection - A basic duplex connection to multiplex + * @param {Map} muxers - The muxers to attempt multiplexing with + * @returns {Promise<{ stream: MuxedStream, Muxer?: MuxerFactory}>} A muxed connection */ async _multiplexInbound (connection, muxers) { const listener = new Multistream.Listener(connection) diff --git a/test/dialing/dial-request.spec.js b/test/dialing/dial-request.spec.js index 4ca25e9d28..fd56620a00 100644 --- a/test/dialing/dial-request.spec.js +++ b/test/dialing/dial-request.spec.js @@ -10,7 +10,7 @@ const AggregateError = require('aggregate-error') const pDefer = require('p-defer') const delay = require('delay') -const { DialRequest } = require('../../src/dialer/dial-request') +const DialRequest = require('../../src/dialer/dial-request') const createMockConnection = require('../utils/mockConnection') const error = new Error('dial failes') diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js index 41a3701236..b431c8921b 100644 --- a/test/identify/index.spec.js +++ b/test/identify/index.spec.js @@ -12,7 +12,8 @@ const pWaitFor = require('p-wait-for') const unit8ArrayToString = require('uint8arrays/to-string') const { codes: Errors } = require('../../src/errors') -const { IdentifyService, multicodecs } = require('../../src/identify') +const IdentifyService = require('../../src/identify') +const multicodecs = IdentifyService.multicodecs const Peers = require('../fixtures/peers') const Libp2p = require('../../src') const Envelope = require('../../src/record/envelope') @@ -54,7 +55,8 @@ describe('Identify', () => { connectionManager: new EventEmitter(), peerStore: localPeerStore, multiaddrs: listenMaddrs, - isStarted: () => true + isStarted: () => true, + _options: { host: {} } } }) const remoteIdentify = new IdentifyService({ @@ -63,7 +65,8 @@ describe('Identify', () => { connectionManager: new EventEmitter(), peerStore: remotePeerStore, multiaddrs: listenMaddrs, - isStarted: () => true + isStarted: () => true, + _options: { host: {} } } }) @@ -109,7 +112,8 @@ describe('Identify', () => { connectionManager: new EventEmitter(), peerStore: localPeerStore, multiaddrs: listenMaddrs, - isStarted: () => true + isStarted: () => true, + _options: { host: {} } } }) @@ -119,7 +123,8 @@ describe('Identify', () => { connectionManager: new EventEmitter(), peerStore: remotePeerStore, multiaddrs: listenMaddrs, - isStarted: () => true + isStarted: () => true, + _options: { host: {} } } }) @@ -167,7 +172,8 @@ describe('Identify', () => { peerId: localPeer, connectionManager: new EventEmitter(), peerStore: localPeerStore, - multiaddrs: [] + multiaddrs: [], + _options: { host: {} } } }) const remoteIdentify = new IdentifyService({ @@ -175,7 +181,8 @@ describe('Identify', () => { peerId: remotePeer, connectionManager: new EventEmitter(), peerStore: remotePeerStore, - multiaddrs: [] + multiaddrs: [], + _options: { host: {} } } }) @@ -246,7 +253,8 @@ describe('Identify', () => { connectionManager: new EventEmitter(), peerStore: localPeerStore, multiaddrs: listenMaddrs, - isStarted: () => true + isStarted: () => true, + _options: { host: {} } } }) @@ -259,7 +267,8 @@ describe('Identify', () => { connectionManager, peerStore: remotePeerStore, multiaddrs: [], - isStarted: () => true + isStarted: () => true, + _options: { host: {} } } }) @@ -316,7 +325,8 @@ describe('Identify', () => { connectionManager: new EventEmitter(), peerStore: localPeerStore, multiaddrs: listenMaddrs, - isStarted: () => true + isStarted: () => true, + _options: { host: {} } } }) diff --git a/test/record/envelope.spec.js b/test/record/envelope.spec.js index ca7408f26f..d95a925ecb 100644 --- a/test/record/envelope.spec.js +++ b/test/record/envelope.spec.js @@ -6,7 +6,6 @@ chai.use(require('chai-bytes')) const uint8arrayFromString = require('uint8arrays/from-string') const uint8arrayEquals = require('uint8arrays/equals') const Envelope = require('../../src/record/envelope') -const Record = require('libp2p-interfaces/src/record') const { codes: ErrorCodes } = require('../../src/errors') const peerUtils = require('../utils/creators/peer') @@ -14,9 +13,10 @@ const peerUtils = require('../utils/creators/peer') const domain = 'libp2p-testing' const codec = uint8arrayFromString('/libp2p/testdata') -class TestRecord extends Record { +class TestRecord { constructor (data) { - super(domain, codec) + this.domain = domain + this.codec = codec this.data = data } diff --git a/test/transports/transport-manager.node.js b/test/transports/transport-manager.node.js index fae5576a8f..6c335d92bf 100644 --- a/test/transports/transport-manager.node.js +++ b/test/transports/transport-manager.node.js @@ -51,8 +51,6 @@ describe('Transport Manager (TCP)', () => { }) it('should be able to listen', async () => { - sinon.spy(tm, '_createSelfPeerRecord') - tm.add(Transport.prototype[Symbol.toStringTag], Transport, { listenerOptions: { listen: 'carefully' } }) const transport = tm._transports.get(Transport.prototype[Symbol.toStringTag]) const spyListener = sinon.spy(transport, 'createListener') diff --git a/test/upgrading/upgrader.spec.js b/test/upgrading/upgrader.spec.js index 7282dcd1c2..96df354952 100644 --- a/test/upgrading/upgrader.spec.js +++ b/test/upgrading/upgrader.spec.js @@ -56,28 +56,6 @@ describe('Upgrader', () => { sinon.restore() }) - it('should ignore a missing remote peer id', async () => { - const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) - - const muxers = new Map([[Muxer.multicodec, Muxer]]) - sinon.stub(localUpgrader, 'muxers').value(muxers) - sinon.stub(remoteUpgrader, 'muxers').value(muxers) - - const cryptos = new Map([[Crypto.protocol, Crypto]]) - sinon.stub(localUpgrader, 'cryptos').value(cryptos) - sinon.stub(remoteUpgrader, 'cryptos').value(cryptos) - - // Remove the peer id from the remote address - outbound.remoteAddr = outbound.remoteAddr.decapsulateCode(421) - - const connections = await Promise.all([ - localUpgrader.upgradeOutbound(outbound), - remoteUpgrader.upgradeInbound(inbound) - ]) - - expect(connections).to.have.length(2) - }) - it('should upgrade with valid muxers and crypto', async () => { const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000..5b9a618c43 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "./node_modules/aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src" + ] +} \ No newline at end of file From bc050832075d88eb5555af04dabbc1e2caaeebcd Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 10 Dec 2020 15:36:49 +0100 Subject: [PATCH 070/447] docs: production guide base setup (#804) --- doc/production/DELEGATE_NODES.md | 3 ++ doc/production/README.md | 65 ++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 doc/production/DELEGATE_NODES.md create mode 100644 doc/production/README.md diff --git a/doc/production/DELEGATE_NODES.md b/doc/production/DELEGATE_NODES.md new file mode 100644 index 0000000000..6fad24f0e8 --- /dev/null +++ b/doc/production/DELEGATE_NODES.md @@ -0,0 +1,3 @@ +# Delegate Nodes + +[TODO](https://github.com/libp2p/js-libp2p/pull/718) diff --git a/doc/production/README.md b/doc/production/README.md new file mode 100644 index 0000000000..e655d0b6ff --- /dev/null +++ b/doc/production/README.md @@ -0,0 +1,65 @@ +# Production + +Nowadays, you can run JavaScript code in several different environments, some of them with their own particularities. Moreover, you can use `js-libp2p` for a wide range of use cases. Different environments and different use cases mean different configurations and challenges in the network. + +Libp2p nodes can vary from nodes behind an application, to infrastructure nodes that enable the network to operate and to be efficient. In this context, the Libp2p project provides public infrastructure to boost the network, enable nodes connectivity and improve constrained nodes performance. This public infrastructure should be leveraged for learning the concepts and experimenting. When an application on top of libp2p aims to move into production, its own infrastructure should be setup as the public nodes will be intensively used by others and its availability is not guaranteed. + +This guide aims to guide you from using the public infrastructure into setting up your own. + +## Table of Contents + +* [Joining the Network](#joining-the-network) +* [Connecting to Nodes with connectivity limitations](#connecting-to-nodes-with-connectivity-limitations) + * [`webrtc-star` servers](#webrtc-star-servers) + * [Circuit Relay](#circuit-relay) +* [Querying the network from the browser](#querying-the-network-from-the-browser) +* [Others](#others) + * [SSL](#ssl) + +## Joining the Network + +Once a libp2p node stars, it will need to connect to a set of peers in order to establish its overlay network. + +Currently `js-libp2p` is not the best choice for being a bootstrap node. Its DHT needs to be improved, in order to become an effective server to enable other nodes to properly bootstrap their network. + +Setting up a fleet of [`go-libp2p`](https://github.com/libp2p/go-libp2p) nodes is the recommended way to proceed here. + +## Connecting to Nodes with connectivity limitations + +While the libp2p core codebase aims to work in multiple environments, there are some limitations that are not possible to overcome at the time of writing. These limitations include browser nodes, nodes behind NAT, reverse proxies, firewalls, or lack of compatible transports. + +In the browser, libp2p supports two transports: `websockets` and `webrtc-star`. Nowadays, browsers do not support listening for connections, but only to dial known addresses. `webrtc-star` servers can be used to enable libp2p nodes to discover other nodes running on the browser and to help them establish a connection. + +For nodes that cannot be dialed (including browser), circuit relay nodes should be used. + +### `webrtc-star` servers + +Regarding `webRTC` connections, a set of star servers are needed to act as a rendezvous point, where peers can learn about other peers (`peer-discovery`), as well as exchange their SDP offers (signaling data). + +You can read on how to setup your own star servers in [libp2p/js-libp2p-webrtc-star/DEPLOYMENT.md](https://github.com/libp2p/js-libp2p-webrtc-star/blob/master/DEPLOYMENT.md). + +It is worth pointing out that with new discovery protocols on the way, as well as support for distributed signaling, the star servers should be deprecated on the long run. + +### Circuit Relay + +Libp2p nodes acting as circuit relay aim to establish connectivity between libp2p nodes (e.g. IPFS nodes) that wouldn't otherwise be able to establish a direct connection to each other. + +A relay is needed in situations where nodes are behind NAT, reverse proxies, firewalls and/or simply don't support the same transports (e.g. go-libp2p vs. browser-libp2p). The circuit relay protocol exists to overcome those scenarios. Nodes with the `auto-relay` feature enabled can automatically bind themselves on a relay to listen for connections on their behalf. + +You can use [libp2p/js-libp2p-relay-server](https://github.com/libp2p/js-libp2p-relay-server) to setup your own relay server. This also includes an easy to customize Docker setup for a HOP Relay. + +## Querying the network from the browser + +Libp2p nodes in scenarios such as browser environment and constrained devices will not be an efficient node in the libp2p DHT overlay, as a consequence of their known limitations regarding connectivity and performance. + +Aiming to support these type of nodes to find other peers and content in the network, delegate nodes can be setup. With a set of well known IPFS delegate nodes, nodes with limitations in the network can leverage them to perform peer and content routing queries. + +Currently, delegate nodes must be IPFS nodes as the IPFS HTTP API is leveraged by them to make routing queries. + +You can read on how to setup your own set of delegated nodes in [DELEGATE_NODES.md](./DELEGATE_NODES.md). + +## Others + +### SSL + +TODO From 5d0ac529e4292c2e0cc7a86784423ff3e45014f7 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 10 Dec 2020 15:50:04 +0100 Subject: [PATCH 071/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5275138497..6a3cba38ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.29.4", + "version": "0.30.0-rc.0", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From c5f61ac05f322d4ec3f589fba953eb0d65fdd78c Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 10 Dec 2020 15:50:04 +0100 Subject: [PATCH 072/447] chore: release version v0.30.0-rc.0 --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d804f3480..0a6174f404 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,31 @@ +# [0.30.0-rc.0](https://github.com/libp2p/js-libp2p/compare/v0.29.4...v0.30.0-rc.0) (2020-12-10) + + +### Bug Fixes + +* remove test/dialing/utils extra file ([3f1dc20](https://github.com/libp2p/js-libp2p/commit/3f1dc20caf1c80078f403deb9174cd06d08567ab)) + + +### chore + +* update pubsub ([#801](https://github.com/libp2p/js-libp2p/issues/801)) ([9205fce](https://github.com/libp2p/js-libp2p/commit/9205fce34d0cd8dd5d32988be34c110fc0a5b6e2)) + + +### Features + +* auto relay ([#723](https://github.com/libp2p/js-libp2p/issues/723)) ([65ec267](https://github.com/libp2p/js-libp2p/commit/65ec267e7f4826caacd042213c3fbacce589ab5b)) +* auto relay network query for new relays ([9faf1bf](https://github.com/libp2p/js-libp2p/commit/9faf1bfcf61581acc715b9be78b71dc14501835a)) +* custom announce filter ([48476d5](https://github.com/libp2p/js-libp2p/commit/48476d504a98b7b51b3e2dc64eab93670fde0c7b)) +* custom dialer addr sorter ([#792](https://github.com/libp2p/js-libp2p/issues/792)) ([91b15b6](https://github.com/libp2p/js-libp2p/commit/91b15b6790952b4db11264961d9c6f2a96d1fe43)) +* discover and connect to closest peers ([#798](https://github.com/libp2p/js-libp2p/issues/798)) ([b73106e](https://github.com/libp2p/js-libp2p/commit/b73106eba2d559621f427f7aa788e9b0ef47d135)) + + +### BREAKING CHANGES + +* pubsub signing policy properties were changed according to libp2p-interfaces changes to a single property. The emitSelf option default value was also modified to match the routers value + + + ## [0.29.4](https://github.com/libp2p/js-libp2p/compare/v0.29.3...v0.29.4) (2020-12-09) From 408868655c4e7d714b42b9392817bb92925d548f Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 11 Dec 2020 12:49:58 +0100 Subject: [PATCH 073/447] chore: remove secio from packages table (#833) --- README.md | 1 - package-list.json | 1 - 2 files changed, 2 deletions(-) diff --git a/README.md b/README.md index e6d8af7285..83528993f3 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,6 @@ List of packages currently in existence for libp2p | [`libp2p-websockets`](//github.com/libp2p/js-libp2p-websockets) | [![npm](https://img.shields.io/npm/v/libp2p-websockets.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-websockets/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-websockets.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-websockets) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-websockets/master)](https://travis-ci.com/libp2p/js-libp2p-websockets) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-websockets/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-websockets) | [Jacob Heun](mailto:jacobheun@gmail.com) | | **secure channels** | | [`libp2p-noise`](//github.com/NodeFactoryIo/js-libp2p-noise) | [![npm](https://img.shields.io/npm/v/libp2p-noise.svg?maxAge=86400&style=flat-square)](//github.com/NodeFactoryIo/js-libp2p-noise/releases) | [![Deps](https://david-dm.org/NodeFactoryIo/js-libp2p-noise.svg?style=flat-square)](https://david-dm.org/NodeFactoryIo/js-libp2p-noise) | [![Travis CI](https://flat.badgen.net/travis/NodeFactoryIo/js-libp2p-noise/master)](https://travis-ci.com/NodeFactoryIo/js-libp2p-noise) | [![codecov](https://codecov.io/gh/NodeFactoryIo/js-libp2p-noise/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/NodeFactoryIo/js-libp2p-noise) | N/A | -| [`libp2p-secio`](//github.com/libp2p/js-libp2p-secio) | [![npm](https://img.shields.io/npm/v/libp2p-secio.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-secio/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-secio.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-secio) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-secio/master)](https://travis-ci.com/libp2p/js-libp2p-secio) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-secio/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-secio) | [Friedel Ziegelmayer](mailto:dignifiedquire@gmail.com) | | **stream multiplexers** | | [`libp2p-mplex`](//github.com/libp2p/js-libp2p-mplex) | [![npm](https://img.shields.io/npm/v/libp2p-mplex.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-mplex/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-mplex.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-mplex) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-mplex/master)](https://travis-ci.com/libp2p/js-libp2p-mplex) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-mplex/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-mplex) | [Vasco Santos](mailto:vasco.santos@moxy.studio) | | **peer discovery** | diff --git a/package-list.json b/package-list.json index 84c648a971..28588539c5 100644 --- a/package-list.json +++ b/package-list.json @@ -23,7 +23,6 @@ "secure channels", ["NodeFactoryIo/js-libp2p-noise", "libp2p-noise"], - ["libp2p/js-libp2p-secio", "libp2p-secio"], "stream multiplexers", ["libp2p/js-libp2p-mplex", "libp2p-mplex"], From 9ae1b758e99e3fc9067e26b4eae4c15ccb1ba303 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 11 Dec 2020 17:53:37 +0100 Subject: [PATCH 074/447] fix: types from ipfs integration (#832) --- package.json | 2 +- src/keychain/cms.js | 19 ++++++++--- src/keychain/index.js | 78 +++++++++++++++++++++++++++---------------- src/peer-routing.js | 2 +- 4 files changed, 66 insertions(+), 35 deletions(-) diff --git a/package.json b/package.json index 6a3cba38ed..7c8da34774 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "it-pipe": "^1.1.0", "it-protocol-buffers": "^0.2.0", "libp2p-crypto": "^0.18.0", - "libp2p-interfaces": "^0.8.0", + "libp2p-interfaces": "^0.8.1", "libp2p-utils": "^0.2.2", "mafmt": "^8.0.0", "merge-options": "^2.0.0", diff --git a/src/keychain/cms.js b/src/keychain/cms.js index 3ba99cfb5b..bcd5c36506 100644 --- a/src/keychain/cms.js +++ b/src/keychain/cms.js @@ -1,4 +1,3 @@ -// @ts-nocheck 'use strict' require('node-forge/lib/pkcs7') @@ -9,6 +8,8 @@ const errcode = require('err-code') const uint8ArrayFromString = require('uint8arrays/from-string') const uint8ArrayToString = require('uint8arrays/to-string') +const privates = new WeakMap() + /** * Cryptographic Message Syntax (aka PKCS #7) * @@ -23,13 +24,15 @@ class CMS { * Creates a new instance with a keychain * * @param {import('./index')} keychain - the available keys + * @param {string} dek */ - constructor (keychain) { + constructor (keychain, dek) { if (!keychain) { throw errcode(new Error('keychain is required'), 'ERR_KEYCHAIN_REQUIRED') } this.keychain = keychain + privates.set(this, { dek }) } /** @@ -48,7 +51,9 @@ class CMS { const key = await this.keychain.findKeyByName(name) const pem = await this.keychain._getPrivateKey(name) - const privateKey = forge.pki.decryptRsaPrivateKey(pem, this.keychain._()) + /** @type {string} */ + const dek = privates.get(this).dek + const privateKey = forge.pki.decryptRsaPrivateKey(pem, dek) const certificate = await certificateForKey(key, privateKey) // create a p7 enveloped message @@ -115,8 +120,14 @@ class CMS { } const key = await this.keychain.findKeyById(r.keyId) + + if (!key) { + throw errcode(new Error('No key available to decrypto'), 'ERR_NO_KEY') + } + const pem = await this.keychain._getPrivateKey(key.name) - const privateKey = forge.pki.decryptRsaPrivateKey(pem, this.keychain._()) + const dek = privates.get(this).dek + const privateKey = forge.pki.decryptRsaPrivateKey(pem, dek) cms.decrypt(r.recipient, privateKey) return uint8ArrayFromString(cms.content.getBytes(), 'ascii') } diff --git a/src/keychain/index.js b/src/keychain/index.js index 440a2913a5..5dc694fd98 100644 --- a/src/keychain/index.js +++ b/src/keychain/index.js @@ -1,11 +1,10 @@ -// @ts-nocheck /* eslint max-nested-callbacks: ["error", 5] */ 'use strict' const sanitize = require('sanitize-filename') const mergeOptions = require('merge-options') const crypto = require('libp2p-crypto') -const DS = require('interface-datastore') +const Datastore = require('interface-datastore') const CMS = require('./cms') const errcode = require('err-code') const { Number } = require('ipfs-utils/src/globalthis') @@ -14,8 +13,14 @@ const uint8ArrayFromString = require('uint8arrays/from-string') require('node-forge/lib/sha512') +/** + * @typedef {import('peer-id')} PeerId + * @typedef {import('interface-datastore/src/key')} Key + */ + const keyPrefix = '/pkcs8/' const infoPrefix = '/info/' +const privates = new WeakMap() // NIST SP 800-132 const NIST = { @@ -46,7 +51,8 @@ function validateKeyName (name) { * This assumes than an error indicates that the keychain is under attack. Delay returning an * error to make brute force attacks harder. * - * @param {string | Error} err - The error + * @param {string|Error} err - The error + * @returns {Promise} * @private */ async function throwDelayed (err) { @@ -62,29 +68,28 @@ async function throwDelayed (err) { * Converts a key name into a datastore name. * * @param {string} name - * @returns {DS.Key} + * @returns {Key} * @private */ function DsName (name) { - return new DS.Key(keyPrefix + name) + return new Datastore.Key(keyPrefix + name) } /** * Converts a key name into a datastore info name. * * @param {string} name - * @returns {DS.Key} + * @returns {Key} * @private */ function DsInfoName (name) { - return new DS.Key(infoPrefix + name) + return new Datastore.Key(infoPrefix + name) } /** * Information about a key. * * @typedef {Object} KeyInfo - * * @property {string} id - The universally unique key id. * @property {string} name - The local key name. */ @@ -101,7 +106,7 @@ class Keychain { /** * Creates a new instance of a key chain. * - * @param {DS} store - where the key are. + * @param {Datastore} store - where the key are. * @param {object} options * @class */ @@ -134,7 +139,7 @@ class Keychain { this.opts.dek.keyLength, this.opts.dek.hash) : '' - Object.defineProperty(this, '_', { value: () => dek }) + privates.set(this, { dek }) } /** @@ -148,13 +153,13 @@ class Keychain { * @returns {CMS} */ get cms () { - return new CMS(this) + return new CMS(this, privates.get(this).dek) } /** * Generates the options for a keychain. A random salt is produced. * - * @returns {object} + * @returns {Object} */ static generateOptions () { const options = Object.assign({}, defaultOptions) @@ -167,7 +172,7 @@ class Keychain { * Gets an object that can encrypt/decrypt protected data. * The default options for a keychain. * - * @returns {object} + * @returns {Object} */ static get options () { return defaultOptions @@ -178,10 +183,10 @@ 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. Used for rsa keys only. - * @returns {KeyInfo} + * @param {number} [size = 2048] - The key size in bits. Used for rsa keys only. + * @returns {Promise} */ - async createKey (name, type, size) { + async createKey (name, type, size = 2048) { const self = this if (!validateKeyName(name) || name === 'self') { @@ -208,9 +213,12 @@ class Keychain { let keyInfo try { + // @ts-ignore Differences between several crypto return types need to be fixed in libp2p-crypto const keypair = await crypto.keys.generateKeyPair(type, size) const kid = await keypair.id() - const pem = await keypair.export(this._()) + /** @type {string} */ + const dek = privates.get(this).dek + const pem = await keypair.export(dek) keyInfo = { name: name, id: kid @@ -230,7 +238,7 @@ class Keychain { /** * List all the keys. * - * @returns {KeyInfo[]} + * @returns {Promise} */ async listKeys () { const self = this @@ -250,7 +258,7 @@ class Keychain { * Find a key by it's id. * * @param {string} id - The universally unique key identifier. - * @returns {KeyInfo} + * @returns {Promise} */ async findKeyById (id) { try { @@ -265,7 +273,7 @@ class Keychain { * Find a key by it's name. * * @param {string} name - The local key name. - * @returns {KeyInfo} + * @returns {Promise} */ async findKeyByName (name) { if (!validateKeyName(name)) { @@ -285,7 +293,7 @@ class Keychain { * Remove an existing key. * * @param {string} name - The local key name; must already exist. - * @returns {KeyInfo} + * @returns {Promise} */ async removeKey (name) { const self = this @@ -306,7 +314,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 {Promise} */ async renameKey (oldName, newName) { const self = this @@ -347,7 +355,7 @@ class Keychain { * * @param {string} name - The local key name; must already exist. * @param {string} password - The password - * @returns {string} + * @returns {Promise} */ async exportKey (name, password) { if (!validateKeyName(name)) { @@ -361,7 +369,9 @@ class Keychain { try { const res = await this.store.get(dsname) const pem = uint8ArrayToString(res) - const privateKey = await crypto.keys.import(pem, this._()) + /** @type {string} */ + const dek = privates.get(this).dek + const privateKey = await crypto.keys.import(pem, dek) return privateKey.export(password) } catch (err) { return throwDelayed(err) @@ -374,7 +384,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 {Promise} */ async importKey (name, pem, password) { const self = this @@ -398,7 +408,9 @@ class Keychain { let kid try { kid = await privateKey.id() - pem = await privateKey.export(this._()) + /** @type {string} */ + const dek = privates.get(this).dek + pem = await privateKey.export(dek) } catch (err) { return throwDelayed(err) } @@ -415,6 +427,13 @@ class Keychain { return keyInfo } + /** + * Import a peer key + * + * @param {string} name - The local key name; must not already exist. + * @param {PeerId} peer - The PEM encoded PKCS #8 string + * @returns {Promise} + */ async importPeer (name, peer) { const self = this if (!validateKeyName(name)) { @@ -431,7 +450,9 @@ class Keychain { try { const kid = await privateKey.id() - const pem = await privateKey.export(this._()) + /** @type {string} */ + const dek = privates.get(this).dek + const pem = await privateKey.export(dek) const keyInfo = { name: name, id: kid @@ -450,8 +471,7 @@ class Keychain { * Gets the private key as PEM encoded PKCS #8 string. * * @param {string} name - * @returns {string} - * @private + * @returns {Promise} */ async _getPrivateKey (name) { if (!validateKeyName(name)) { diff --git a/src/peer-routing.js b/src/peer-routing.js index 26fe9625b4..9a4507aebd 100644 --- a/src/peer-routing.js +++ b/src/peer-routing.js @@ -73,7 +73,7 @@ class PeerRouting { /** * Iterates over all peer routers in series to find the given peer. * - * @param {string} id - The id of the peer to find + * @param {PeerId} id - The id of the peer to find * @param {object} [options] * @param {number} [options.timeout] - How long the query should run * @returns {Promise<{ id: PeerId, multiaddrs: Multiaddr[] }>} From 21e8ced81a1b9b18a748cae0df1b3755a3b8d566 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 11 Dec 2020 18:28:22 +0100 Subject: [PATCH 075/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7c8da34774..2354df371c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.30.0-rc.0", + "version": "0.30.0-rc.1", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From 37d66fd88ce942780527af1451b2f10f5c9b932a Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 11 Dec 2020 18:28:22 +0100 Subject: [PATCH 076/447] chore: release version v0.30.0-rc.1 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a6174f404..0dcbf8807f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# [0.30.0-rc.1](https://github.com/libp2p/js-libp2p/compare/v0.30.0-rc.0...v0.30.0-rc.1) (2020-12-11) + + +### Bug Fixes + +* types from ipfs integration ([#832](https://github.com/libp2p/js-libp2p/issues/832)) ([216eb97](https://github.com/libp2p/js-libp2p/commit/216eb9730ef473f73a974c3dbaf306ecdc815c8b)) + + + # [0.30.0-rc.0](https://github.com/libp2p/js-libp2p/compare/v0.29.4...v0.30.0-rc.0) (2020-12-10) From 01d43a7b60f73061205c39352ae38d27ea8c072a Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 15 Dec 2020 14:54:38 +0100 Subject: [PATCH 077/447] chore: fix multicodec updates (#835) * chore: fix specific multicodec version * chore: fix multicodec issues * chore: remove prepare script --- src/record/peer-record/consts.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/record/peer-record/consts.js b/src/record/peer-record/consts.js index 6a748668ca..9a83e03067 100644 --- a/src/record/peer-record/consts.js +++ b/src/record/peer-record/consts.js @@ -3,9 +3,14 @@ const multicodec = require('multicodec') // The domain string used for peer records contained in a Envelope. -module.exports.ENVELOPE_DOMAIN_PEER_RECORD = multicodec.getName(multicodec.LIBP2P_PEER_RECORD) +const domain = multicodec.getName(multicodec.LIBP2P_PEER_RECORD) || 'libp2p-peer-record' // The type hint used to identify peer records in a Envelope. // Defined in https://github.com/multiformats/multicodec/blob/master/table.csv // with name "libp2p-peer-record" -module.exports.ENVELOPE_PAYLOAD_TYPE_PEER_RECORD = Uint8Array.from([3, 1]) +const payloadType = Uint8Array.from([3, 1]) + +module.exports = { + ENVELOPE_DOMAIN_PEER_RECORD: domain, + ENVELOPE_PAYLOAD_TYPE_PEER_RECORD: payloadType +} From 239413e33187be81030ab5e932395d6b6f2b98ba Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 15 Dec 2020 15:08:22 +0100 Subject: [PATCH 078/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2354df371c..34b5d3d73b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.30.0-rc.1", + "version": "0.30.0-rc.2", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From 58d4f9a91542f180907689319d159b326f71096d Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 15 Dec 2020 15:08:22 +0100 Subject: [PATCH 079/447] chore: release version v0.30.0-rc.2 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dcbf8807f..bb72990487 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# [0.30.0-rc.2](https://github.com/libp2p/js-libp2p/compare/v0.30.0-rc.1...v0.30.0-rc.2) (2020-12-15) + + + # [0.30.0-rc.1](https://github.com/libp2p/js-libp2p/compare/v0.30.0-rc.0...v0.30.0-rc.1) (2020-12-11) From 24bb8df5210615768eb4642e5e5099abdbe926ae Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 16 Dec 2020 14:03:09 +0100 Subject: [PATCH 080/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 34b5d3d73b..7fe1655c84 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.30.0-rc.2", + "version": "0.30.0", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From d19401aa4cafcf2237af90b677c743b0ff0f871e Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 16 Dec 2020 14:03:09 +0100 Subject: [PATCH 081/447] chore: release version v0.30.0 --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb72990487..35892e4b5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +# [0.30.0](https://github.com/libp2p/js-libp2p/compare/v0.29.4...v0.30.0) (2020-12-16) + + +### Bug Fixes + +* remove test/dialing/utils extra file ([689c35e](https://github.com/libp2p/js-libp2p/commit/689c35ed1c68e514293a9895d496e2e8440454e9)) +* types from ipfs integration ([#832](https://github.com/libp2p/js-libp2p/issues/832)) ([9ae1b75](https://github.com/libp2p/js-libp2p/commit/9ae1b758e99e3fc9067e26b4eae4c15ccb1ba303)) + + +### chore + +* update pubsub ([#801](https://github.com/libp2p/js-libp2p/issues/801)) ([e50c6ab](https://github.com/libp2p/js-libp2p/commit/e50c6abcf2ebc80ebf2dfadd015ab21a20cffadc)) + + +### Features + +* auto relay ([#723](https://github.com/libp2p/js-libp2p/issues/723)) ([caf66ea](https://github.com/libp2p/js-libp2p/commit/caf66ea1439f6b75a0c321a16bd5c5d7d6a2bd47)) +* auto relay network query for new relays ([0bf0b7c](https://github.com/libp2p/js-libp2p/commit/0bf0b7cf8968d55002ac4c559ffb59985feeb092)) +* custom announce filter ([ef9d3ca](https://github.com/libp2p/js-libp2p/commit/ef9d3ca2c6f35d692d6079e74088c5146d46eebe)) +* custom dialer addr sorter ([#792](https://github.com/libp2p/js-libp2p/issues/792)) ([585ad52](https://github.com/libp2p/js-libp2p/commit/585ad52b4c71dd7514e99a287e0318b2b837ec48)) +* discover and connect to closest peers ([#798](https://github.com/libp2p/js-libp2p/issues/798)) ([baedf3f](https://github.com/libp2p/js-libp2p/commit/baedf3fe5ab946e938db1415d1662452cdfc0cc1)) + + +### BREAKING CHANGES + +* pubsub signing policy properties were changed according to libp2p-interfaces changes to a single property. The emitSelf option default value was also modified to match the routers value + + + # [0.30.0-rc.2](https://github.com/libp2p/js-libp2p/compare/v0.30.0-rc.1...v0.30.0-rc.2) (2020-12-15) From 42b51d8f017dba05aecdf6734552ca0b547b7771 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 17 Dec 2020 14:22:36 +0100 Subject: [PATCH 082/447] chore: add github actions badge and fix codecov (#837) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 83528993f3..8765f6bd48 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@

- - + +
From d60922b79948a8e19ce5824372363f2495229906 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Fri, 15 Jan 2021 04:27:23 -0500 Subject: [PATCH 083/447] docs: Add bootstrap to custom peer discovery (#859) --- doc/CONFIGURATION.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 79c20c8767..f9e8019410 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -261,13 +261,14 @@ const TCP = require('libp2p-tcp') const MPLEX = require('libp2p-mplex') const { NOISE } = require('libp2p-noise') const MulticastDNS = require('libp2p-mdns') +const Bootstrap = require('libp2p-bootstrap') const node = await Libp2p.create({ modules: { transport: [TCP], streamMuxer: [MPLEX], connEncryption: [NOISE], - peerDiscovery: [MulticastDNS] + peerDiscovery: [MulticastDNS, Bootstrap] }, config: { peerDiscovery: { @@ -277,6 +278,15 @@ const node = await Libp2p.create({ [MulticastDNS.tag]: { interval: 1000, enabled: true + }, + [Bootstrap.tag:] { + list: [ // A list of bootstrap peers to connect to starting up the node + "/ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", + "/dnsaddr/bootstrap.libp2p.io/ipfs/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + "/dnsaddr/bootstrap.libp2p.io/ipfs/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa", + ], + interval: 2000, + enabled: true } // .. other discovery module options. } From 77e8273a64ace9cdc0964824cd2aa2654e958401 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 18 Jan 2021 11:15:02 +0100 Subject: [PATCH 084/447] chore: add chat example (#840) --- .github/workflows/main.yml | 7 ++ examples/chat/src/dialer.js | 2 +- .../chat/src/{libp2p-bundle.js => libp2p.js} | 0 examples/chat/src/listener.js | 2 +- examples/chat/test.js | 77 +++++++++++++++++++ 5 files changed, 86 insertions(+), 2 deletions(-) rename examples/chat/src/{libp2p-bundle.js => libp2p.js} (100%) create mode 100644 examples/chat/test.js diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ade1dcbcc0..6dabc16c2a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -65,3 +65,10 @@ jobs: - uses: actions/checkout@v2 - run: yarn - run: cd examples && yarn && npm run test -- auto-relay + test-chat-example: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: cd examples && yarn && npm run test -- chat \ No newline at end of file diff --git a/examples/chat/src/dialer.js b/examples/chat/src/dialer.js index 10581ed237..623999847b 100644 --- a/examples/chat/src/dialer.js +++ b/examples/chat/src/dialer.js @@ -3,7 +3,7 @@ const PeerId = require('peer-id') const multiaddr = require('multiaddr') -const createLibp2p = require('./libp2p-bundle') +const createLibp2p = require('./libp2p') const { stdinToStream, streamToConsole } = require('./stream') async function run() { diff --git a/examples/chat/src/libp2p-bundle.js b/examples/chat/src/libp2p.js similarity index 100% rename from examples/chat/src/libp2p-bundle.js rename to examples/chat/src/libp2p.js diff --git a/examples/chat/src/listener.js b/examples/chat/src/listener.js index 0a64660072..ea7893ee4c 100644 --- a/examples/chat/src/listener.js +++ b/examples/chat/src/listener.js @@ -2,7 +2,7 @@ /* eslint-disable no-console */ const PeerId = require('peer-id') -const createLibp2p = require('./libp2p-bundle.js') +const createLibp2p = require('./libp2p.js') const { stdinToStream, streamToConsole } = require('./stream') async function run() { diff --git a/examples/chat/test.js b/examples/chat/test.js new file mode 100644 index 0000000000..63e67f0c4a --- /dev/null +++ b/examples/chat/test.js @@ -0,0 +1,77 @@ +'use strict' + +const path = require('path') +const execa = require('execa') +const pDefer = require('p-defer') +const uint8ArrayToString = require('uint8arrays/to-string') + +function startProcess(name) { + return execa('node', [path.join(__dirname, name)], { + cwd: path.resolve(__dirname), + all: true + }) +} + +async function test () { + const message = 'test message' + let listenerOutput = '' + let dialerOutput = '' + + let isListening = false + let messageSent = false + const listenerReady = pDefer() + const dialerReady = pDefer() + const messageReceived = pDefer() + + // Step 1 process + process.stdout.write('node listener.js\n') + const listenerProc = startProcess('src/listener.js') + listenerProc.all.on('data', async (data) => { + process.stdout.write(data) + + listenerOutput += uint8ArrayToString(data) + + if (!isListening && listenerOutput.includes('Listener ready, listening on')) { + listenerReady.resolve() + isListening = true + } else if (isListening && listenerOutput.includes(message)) { + messageReceived.resolve() + } + }) + + await listenerReady.promise + process.stdout.write('==================================================================\n') + + // Step 2 process + process.stdout.write('node dialer.js\n') + const dialerProc = startProcess('src/dialer.js') + dialerProc.all.on('data', async (data) => { + process.stdout.write(data) + dialerOutput += uint8ArrayToString(data) + + if (!messageSent && dialerOutput.includes('Type a message and see what happens')) { + dialerReady.resolve() + dialerProc.stdin.write(message) + dialerProc.stdin.write('\n') + messageSent = true + } + }) + + await dialerReady.promise + process.stdout.write('==================================================================\n') + await messageReceived.promise + process.stdout.write('chat message received\n') + + listenerProc.kill() + dialerProc.kill() + await Promise.all([ + listenerProc, + dialerProc + ]).catch((err) => { + if (err.signal !== 'SIGTERM') { + throw err + } + }) +} + +module.exports = test From 6c41e3045608bcae8061d20501be5751dad8157a Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 18 Jan 2021 17:07:30 +0100 Subject: [PATCH 085/447] fix: event emitter types with local types (#864) --- src/connection-manager/index.js | 4 +++- src/connection-manager/latency-monitor.js | 4 +++- .../visibility-change-emitter.js | 4 +++- src/index.js | 4 +++- src/metrics/stats.js | 4 +++- src/peer-store/index.js | 4 +++- src/types.ts | 19 +++++++++++++++++++ 7 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/connection-manager/index.js b/src/connection-manager/index.js index 4add4840e1..99a6ae1d47 100644 --- a/src/connection-manager/index.js +++ b/src/connection-manager/index.js @@ -10,7 +10,9 @@ const mergeOptions = require('merge-options') const LatencyMonitor = require('./latency-monitor') const retimer = require('retimer') -const { EventEmitter } = require('events') +/** @typedef {import('../types').EventEmitterFactory} Events */ +/** @type Events */ +const EventEmitter = require('events') const PeerId = require('peer-id') diff --git a/src/connection-manager/latency-monitor.js b/src/connection-manager/latency-monitor.js index c9301ee142..094e5963f7 100644 --- a/src/connection-manager/latency-monitor.js +++ b/src/connection-manager/latency-monitor.js @@ -7,7 +7,9 @@ /* global window */ const globalThis = require('ipfs-utils/src/globalthis') -const { EventEmitter } = require('events') +/** @typedef {import('../types').EventEmitterFactory} Events */ +/** @type Events */ +const EventEmitter = require('events') const VisibilityChangeEmitter = require('./visibility-change-emitter') const debug = require('debug')('latency-monitor:LatencyMonitor') diff --git a/src/connection-manager/visibility-change-emitter.js b/src/connection-manager/visibility-change-emitter.js index ebe5e7d076..bf7da71b82 100644 --- a/src/connection-manager/visibility-change-emitter.js +++ b/src/connection-manager/visibility-change-emitter.js @@ -6,7 +6,9 @@ */ 'use strict' -const { EventEmitter } = require('events') +/** @typedef {import('../types').EventEmitterFactory} Events */ +/** @type Events */ +const EventEmitter = require('events') const debug = require('debug')('latency-monitor:VisibilityChangeEmitter') diff --git a/src/index.js b/src/index.js index 8393d3faa5..335612e028 100644 --- a/src/index.js +++ b/src/index.js @@ -4,7 +4,9 @@ const debug = require('debug') const log = Object.assign(debug('libp2p'), { error: debug('libp2p:err') }) -const { EventEmitter } = require('events') +/** @typedef {import('./types').EventEmitterFactory} Events */ +/** @type Events */ +const EventEmitter = require('events') const globalThis = require('ipfs-utils/src/globalthis') const errCode = require('err-code') diff --git a/src/metrics/stats.js b/src/metrics/stats.js index e35ab311fc..f820c5b429 100644 --- a/src/metrics/stats.js +++ b/src/metrics/stats.js @@ -1,7 +1,9 @@ // @ts-nocheck 'use strict' -const { EventEmitter } = require('events') +/** @typedef {import('../types').EventEmitterFactory} Events */ +/** @type Events */ +const EventEmitter = require('events') const Big = require('bignumber.js') const MovingAverage = require('moving-average') const retimer = require('retimer') diff --git a/src/peer-store/index.js b/src/peer-store/index.js index b3df1bbb94..0741229057 100644 --- a/src/peer-store/index.js +++ b/src/peer-store/index.js @@ -2,7 +2,9 @@ const errcode = require('err-code') -const { EventEmitter } = require('events') +/** @typedef {import('../types').EventEmitterFactory} Events */ +/** @type Events */ +const EventEmitter = require('events') const PeerId = require('peer-id') const AddressBook = require('./address-book') diff --git a/src/types.ts b/src/types.ts index 3e87d7c803..16c00ef109 100644 --- a/src/types.ts +++ b/src/types.ts @@ -82,3 +82,22 @@ export type CircuitMessageProto = { CAN_HOP: CAN_HOP } } + +export interface EventEmitterFactory { + new(): EventEmitter; +} + +export interface EventEmitter { + addListener(event: string | symbol, listener: (...args: any[]) => void); + on(event: string | symbol, listener: (...args: any[]) => void); + once(event: string | symbol, listener: (...args: any[]) => void); + removeListener(event: string | symbol, listener: (...args: any[]) => void); + off(event: string | symbol, listener: (...args: any[]) => void); + removeAllListeners(event?: string | symbol); + setMaxListeners(n: number); + getMaxListeners(): number; + listeners(event: string | symbol): Function[]; // eslint-disable-line @typescript-eslint/ban-types + rawListeners(event: string | symbol): Function[]; // eslint-disable-line @typescript-eslint/ban-types + emit(event: string | symbol, ...args: any[]): boolean; + listenerCount(event: string | symbol): number; +} From f40697975eaadb048597e64189bf7f633806e690 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 18 Jan 2021 17:14:30 +0100 Subject: [PATCH 086/447] chore: update contributors --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 7fe1655c84..16ab383c26 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.30.0", + "version": "0.30.1", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", @@ -153,7 +153,7 @@ "Thomas Eizinger ", "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", "Didrik Nordström ", - "Irakli Gozalishvili ", + "Fei Liu ", "Ethan Lam ", "Joel Gustafson ", "Julien Bouquillon ", @@ -166,20 +166,20 @@ "Smite Chow ", "Soeren ", "Sönke Hahn ", - "robertkiel ", "Tiago Alves ", "Daijiro Wachi ", "Yusef Napora ", "Zane Starr ", + "robertkiel ", "Cindy Wu ", "Chris Bratlien ", "ebinks ", - "Bernd Strehl ", "Florian-Merle ", "Francis Gulotta ", "Felipe Martins ", - "isan_rivkin ", + "Bernd Strehl ", "Henrique Dias ", - "Fei Liu " + "isan_rivkin ", + "Irakli Gozalishvili " ] } From 9014ea657a726a68c59bf0d42ef5f480ef38f5de Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 18 Jan 2021 17:14:31 +0100 Subject: [PATCH 087/447] chore: release version v0.30.1 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35892e4b5b..0044e41e1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.30.1](https://github.com/libp2p/js-libp2p/compare/v0.30.0...v0.30.1) (2021-01-18) + + +### Bug Fixes + +* event emitter types with local types ([#864](https://github.com/libp2p/js-libp2p/issues/864)) ([6c41e30](https://github.com/libp2p/js-libp2p/commit/6c41e3045608bcae8061d20501be5751dad8157a)) + + + # [0.30.0](https://github.com/libp2p/js-libp2p/compare/v0.29.4...v0.30.0) (2020-12-16) From 0b854a949fd6886a7619662d98040b7b7cc9a7bb Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 19 Jan 2021 09:57:56 +0100 Subject: [PATCH 088/447] chore: add browser example test (#846) --- .github/workflows/main.yml | 9 +++- examples/libp2p-in-the-browser/package.json | 1 + examples/libp2p-in-the-browser/test.js | 52 +++++++++++++++++++++ examples/package.json | 3 ++ examples/test.js | 7 ++- 5 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 examples/libp2p-in-the-browser/test.js diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6dabc16c2a..4acb1a87e5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -71,4 +71,11 @@ jobs: steps: - uses: actions/checkout@v2 - run: yarn - - run: cd examples && yarn && npm run test -- chat \ No newline at end of file + - run: cd examples && yarn && npm run test -- chat + test-libp2p-in-the-browser-example: + needs: check + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: cd examples && yarn && npm run test -- libp2p-in-the-browser diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json index 8d09b86d31..b59c8cdbf9 100644 --- a/examples/libp2p-in-the-browser/package.json +++ b/examples/libp2p-in-the-browser/package.json @@ -8,6 +8,7 @@ ], "scripts": { "test": "echo \"Error: no test specified\" && exit 1", + "build": "parcel build index.html", "start": "parcel index.html" }, "keywords": [], diff --git a/examples/libp2p-in-the-browser/test.js b/examples/libp2p-in-the-browser/test.js new file mode 100644 index 0000000000..c0e67fe7bf --- /dev/null +++ b/examples/libp2p-in-the-browser/test.js @@ -0,0 +1,52 @@ +'use strict' + +const execa = require('execa') +const { chromium } = require('playwright'); + +async function run() { + let url = '' + const proc = execa('parcel', ['./index.html'], { + preferLocal: true, + localDir: __dirname, + cwd: __dirname, + all: true + }) + + proc.all.on('data', async (chunk) => { + /**@type {string} */ + const out = chunk.toString() + + if (out.includes('Server running at')) { + url = out.replace('Server running at ', '') + } + + if (out.includes('✨ Built in ')) { + try { + const browser = await chromium.launch(); + const page = await browser.newPage(); + await page.goto(url); + await page.waitForFunction(selector => document.querySelector(selector).innerText === 'libp2p started!', '#status') + await page.waitForFunction( + selector => { + const text = document.querySelector(selector).innerText + return text.includes('libp2p id is') && + text.includes('Found peer') && + text.includes('Connected to') + }, + '#output', + { timeout: 5000 } + ) + await browser.close(); + + } catch (err) { + console.error(err) + process.exit(1) + } finally { + proc.cancel() + } + } + }) + +} + +module.exports = run diff --git a/examples/package.json b/examples/package.json index feaf656d1b..b8c0a46a54 100644 --- a/examples/package.json +++ b/examples/package.json @@ -12,5 +12,8 @@ "fs-extra": "^8.1.0", "p-defer": "^3.0.0", "which": "^2.0.1" + }, + "devDependencies": { + "playwright": "^1.7.1" } } diff --git a/examples/test.js b/examples/test.js index 3da6eccdb9..69f2be8bca 100644 --- a/examples/test.js +++ b/examples/test.js @@ -22,7 +22,6 @@ async function testExample (dir) { await installDeps(dir) await build(dir) await runTest(dir) - // TODO: add browser test setup } async function installDeps (dir) { @@ -89,7 +88,7 @@ async function runTest (dir) { return } - const runTest = require(testFile) + const test = require(testFile) - await runTest() -} \ No newline at end of file + await test() +} From 0a02207116fa0b1d300fb14e5114987691493039 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 19 Jan 2021 11:02:56 +0100 Subject: [PATCH 089/447] chore: add discovery example tests (#841) --- .github/workflows/main.yml | 7 ++++ examples/discovery-mechanisms/1.js | 10 +---- examples/discovery-mechanisms/bootstrapers.js | 13 ++++++ examples/discovery-mechanisms/test-1.js | 42 +++++++++++++++++++ examples/discovery-mechanisms/test-2.js | 35 ++++++++++++++++ examples/discovery-mechanisms/test.js | 11 +++++ 6 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 examples/discovery-mechanisms/bootstrapers.js create mode 100644 examples/discovery-mechanisms/test-1.js create mode 100644 examples/discovery-mechanisms/test-2.js create mode 100644 examples/discovery-mechanisms/test.js diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4acb1a87e5..306d1fd6c9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -79,3 +79,10 @@ jobs: - uses: actions/checkout@v2 - run: yarn - run: cd examples && yarn && npm run test -- libp2p-in-the-browser + test-discovery-mechanisms-example: + needs: check + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: cd examples && yarn && npm run test -- discovery-mechanisms diff --git a/examples/discovery-mechanisms/1.js b/examples/discovery-mechanisms/1.js index 8dbf31d184..d5187fcd73 100644 --- a/examples/discovery-mechanisms/1.js +++ b/examples/discovery-mechanisms/1.js @@ -7,15 +7,7 @@ const Mplex = require('libp2p-mplex') const { NOISE } = require('libp2p-noise') const Bootstrap = require('libp2p-bootstrap') -// Find this list at: https://github.com/ipfs/js-ipfs/blob/master/src/core/runtime/config-nodejs.json -const bootstrapers = [ - '/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ', - '/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' -] +const bootstrapers = require('./bootstrapers') ;(async () => { const node = await Libp2p.create({ diff --git a/examples/discovery-mechanisms/bootstrapers.js b/examples/discovery-mechanisms/bootstrapers.js new file mode 100644 index 0000000000..50da9eb4fa --- /dev/null +++ b/examples/discovery-mechanisms/bootstrapers.js @@ -0,0 +1,13 @@ +'use strict' + +// Find this list at: https://github.com/ipfs/js-ipfs/blob/master/src/core/runtime/config-nodejs.json +const bootstrapers = [ + '/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ', + '/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' +] + +module.exports = bootstrapers diff --git a/examples/discovery-mechanisms/test-1.js b/examples/discovery-mechanisms/test-1.js new file mode 100644 index 0000000000..d187953ca5 --- /dev/null +++ b/examples/discovery-mechanisms/test-1.js @@ -0,0 +1,42 @@ +'use strict' + +const path = require('path') +const execa = require('execa') +const pWaitFor = require('p-wait-for') +const uint8ArrayToString = require('uint8arrays/to-string') +const bootstrapers = require('./bootstrapers') + +const discoveredCopy = 'Discovered:' +const connectedCopy = 'Connection established to:' + +async function test () { + const discoveredNodes = [] + const connectedNodes = [] + + process.stdout.write('1.js\n') + + const proc = execa('node', [path.join(__dirname, '1.js')], { + cwd: path.resolve(__dirname), + all: true + }) + + proc.all.on('data', async (data) => { + process.stdout.write(data) + const line = uint8ArrayToString(data) + + // Discovered or Connected + if (line.includes(discoveredCopy)) { + const id = line.trim().split(discoveredCopy)[1] + discoveredNodes.push(id) + } else if (line.includes(connectedCopy)) { + const id = line.trim().split(connectedCopy)[1] + connectedNodes.push(id) + } + }) + + await pWaitFor(() => discoveredNodes.length === bootstrapers.length && connectedNodes.length === bootstrapers.length) + + proc.kill() +} + +module.exports = test diff --git a/examples/discovery-mechanisms/test-2.js b/examples/discovery-mechanisms/test-2.js new file mode 100644 index 0000000000..0aa5c84857 --- /dev/null +++ b/examples/discovery-mechanisms/test-2.js @@ -0,0 +1,35 @@ +'use strict' + +const path = require('path') +const execa = require('execa') +const pWaitFor = require('p-wait-for') +const uint8ArrayToString = require('uint8arrays/to-string') + +const discoveredCopy = 'Discovered:' + +async function test() { + const discoveredNodes = [] + + process.stdout.write('2.js\n') + + const proc = execa('node', [path.join(__dirname, '2.js')], { + cwd: path.resolve(__dirname), + all: true + }) + + proc.all.on('data', async (data) => { + process.stdout.write(data) + const line = uint8ArrayToString(data) + + if (line.includes(discoveredCopy)) { + const id = line.trim().split(discoveredCopy)[1] + discoveredNodes.push(id) + } + }) + + await pWaitFor(() => discoveredNodes.length === 2) + + proc.kill() +} + +module.exports = test diff --git a/examples/discovery-mechanisms/test.js b/examples/discovery-mechanisms/test.js new file mode 100644 index 0000000000..38b2a347fd --- /dev/null +++ b/examples/discovery-mechanisms/test.js @@ -0,0 +1,11 @@ +'use strict' + +const test1 = require('./test-1') +const test2 = require('./test-2') + +async function test () { + await test1() + await test2() +} + +module.exports = test From f45cd1c4b545e8ae2e7074d3d0584a2e757fcf65 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 20 Jan 2021 10:46:04 +0100 Subject: [PATCH 090/447] chore: echo example test (#842) --- .github/workflows/main.yml | 7 +++ examples/echo/src/dialer.js | 2 +- .../echo/src/{libp2p-bundle.js => libp2p.js} | 0 examples/echo/src/listener.js | 2 +- examples/echo/test.js | 61 +++++++++++++++++++ 5 files changed, 70 insertions(+), 2 deletions(-) rename examples/echo/src/{libp2p-bundle.js => libp2p.js} (100%) create mode 100644 examples/echo/test.js diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 306d1fd6c9..5faac18f91 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -72,6 +72,13 @@ jobs: - uses: actions/checkout@v2 - run: yarn - run: cd examples && yarn && npm run test -- chat + test-echo-example: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: cd examples && yarn && npm run test -- echo test-libp2p-in-the-browser-example: needs: check runs-on: macos-latest diff --git a/examples/echo/src/dialer.js b/examples/echo/src/dialer.js index aa20208edd..5938760783 100644 --- a/examples/echo/src/dialer.js +++ b/examples/echo/src/dialer.js @@ -6,7 +6,7 @@ */ const PeerId = require('peer-id') -const createLibp2p = require('./libp2p-bundle') +const createLibp2p = require('./libp2p') const pipe = require('it-pipe') async function run() { diff --git a/examples/echo/src/libp2p-bundle.js b/examples/echo/src/libp2p.js similarity index 100% rename from examples/echo/src/libp2p-bundle.js rename to examples/echo/src/libp2p.js diff --git a/examples/echo/src/listener.js b/examples/echo/src/listener.js index e4a9cd171b..1f814fc3e0 100644 --- a/examples/echo/src/listener.js +++ b/examples/echo/src/listener.js @@ -6,7 +6,7 @@ */ const PeerId = require('peer-id') -const createLibp2p = require('./libp2p-bundle') +const createLibp2p = require('./libp2p') const pipe = require('it-pipe') async function run() { diff --git a/examples/echo/test.js b/examples/echo/test.js new file mode 100644 index 0000000000..168f00445a --- /dev/null +++ b/examples/echo/test.js @@ -0,0 +1,61 @@ +'use strict' + +const path = require('path') +const execa = require('execa') +const pDefer = require('p-defer') +const uint8ArrayToString = require('uint8arrays/to-string') + +function startProcess(name) { + return execa('node', [path.join(__dirname, name)], { + cwd: path.resolve(__dirname), + all: true + }) +} + +async function test () { + const listenerReady = pDefer() + const messageReceived = pDefer() + + // Step 1 process + process.stdout.write('node listener.js\n') + const listenerProc = startProcess('src/listener.js') + listenerProc.all.on('data', async (data) => { + process.stdout.write(data) + const s = uint8ArrayToString(data) + + if (s.includes('Listener ready, listening on:')) { + listenerReady.resolve() + } + }) + + await listenerReady.promise + process.stdout.write('==================================================================\n') + + // Step 2 process + process.stdout.write('node dialer.js\n') + const dialerProc = startProcess('src/dialer.js') + dialerProc.all.on('data', async (data) => { + process.stdout.write(data) + const s = uint8ArrayToString(data) + + if (s.includes('received echo:')) { + messageReceived.resolve() + } + }) + + await messageReceived.promise + process.stdout.write('echo message received\n') + + listenerProc.kill() + dialerProc.kill() + await Promise.all([ + listenerProc, + dialerProc + ]).catch((err) => { + if (err.signal !== 'SIGTERM') { + throw err + } + }) +} + +module.exports = test From 67067c97d5ef91fae88ed086541fe68c46f8bc64 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 21 Jan 2021 09:27:27 +0100 Subject: [PATCH 091/447] chore: connection encryption example test (#843) --- .github/workflows/main.yml | 7 +++++ .../1.js | 2 +- .../README.md | 2 +- examples/connection-encryption/test.js | 30 +++++++++++++++++++ 4 files changed, 39 insertions(+), 2 deletions(-) rename examples/{encrypted-communications => connection-encryption}/1.js (96%) rename examples/{encrypted-communications => connection-encryption}/README.md (98%) create mode 100644 examples/connection-encryption/test.js diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5faac18f91..7323151050 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -72,6 +72,13 @@ jobs: - uses: actions/checkout@v2 - run: yarn - run: cd examples && yarn && npm run test -- chat + test-connection-encryption-example: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: cd examples && yarn && npm run test -- connection-encryption test-echo-example: needs: check runs-on: ubuntu-latest diff --git a/examples/encrypted-communications/1.js b/examples/connection-encryption/1.js similarity index 96% rename from examples/encrypted-communications/1.js rename to examples/connection-encryption/1.js index d17498afcd..1d3f0bcb01 100644 --- a/examples/encrypted-communications/1.js +++ b/examples/connection-encryption/1.js @@ -1,6 +1,6 @@ 'use strict' -const Libp2p = require('../../') +const Libp2p = require('../..') const TCP = require('libp2p-tcp') const Mplex = require('libp2p-mplex') const { NOISE } = require('libp2p-noise') diff --git a/examples/encrypted-communications/README.md b/examples/connection-encryption/README.md similarity index 98% rename from examples/encrypted-communications/README.md rename to examples/connection-encryption/README.md index 3e23ea6de5..2d1c0614a6 100644 --- a/examples/encrypted-communications/README.md +++ b/examples/connection-encryption/README.md @@ -1,4 +1,4 @@ -# Encrypted Communications +# Connection Encryption libp2p can leverage the encrypted communications from the transports it uses (i.e WebRTC). To ensure that every connection is encrypted, independently of how it was set up, libp2p also supports a set of modules that encrypt every communication established. diff --git a/examples/connection-encryption/test.js b/examples/connection-encryption/test.js new file mode 100644 index 0000000000..9be0ab219c --- /dev/null +++ b/examples/connection-encryption/test.js @@ -0,0 +1,30 @@ +'use strict' + +const path = require('path') +const execa = require('execa') +const pDefer = require('p-defer') +const uint8ArrayToString = require('uint8arrays/to-string') + +async function test () { + const messageReceived = pDefer() + process.stdout.write('1.js\n') + + const proc = execa('node', [path.join(__dirname, '1.js')], { + cwd: path.resolve(__dirname), + all: true + }) + + proc.all.on('data', async (data) => { + process.stdout.write(data) + + const s = uint8ArrayToString(data) + if (s.includes('This information is sent out encrypted to the other peer')) { + messageReceived.resolve() + } + }) + + await messageReceived.promise + proc.kill() +} + +module.exports = test From a28c878f4aaadb2392e7386d5122db3320eb211d Mon Sep 17 00:00:00 2001 From: Samlior Date: Thu, 21 Jan 2021 19:09:53 +0800 Subject: [PATCH 092/447] chore: fix close for ConnectionManager (#861) --- src/connection-manager/index.js | 2 +- test/connection-manager/index.node.js | 36 +++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/connection-manager/index.js b/src/connection-manager/index.js index 99a6ae1d47..1bdef27af9 100644 --- a/src/connection-manager/index.js +++ b/src/connection-manager/index.js @@ -160,7 +160,7 @@ class ConnectionManager extends EventEmitter { } } - await tasks + await Promise.all(tasks) this.connections.clear() } diff --git a/test/connection-manager/index.node.js b/test/connection-manager/index.node.js index b2f3ba5d2b..aae0b3462b 100644 --- a/test/connection-manager/index.node.js +++ b/test/connection-manager/index.node.js @@ -3,6 +3,7 @@ const { expect } = require('aegir/utils/chai') const sinon = require('sinon') +const { CLOSED } = require('libp2p-interfaces/src/connection/status') const delay = require('delay') const pWaitFor = require('p-wait-for') @@ -268,5 +269,40 @@ describe('libp2p.connections', () => { await libp2p.stop() }) + + it('should be closed status once immediately stopping', async () => { + const [libp2p] = await peerUtils.createPeer({ + config: { + peerId: peerIds[0], + addresses: { + listen: ['/ip4/127.0.0.1/tcp/15003/ws'] + }, + modules: baseOptions.modules + } + }) + const [remoteLibp2p] = await peerUtils.createPeer({ + config: { + peerId: peerIds[1], + addresses: { + listen: ['/ip4/127.0.0.1/tcp/15004/ws'] + }, + modules: baseOptions.modules + } + }) + + libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) + await libp2p.dial(remoteLibp2p.peerId) + + const totalConns = Array.from(libp2p.connections.values()) + expect(totalConns.length).to.eql(1) + const conns = totalConns[0] + expect(conns.length).to.eql(1) + const conn = conns[0] + + await libp2p.stop() + expect(conn.stat.status).to.eql(CLOSED) + + await remoteLibp2p.stop() + }) }) }) From 45c33675a7412c66d0fd4e113ef8506077b6f492 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 21 Jan 2021 12:41:27 +0000 Subject: [PATCH 093/447] fix: store multiaddrs during content and peer routing queries (#865) * fix: store provider multiaddrs during find providers Changes the behaviour of `libp2p.contentRouting.findProviders` to store the multiaddrs reported by the routers before yielding results to the caller, so when they try to dial the provider, the multiaddrs are already in the peer store's address book. Also dedupes providers reported by routers but keeps all of the addresses reported, even for duplicates. Also, also fixes a performance bug where the previous implementation would wait for any router to completely finish finding providers before sending any results to the caller. It'll now yield results as they come in which makes it much, much faster. --- package.json | 6 + .../index.js} | 44 ++--- src/content-routing/utils.js | 89 ++++++++++ src/peer-routing.js | 67 ++++---- test/content-routing/content-routing.node.js | 133 ++++++++++++++- test/peer-routing/peer-routing.node.js | 161 ++++++++++++++---- 6 files changed, 407 insertions(+), 93 deletions(-) rename src/{content-routing.js => content-routing/index.js} (78%) create mode 100644 src/content-routing/utils.js diff --git a/package.json b/package.json index 16ab383c26..b97b8da44e 100644 --- a/package.json +++ b/package.json @@ -65,10 +65,16 @@ "ipfs-utils": "^5.0.1", "it-all": "^1.0.1", "it-buffer": "^0.1.2", + "it-drain": "^1.0.3", + "it-filter": "^1.0.1", + "it-first": "^1.0.4", "it-handshake": "^1.0.1", "it-length-prefixed": "^3.0.1", + "it-map": "^1.0.4", + "it-merge": "1.0.0", "it-pipe": "^1.1.0", "it-protocol-buffers": "^0.2.0", + "it-take": "1.0.0", "libp2p-crypto": "^0.18.0", "libp2p-interfaces": "^0.8.1", "libp2p-utils": "^0.2.2", diff --git a/src/content-routing.js b/src/content-routing/index.js similarity index 78% rename from src/content-routing.js rename to src/content-routing/index.js index cba4a38bce..f5d999264d 100644 --- a/src/content-routing.js +++ b/src/content-routing/index.js @@ -1,10 +1,16 @@ 'use strict' const errCode = require('err-code') -const { messages, codes } = require('./errors') +const { messages, codes } = require('../errors') +const { + storeAddresses, + uniquePeers, + requirePeers, + maybeLimitSource +} = require('./utils') -const all = require('it-all') -const pAny = require('p-any') +const merge = require('it-merge') +const { pipe } = require('it-pipe') /** * @typedef {import('peer-id')} PeerId @@ -21,22 +27,21 @@ const pAny = require('p-any') class ContentRouting { /** * @class - * @param {import('./')} libp2p + * @param {import('..')} libp2p */ constructor (libp2p) { this.libp2p = libp2p this.routers = libp2p._modules.contentRouting || [] this.dht = libp2p._dht - // If we have the dht, make it first + // If we have the dht, add it to the available content routers if (this.dht) { - this.routers.unshift(this.dht) + this.routers.push(this.dht) } } /** - * Iterates over all content routers in series to find providers of the given key. - * Once a content router succeeds, iteration will stop. + * Iterates over all content routers in parallel to find providers of the given key. * * @param {CID} key - The CID key of the content to find * @param {object} [options] @@ -44,25 +49,20 @@ class ContentRouting { * @param {number} [options.maxNumProviders] - maximum number of providers to find * @returns {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} */ - async * findProviders (key, options) { + async * findProviders (key, options = {}) { if (!this.routers.length) { throw errCode(new Error('No content this.routers available'), 'NO_ROUTERS_AVAILABLE') } - const result = await pAny( - this.routers.map(async (router) => { - const provs = await all(router.findProviders(key, options)) - - if (!provs || !provs.length) { - throw errCode(new Error('not found'), 'NOT_FOUND') - } - return provs - }) + yield * pipe( + merge( + ...this.routers.map(router => router.findProviders(key, options)) + ), + (source) => storeAddresses(source, this.libp2p.peerStore), + (source) => uniquePeers(source), + (source) => maybeLimitSource(source, options.maxNumProviders), + (source) => requirePeers(source) ) - - for (const peer of result) { - yield peer - } } /** diff --git a/src/content-routing/utils.js b/src/content-routing/utils.js new file mode 100644 index 0000000000..55ba3a7516 --- /dev/null +++ b/src/content-routing/utils.js @@ -0,0 +1,89 @@ +'use strict' + +const errCode = require('err-code') +const filter = require('it-filter') +const map = require('it-map') +const take = require('it-take') + +/** + * @typedef {import('peer-id')} PeerId + * @typedef {import('multiaddr')} Multiaddr + */ + +/** + * Store the multiaddrs from every peer in the passed peer store + * + * @param {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} source + * @param {import('../peer-store')} peerStore + */ +function storeAddresses (source, peerStore) { + return map(source, (peer) => { + // ensure we have the addresses for a given peer + peerStore.addressBook.add(peer.id, peer.multiaddrs) + + return peer + }) +} + +/** + * Filter peers by unique peer id + * + * @param {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} source + */ +function uniquePeers (source) { + /** @type Set */ + const seen = new Set() + + return filter(source, (peer) => { + // dedupe by peer id + if (seen.has(peer.id.toString())) { + return false + } + + seen.add(peer.id.toString()) + + return true + }) +} + +/** + * Require at least `min` peers to be yielded from `source` + * + * @param {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} source + * @param {number} min + */ +async function * requirePeers (source, min = 1) { + let seen = 0 + + for await (const peer of source) { + seen++ + + yield peer + } + + if (seen < min) { + throw errCode(new Error('not found'), 'NOT_FOUND') + } +} + +/** + * If `max` is passed, only take that number of peers from the source + * otherwise take all the peers + * + * @param {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} source + * @param {number} [max] + */ +function maybeLimitSource (source, max) { + if (max) { + return take(source, max) + } + + return source +} + +module.exports = { + storeAddresses, + uniquePeers, + requirePeers, + maybeLimitSource +} diff --git a/src/peer-routing.js b/src/peer-routing.js index 9a4507aebd..637b8d2425 100644 --- a/src/peer-routing.js +++ b/src/peer-routing.js @@ -5,16 +5,24 @@ const log = Object.assign(debug('libp2p:peer-routing'), { error: debug('libp2p:peer-routing:err') }) const errCode = require('err-code') - -const all = require('it-all') -const pAny = require('p-any') +const { + storeAddresses, + uniquePeers, + requirePeers +} = require('./content-routing/utils') + +const merge = require('it-merge') +const { pipe } = require('it-pipe') +const first = require('it-first') +const drain = require('it-drain') +const filter = require('it-filter') const { setDelayedInterval, clearDelayedInterval } = require('set-delayed-interval') +const PeerId = require('peer-id') /** - * @typedef {import('peer-id')} PeerId * @typedef {import('multiaddr')} Multiaddr */ class PeerRouting { @@ -27,9 +35,9 @@ class PeerRouting { this._peerStore = libp2p.peerStore this._routers = libp2p._modules.peerRouting || [] - // If we have the dht, make it first + // If we have the dht, add it to the available peer routers if (libp2p._dht) { - this._routers.unshift(libp2p._dht) + this._routers.push(libp2p._dht) } this._refreshManagerOptions = libp2p._options.peerRouting.refreshManager @@ -55,9 +63,8 @@ class PeerRouting { */ async _findClosestPeersTask () { try { - for await (const { id, multiaddrs } of this.getClosestPeers(this._peerId.id)) { - this._peerStore.addressBook.add(id, multiaddrs) - } + // nb getClosestPeers adds the addresses to the address book + await drain(this.getClosestPeers(this._peerId.id)) } catch (err) { log.error(err) } @@ -71,7 +78,7 @@ class PeerRouting { } /** - * Iterates over all peer routers in series to find the given peer. + * Iterates over all peer routers in parallel to find the given peer. * * @param {PeerId} id - The id of the peer to find * @param {object} [options] @@ -83,16 +90,20 @@ class PeerRouting { throw errCode(new Error('No peer routers available'), 'NO_ROUTERS_AVAILABLE') } - return pAny(this._routers.map(async (router) => { - const result = await router.findPeer(id, options) + const output = await pipe( + merge( + ...this._routers.map(router => [router.findPeer(id, options)]) + ), + (source) => filter(source, Boolean), + (source) => storeAddresses(source, this._peerStore), + (source) => first(source) + ) - // If we don't have a result, we need to provide an error to keep trying - if (!result || Object.keys(result).length === 0) { - throw errCode(new Error('not found'), 'NOT_FOUND') - } + if (output) { + return output + } - return result - })) + throw errCode(new Error('not found'), 'NOT_FOUND') } /** @@ -108,20 +119,14 @@ class PeerRouting { throw errCode(new Error('No peer routers available'), 'NO_ROUTERS_AVAILABLE') } - const result = await pAny( - this._routers.map(async (router) => { - const peers = await all(router.getClosestPeers(key, options)) - - if (!peers || !peers.length) { - throw errCode(new Error('not found'), 'NOT_FOUND') - } - return peers - }) + yield * pipe( + merge( + ...this._routers.map(router => router.getClosestPeers(key, options)) + ), + (source) => storeAddresses(source, this._peerStore), + (source) => uniquePeers(source), + (source) => requirePeers(source) ) - - for (const peer of result) { - yield peer - } } } diff --git a/test/content-routing/content-routing.node.js b/test/content-routing/content-routing.node.js index 1bef142d22..4baa810c35 100644 --- a/test/content-routing/content-routing.node.js +++ b/test/content-routing/content-routing.node.js @@ -12,6 +12,8 @@ const CID = require('cids') const ipfsHttpClient = require('ipfs-http-client') const DelegatedContentRouter = require('libp2p-delegated-content-routing') const multiaddr = require('multiaddr') +const drain = require('it-drain') +const all = require('it-all') const peerUtils = require('../utils/creators/peer') const { baseOptions, routingOptions } = require('./utils') @@ -78,10 +80,14 @@ describe('content-routing', () => { it('should use the nodes dht to find providers', async () => { const deferred = pDefer() + const [providerPeerId] = await peerUtils.createPeerId({ fixture: false }) sinon.stub(nodes[0]._dht, 'findProviders').callsFake(function * () { deferred.resolve() - yield + yield { + id: providerPeerId, + multiaddrs: [] + } }) await nodes[0].contentRouting.findProviders().next() @@ -138,10 +144,14 @@ describe('content-routing', () => { it('should use the delegate router to find providers', async () => { const deferred = pDefer() + const [providerPeerId] = await peerUtils.createPeerId({ fixture: false }) sinon.stub(delegate, 'findProviders').callsFake(function * () { deferred.resolve() - yield + yield { + id: providerPeerId, + multiaddrs: [] + } }) await node.contentRouting.findProviders().next() @@ -251,6 +261,110 @@ describe('content-routing', () => { afterEach(() => node.stop()) + it('should store the multiaddrs of a peer', async () => { + const [providerPeerId] = await peerUtils.createPeerId({ fixture: false }) + const result = { + id: providerPeerId, + multiaddrs: [ + multiaddr('/ip4/123.123.123.123/tcp/49320') + ] + } + + sinon.stub(node._dht, 'findProviders').callsFake(function * () {}) + sinon.stub(delegate, 'findProviders').callsFake(function * () { + yield result + }) + + expect(node.peerStore.addressBook.get(providerPeerId)).to.not.be.ok() + + await drain(node.contentRouting.findProviders('a cid')) + + expect(node.peerStore.addressBook.get(providerPeerId)).to.deep.include({ + isCertified: false, + multiaddr: result.multiaddrs[0] + }) + }) + + it('should not wait for routing findProviders to finish before returning results', async () => { + const [providerPeerId] = await peerUtils.createPeerId({ fixture: false }) + const result = { + id: providerPeerId, + multiaddrs: [ + multiaddr('/ip4/123.123.123.123/tcp/49320') + ] + } + + const defer = pDefer() + + sinon.stub(node._dht, 'findProviders').callsFake(async function * () { // eslint-disable-line require-yield + await defer.promise + }) + sinon.stub(delegate, 'findProviders').callsFake(async function * () { + yield result + + await defer.promise + }) + + for await (const provider of node.contentRouting.findProviders('a cid')) { + expect(provider.id).to.deep.equal(providerPeerId) + defer.resolve() + } + }) + + it('should dedupe results', async () => { + const [providerPeerId] = await peerUtils.createPeerId({ fixture: false }) + const result = { + id: providerPeerId, + multiaddrs: [ + multiaddr('/ip4/123.123.123.123/tcp/49320') + ] + } + + sinon.stub(node._dht, 'findProviders').callsFake(async function * () { + yield result + }) + sinon.stub(delegate, 'findProviders').callsFake(async function * () { + yield result + }) + + const results = await all(node.contentRouting.findProviders('a cid')) + + expect(results).to.be.an('array').with.lengthOf(1).that.deep.equals([result]) + }) + + it('should combine multiaddrs when different addresses are returned by different content routers', async () => { + const [providerPeerId] = await peerUtils.createPeerId({ fixture: false }) + const result1 = { + id: providerPeerId, + multiaddrs: [ + multiaddr('/ip4/123.123.123.123/tcp/49320') + ] + } + const result2 = { + id: providerPeerId, + multiaddrs: [ + multiaddr('/ip4/213.213.213.213/tcp/2344') + ] + } + + sinon.stub(node._dht, 'findProviders').callsFake(async function * () { + yield result1 + }) + sinon.stub(delegate, 'findProviders').callsFake(async function * () { + yield result2 + }) + + await drain(node.contentRouting.findProviders('a cid')) + + expect(node.peerStore.addressBook.get(providerPeerId)).to.deep.include({ + isCertified: false, + multiaddr: result1.multiaddrs[0] + }).and.to.deep.include({ + isCertified: false, + multiaddr: result2.multiaddrs[0] + }) + }) + it('should use both the dht and delegate router to provide', async () => { const dhtDeferred = pDefer() const delegatedDeferred = pDefer() @@ -271,15 +385,18 @@ describe('content-routing', () => { ]) }) - it('should only use the dht if it finds providers', async () => { - const results = [true] + it('should use the dht if the delegate fails to find providers', async () => { + const [providerPeerId] = await peerUtils.createPeerId({ fixture: false }) + const results = [{ + id: providerPeerId, + multiaddrs: [] + }] sinon.stub(node._dht, 'findProviders').callsFake(function * () { yield results[0] }) sinon.stub(delegate, 'findProviders').callsFake(function * () { // eslint-disable-line require-yield - throw new Error('the delegate should not have been called') }) const providers = [] @@ -292,7 +409,11 @@ describe('content-routing', () => { }) it('should use the delegate if the dht fails to find providers', async () => { - const results = [true] + const [providerPeerId] = await peerUtils.createPeerId({ fixture: false }) + const results = [{ + id: providerPeerId, + multiaddrs: [] + }] sinon.stub(node._dht, 'findProviders').callsFake(function * () {}) diff --git a/test/peer-routing/peer-routing.node.js b/test/peer-routing/peer-routing.node.js index 74cc6393e6..3432cba8a4 100644 --- a/test/peer-routing/peer-routing.node.js +++ b/test/peer-routing/peer-routing.node.js @@ -10,6 +10,8 @@ const delay = require('delay') const pDefer = require('p-defer') const pWaitFor = require('p-wait-for') const mergeOptions = require('merge-options') +const drain = require('it-drain') +const all = require('it-all') const ipfsHttpClient = require('ipfs-http-client') const DelegatedPeerRouter = require('libp2p-delegated-peer-routing') @@ -82,10 +84,14 @@ describe('peer-routing', () => { it('should use the nodes dht to get the closest peers', async () => { const deferred = pDefer() + const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) sinon.stub(nodes[0]._dht, 'getClosestPeers').callsFake(function * () { deferred.resolve() - yield + yield { + id: remotePeerId, + multiaddrs: [] + } }) await nodes[0].peerRouting.getClosestPeers().next() @@ -128,10 +134,14 @@ describe('peer-routing', () => { it('should use the delegate router to find peers', async () => { const deferred = pDefer() + const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) sinon.stub(delegate, 'findPeer').callsFake(() => { deferred.resolve() - return 'fake peer-id' + return { + id: remotePeerId, + multiaddrs: [] + } }) await node.peerRouting.findPeer() @@ -140,10 +150,14 @@ describe('peer-routing', () => { it('should use the delegate router to get the closest peers', async () => { const deferred = pDefer() + const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) sinon.stub(delegate, 'getClosestPeers').callsFake(function * () { deferred.resolve() - yield + yield { + id: remotePeerId, + multiaddrs: [] + } }) await node.peerRouting.getClosestPeers().next() @@ -152,7 +166,7 @@ describe('peer-routing', () => { }) it('should be able to find a peer', async () => { - const peerKey = 'QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL' + const peerKey = PeerId.createFromB58String('QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL') const mockApi = nock('http://0.0.0.0:60197') .post('/api/v0/dht/findpeer') .query(true) @@ -277,55 +291,93 @@ describe('peer-routing', () => { afterEach(() => node.stop()) - it('should only use the dht if it finds the peer', async () => { - const dhtDeferred = pDefer() + it('should use the delegate if the dht fails to find the peer', async () => { + const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) + const results = { + id: remotePeerId, + multiaddrs: [] + } - sinon.stub(node._dht, 'findPeer').callsFake(() => { - dhtDeferred.resolve() - return { id: node.peerId } - }) + sinon.stub(node._dht, 'findPeer').callsFake(() => {}) sinon.stub(delegate, 'findPeer').callsFake(() => { - throw new Error('the delegate should not have been called') + return results }) - await node.peerRouting.findPeer('a peer id') - await dhtDeferred.promise + const peer = await node.peerRouting.findPeer(remotePeerId) + expect(peer).to.eql(results) }) - it('should use the delegate if the dht fails to find the peer', async () => { - const results = [true] + it('should not wait for the dht to return if the delegate does first', async () => { + const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) + const results = { + id: remotePeerId, + multiaddrs: [] + } - sinon.stub(node._dht, 'findPeer').callsFake(() => {}) + const defer = pDefer() + + sinon.stub(node._dht, 'findPeer').callsFake(async () => { + await defer.promise + }) sinon.stub(delegate, 'findPeer').callsFake(() => { return results }) - const peer = await node.peerRouting.findPeer('a peer id') + const peer = await node.peerRouting.findPeer(remotePeerId) expect(peer).to.eql(results) + + defer.resolve() }) - it('should only use the dht if it gets the closest peers', async () => { - const results = [true] + it('should not wait for the delegate to return if the dht does first', async () => { + const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) + const results = { + id: remotePeerId, + multiaddrs: [] + } - sinon.stub(node._dht, 'getClosestPeers').callsFake(function * () { - yield results[0] - }) + const defer = pDefer() - sinon.stub(delegate, 'getClosestPeers').callsFake(function * () { // eslint-disable-line require-yield - throw new Error('the delegate should not have been called') + sinon.stub(node._dht, 'findPeer').callsFake(() => { + return results }) + sinon.stub(delegate, 'findPeer').callsFake(async () => { + await defer.promise + }) + + const peer = await node.peerRouting.findPeer(remotePeerId) + expect(peer).to.eql(results) - const closest = [] - for await (const peer of node.peerRouting.getClosestPeers('a cid')) { - closest.push(peer) + defer.resolve() + }) + + it('should store the addresses of the found peer', async () => { + const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) + const results = { + id: remotePeerId, + multiaddrs: [ + multiaddr('/ip4/123.123.123.123/tcp/38982') + ] } - expect(closest).to.have.length.above(0) - expect(closest).to.eql(results) + const spy = sinon.spy(node.peerStore.addressBook, 'add') + + sinon.stub(node._dht, 'findPeer').callsFake(() => { + return results + }) + sinon.stub(delegate, 'findPeer').callsFake(() => {}) + + await node.peerRouting.findPeer(remotePeerId) + + expect(spy.calledWith(results.id, results.multiaddrs)).to.be.true() }) it('should use the delegate if the dht fails to get the closest peer', async () => { - const results = [true] + const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) + const results = [{ + id: remotePeerId, + multiaddrs: [] + }] sinon.stub(node._dht, 'getClosestPeers').callsFake(function * () { }) @@ -333,14 +385,55 @@ describe('peer-routing', () => { yield results[0] }) - const closest = [] - for await (const peer of node.peerRouting.getClosestPeers('a cid')) { - closest.push(peer) - } + const closest = await all(node.peerRouting.getClosestPeers('a cid')) expect(closest).to.have.length.above(0) expect(closest).to.eql(results) }) + + it('should store the addresses of the closest peer', async () => { + const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) + const result = { + id: remotePeerId, + multiaddrs: [ + multiaddr('/ip4/123.123.123.123/tcp/38982') + ] + } + + const spy = sinon.spy(node.peerStore.addressBook, 'add') + + sinon.stub(node._dht, 'getClosestPeers').callsFake(function * () { }) + + sinon.stub(delegate, 'getClosestPeers').callsFake(function * () { + yield result + }) + + await drain(node.peerRouting.getClosestPeers('a cid')) + + expect(spy.calledWith(result.id, result.multiaddrs)).to.be.true() + }) + + it('should dedupe closest peers', async () => { + const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) + const results = [{ + id: remotePeerId, + multiaddrs: [ + multiaddr('/ip4/123.123.123.123/tcp/38982') + ] + }] + + sinon.stub(node._dht, 'getClosestPeers').callsFake(function * () { + yield * results + }) + + sinon.stub(delegate, 'getClosestPeers').callsFake(function * () { + yield * results + }) + + const peers = await all(node.peerRouting.getClosestPeers('a cid')) + + expect(peers).to.be.an('array').with.a.lengthOf(1).that.deep.equals(results) + }) }) describe('peer routing refresh manager service', () => { From 000826db21094851aeb66deb2b4811c9d90396b8 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 21 Jan 2021 13:50:48 +0100 Subject: [PATCH 094/447] chore: update contributors --- package.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index b97b8da44e..68e53ab375 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.30.1", + "version": "0.30.2", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", @@ -156,11 +156,11 @@ "Andrew Nesbitt ", "Giovanni T. Parra ", "Ryan Bell ", + "Samlior ", "Thomas Eizinger ", "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", "Didrik Nordström ", - "Fei Liu ", - "Ethan Lam ", + "Irakli Gozalishvili ", "Joel Gustafson ", "Julien Bouquillon ", "Kevin Kwok ", @@ -168,7 +168,7 @@ "Dmitriy Ryajov ", "RasmusErik Voel Jensen ", "Diogo Silva ", - "Samlior ", + "isan_rivkin ", "Smite Chow ", "Soeren ", "Sönke Hahn ", @@ -179,13 +179,13 @@ "robertkiel ", "Cindy Wu ", "Chris Bratlien ", - "ebinks ", "Florian-Merle ", "Francis Gulotta ", "Felipe Martins ", - "Bernd Strehl ", + "ebinks ", "Henrique Dias ", - "isan_rivkin ", - "Irakli Gozalishvili " + "Bernd Strehl ", + "Fei Liu ", + "Ethan Lam " ] } From 961b48bb8d944ccd1df2b08806fba2c84f1a0a5e Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 21 Jan 2021 13:50:49 +0100 Subject: [PATCH 095/447] chore: release version v0.30.2 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0044e41e1c..3a0ef60696 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.30.2](https://github.com/libp2p/js-libp2p/compare/v0.30.1...v0.30.2) (2021-01-21) + + +### Bug Fixes + +* store multiaddrs during content and peer routing queries ([#865](https://github.com/libp2p/js-libp2p/issues/865)) ([45c3367](https://github.com/libp2p/js-libp2p/commit/45c33675a7412c66d0fd4e113ef8506077b6f492)) + + + ## [0.30.1](https://github.com/libp2p/js-libp2p/compare/v0.30.0...v0.30.1) (2021-01-18) From 748b5528761781e6d2f34971de7a3bd841a5180c Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 22 Jan 2021 10:24:15 +0100 Subject: [PATCH 096/447] chore: pnet example test (#845) --- .github/workflows/main.yml | 7 +++++++ examples/pnet/test.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 examples/pnet/test.js diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7323151050..a9f3143036 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -100,3 +100,10 @@ jobs: - uses: actions/checkout@v2 - run: yarn - run: cd examples && yarn && npm run test -- discovery-mechanisms + test-pnet-example: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: cd examples && yarn && npm run test -- pnet diff --git a/examples/pnet/test.js b/examples/pnet/test.js new file mode 100644 index 0000000000..129c883ad7 --- /dev/null +++ b/examples/pnet/test.js @@ -0,0 +1,30 @@ +'use strict' + +const path = require('path') +const execa = require('execa') +const pDefer = require('p-defer') +const uint8ArrayToString = require('uint8arrays/to-string') + +async function test () { + const messageReceived = pDefer() + process.stdout.write('index.js\n') + + const proc = execa('node', [path.join(__dirname, 'index.js')], { + cwd: path.resolve(__dirname), + all: true + }) + + proc.all.on('data', async (data) => { + process.stdout.write(data) + + const s = uint8ArrayToString(data) + if (s.includes('This message is sent on a private network')) { + messageReceived.resolve() + } + }) + + await messageReceived.promise + proc.kill() +} + +module.exports = test From 037c965a6733dd79cc1dc8de15712420c2b43ad8 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 27 Jan 2021 09:45:31 +0100 Subject: [PATCH 097/447] chore: update deps (#869) --- .aegir.js | 2 +- package.json | 48 ++++++++++---------- src/circuit/utils.js | 2 +- src/connection-manager/latency-monitor.js | 6 +-- src/dialer/dial-request.js | 4 +- src/dialer/index.js | 2 +- src/index.js | 3 +- src/keychain/index.js | 9 ++-- src/pnet/crypto.js | 5 +- test/content-routing/content-routing.node.js | 25 +++++++--- 10 files changed, 57 insertions(+), 49 deletions(-) diff --git a/.aegir.js b/.aegir.js index d57cba6aca..a727c36e91 100644 --- a/.aegir.js +++ b/.aegir.js @@ -45,7 +45,7 @@ const after = async () => { } module.exports = { - bundlesize: { maxSize: '225kB' }, + bundlesize: { maxSize: '260kB' }, hooks: { pre: before, post: after diff --git a/package.json b/package.json index 68e53ab375..546cb35487 100644 --- a/package.json +++ b/package.json @@ -52,41 +52,41 @@ }, "dependencies": { "abort-controller": "^3.0.0", - "aggregate-error": "^3.0.1", - "any-signal": "^1.1.0", - "bignumber.js": "^9.0.0", - "cids": "^1.0.0", + "aggregate-error": "^3.1.0", + "any-signal": "^2.1.1", + "bignumber.js": "^9.0.1", + "cids": "^1.1.5", "class-is": "^1.1.0", - "debug": "^4.1.1", + "debug": "^4.3.1", "err-code": "^2.0.0", - "events": "^3.1.0", + "events": "^3.2.0", "hashlru": "^2.3.0", - "interface-datastore": "^2.0.0", - "ipfs-utils": "^5.0.1", - "it-all": "^1.0.1", + "interface-datastore": "^3.0.3", + "ipfs-utils": "^6.0.0", + "it-all": "^1.0.4", "it-buffer": "^0.1.2", "it-drain": "^1.0.3", "it-filter": "^1.0.1", "it-first": "^1.0.4", - "it-handshake": "^1.0.1", - "it-length-prefixed": "^3.0.1", + "it-handshake": "^1.0.2", + "it-length-prefixed": "^3.1.0", "it-map": "^1.0.4", "it-merge": "1.0.0", "it-pipe": "^1.1.0", "it-protocol-buffers": "^0.2.0", "it-take": "1.0.0", - "libp2p-crypto": "^0.18.0", + "libp2p-crypto": "^0.19.0", "libp2p-interfaces": "^0.8.1", "libp2p-utils": "^0.2.2", "mafmt": "^8.0.0", - "merge-options": "^2.0.0", + "merge-options": "^3.0.4", "moving-average": "^1.0.0", "multiaddr": "^8.1.0", - "multicodec": "^2.0.0", + "multicodec": "^2.1.0", "multihashing-async": "^2.0.1", "multistream-select": "^1.0.0", "mutable-proxy": "^1.0.0", - "node-forge": "^0.9.1", + "node-forge": "^0.10.0", "p-any": "^3.0.0", "p-fifo": "^1.0.0", "p-settle": "^4.0.1", @@ -97,7 +97,7 @@ "set-delayed-interval": "^1.0.0", "streaming-iterables": "^5.0.2", "timeout-abort-controller": "^1.1.1", - "varint": "^5.0.0", + "varint": "^6.0.0", "xsalsa20": "^1.0.2" }, "devDependencies": { @@ -106,20 +106,20 @@ "aegir": "^29.2.0", "chai-bytes": "^0.1.2", "chai-string": "^1.5.0", - "delay": "^4.3.0", + "delay": "^4.4.0", "interop-libp2p": "^0.3.0", "into-stream": "^6.0.0", - "ipfs-http-client": "^47.0.1", + "ipfs-http-client": "^48.2.2", "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.8.0", + "libp2p-delegated-content-routing": "^0.9.0", "libp2p-delegated-peer-routing": "^0.8.0", "libp2p-floodsub": "^0.24.0", - "libp2p-gossipsub": "^0.7.0", - "libp2p-kad-dht": "^0.20.0", + "libp2p-gossipsub": "^0.8.0", + "libp2p-kad-dht": "^0.20.5", "libp2p-mdns": "^0.15.0", "libp2p-mplex": "^0.10.1", "libp2p-noise": "^2.0.0", @@ -131,11 +131,11 @@ "nock": "^13.0.3", "p-defer": "^3.0.0", "p-times": "^3.0.0", - "p-wait-for": "^3.1.0", + "p-wait-for": "^3.2.0", "promisify-es6": "^1.0.3", "rimraf": "^3.0.2", - "sinon": "^9.0.2", - "uint8arrays": "^1.1.0" + "sinon": "^9.2.4", + "uint8arrays": "^2.0.5" }, "contributors": [ "David Dias ", diff --git a/src/circuit/utils.js b/src/circuit/utils.js index f75e13386a..c1a3ff10eb 100644 --- a/src/circuit/utils.js +++ b/src/circuit/utils.js @@ -12,7 +12,7 @@ const TextEncoder = require('ipfs-utils/src/text-encoder') * @returns {Promise} */ module.exports.namespaceToCid = async (namespace) => { - const bytes = new TextEncoder('utf8').encode(namespace) + const bytes = new TextEncoder().encode(namespace) const hash = await multihashing(bytes, 'sha2-256') return new CID(hash) diff --git a/src/connection-manager/latency-monitor.js b/src/connection-manager/latency-monitor.js index 094e5963f7..d5b44e9ab4 100644 --- a/src/connection-manager/latency-monitor.js +++ b/src/connection-manager/latency-monitor.js @@ -5,8 +5,6 @@ * This code is based on `latency-monitor` (https://github.com/mlucool/latency-monitor) by `mlucool` (https://github.com/mlucool), available under Apache License 2.0 (https://github.com/mlucool/latency-monitor/blob/master/LICENSE) */ -/* global window */ -const globalThis = require('ipfs-utils/src/globalthis') /** @typedef {import('../types').EventEmitterFactory} Events */ /** @type Events */ const EventEmitter = require('events') @@ -74,9 +72,9 @@ class LatencyMonitor extends EventEmitter { that.asyncTestFn = asyncTestFn // If there is no asyncFn, we measure latency // If process: use high resolution timer - if (globalThis.process && globalThis.process.hrtime) { + if (globalThis.process && globalThis.process.hrtime) { // eslint-disable-line no-undef debug('Using process.hrtime for timing') - that.now = globalThis.process.hrtime + that.now = globalThis.process.hrtime // eslint-disable-line no-undef that.getDeltaMS = (startTime) => { const hrtime = that.now(startTime) return (hrtime[0] * 1000) + (hrtime[1] / 1000000) diff --git a/src/dialer/dial-request.js b/src/dialer/dial-request.js index 62bab31be6..8027bacc76 100644 --- a/src/dialer/dial-request.js +++ b/src/dialer/dial-request.js @@ -2,7 +2,7 @@ const errCode = require('err-code') const AbortController = require('abort-controller').default -const anySignal = require('any-signal') +const { anySignal } = require('any-signal') const FIFO = require('p-fifo') const pAny = require('p-any') @@ -67,7 +67,7 @@ class DialRequest { let conn try { const signal = dialAbortControllers[i].signal - conn = await this.dialAction(addr, { ...options, signal: anySignal([signal, options.signal]) }) + conn = await this.dialAction(addr, { ...options, signal: options.signal ? anySignal([signal, options.signal]) : signal }) // Remove the successful AbortController so it is not aborted dialAbortControllers.splice(i, 1) } finally { diff --git a/src/dialer/index.js b/src/dialer/index.js index 09ae2627ad..17b96e82b9 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -7,7 +7,7 @@ const log = Object.assign(debug('libp2p:dialer'), { const errCode = require('err-code') const multiaddr = require('multiaddr') const TimeoutController = require('timeout-abort-controller') -const anySignal = require('any-signal') +const { anySignal } = require('any-signal') const DialRequest = require('./dial-request') const { publicAddressesFirst } = require('libp2p-utils/src/address-sort') diff --git a/src/index.js b/src/index.js index 335612e028..86dbc4ce93 100644 --- a/src/index.js +++ b/src/index.js @@ -7,7 +7,6 @@ const log = Object.assign(debug('libp2p'), { /** @typedef {import('./types').EventEmitterFactory} Events */ /** @type Events */ const EventEmitter = require('events') -const globalThis = require('ipfs-utils/src/globalthis') const errCode = require('err-code') const PeerId = require('peer-id') @@ -243,7 +242,7 @@ class Libp2p extends EventEmitter { // Attach private network protector if (this._modules.connProtector) { this.upgrader.protector = this._modules.connProtector - } else if (globalThis.process !== undefined && globalThis.process.env && globalThis.process.env.LIBP2P_FORCE_PNET) { + } else if (globalThis.process !== undefined && globalThis.process.env && globalThis.process.env.LIBP2P_FORCE_PNET) { // eslint-disable-line no-undef throw new Error('Private network is enforced, but no protector was provided') } diff --git a/src/keychain/index.js b/src/keychain/index.js index 5dc694fd98..fdf46cab70 100644 --- a/src/keychain/index.js +++ b/src/keychain/index.js @@ -4,10 +4,9 @@ const sanitize = require('sanitize-filename') const mergeOptions = require('merge-options') const crypto = require('libp2p-crypto') -const Datastore = require('interface-datastore') +const { Key } = require('interface-datastore') const CMS = require('./cms') const errcode = require('err-code') -const { Number } = require('ipfs-utils/src/globalthis') const uint8ArrayToString = require('uint8arrays/to-string') const uint8ArrayFromString = require('uint8arrays/from-string') @@ -15,7 +14,7 @@ require('node-forge/lib/sha512') /** * @typedef {import('peer-id')} PeerId - * @typedef {import('interface-datastore/src/key')} Key + * @typedef {import('interface-datastore/src/types').Datastore} Datastore */ const keyPrefix = '/pkcs8/' @@ -72,7 +71,7 @@ async function throwDelayed (err) { * @private */ function DsName (name) { - return new Datastore.Key(keyPrefix + name) + return new Key(keyPrefix + name) } /** @@ -83,7 +82,7 @@ function DsName (name) { * @private */ function DsInfoName (name) { - return new Datastore.Key(infoPrefix + name) + return new Key(infoPrefix + name) } /** diff --git a/src/pnet/crypto.js b/src/pnet/crypto.js index 9cfcbc8e6f..9cf6f76581 100644 --- a/src/pnet/crypto.js +++ b/src/pnet/crypto.js @@ -63,9 +63,10 @@ module.exports.decodeV1PSK = (pskBuffer) => { const metadata = uint8ArrayToString(pskBuffer).split(/(?:\r\n|\r|\n)/g) const pskTag = metadata.shift() const codec = metadata.shift() - const psk = uint8ArrayFromString(metadata.shift(), 'base16') + const pskString = metadata.shift() + const psk = pskString && uint8ArrayFromString(pskString, 'base16') - if (psk.byteLength !== KEY_LENGTH) { + if (!psk || psk.byteLength !== KEY_LENGTH) { throw new Error(Errors.INVALID_PSK) } diff --git a/test/content-routing/content-routing.node.js b/test/content-routing/content-routing.node.js index 4baa810c35..30caf0d610 100644 --- a/test/content-routing/content-routing.node.js +++ b/test/content-routing/content-routing.node.js @@ -161,25 +161,36 @@ describe('content-routing', () => { it('should be able to register as a provider', async () => { const cid = new CID('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB') - const mockApi = nock('http://0.0.0.0:60197') - // mock the refs call - .post('/api/v0/refs') + const provider = 'QmZNgCqZCvTsi3B4Vt7gsSqpkqDpE7M2Y9TDmEhbDb4ceF' + + const mockBlockApi = nock('http://0.0.0.0:60197') + // mock the block/stat call + .post('/api/v0/block/stat') .query(true) - .reply(200, null, [ + .reply(200, '{"Key":"QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB","Size":"2169"}', [ + 'Content-Type', 'application/json', + 'X-Chunked-Output', '1' + ]) + const mockDhtApi = nock('http://0.0.0.0:60197') + // mock the dht/provide call + .post('/api/v0/dht/provide') + .query(true) + .reply(200, `{"Extra":"","ID":"QmWKqWXCtRXEeCQTo3FoZ7g4AfnGiauYYiczvNxFCHicbB","Responses":[{"Addrs":["/ip4/0.0.0.0/tcp/0"],"ID":"${provider}"}],"Type":4}\n`, [ 'Content-Type', 'application/json', 'X-Chunked-Output', '1' ]) await node.contentRouting.provide(cid) - expect(mockApi.isDone()).to.equal(true) + expect(mockBlockApi.isDone()).to.equal(true) + expect(mockDhtApi.isDone()).to.equal(true) }) it('should handle errors when registering as a provider', async () => { const cid = new CID('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB') const mockApi = nock('http://0.0.0.0:60197') - // mock the refs call - .post('/api/v0/refs') + // mock the block/stat call + .post('/api/v0/block/stat') .query(true) .reply(502, 'Bad Gateway', ['Content-Type', 'application/json']) From 9942cbd50c31095604b42471f9cfe098dc38d656 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 27 Jan 2021 14:33:32 +0100 Subject: [PATCH 098/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 546cb35487..3dd149c665 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.30.2", + "version": "0.30.3", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From b5c9e48b688bed09dfca1ea2878d4d6a7ad83c11 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 27 Jan 2021 14:33:33 +0100 Subject: [PATCH 099/447] chore: release version v0.30.3 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a0ef60696..851076c0c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [0.30.3](https://github.com/libp2p/js-libp2p/compare/v0.30.2...v0.30.3) (2021-01-27) + + + ## [0.30.2](https://github.com/libp2p/js-libp2p/compare/v0.30.1...v0.30.2) (2021-01-21) From 0a6bc0d1013dfd80ab600e8f74c1544b433ece29 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 27 Jan 2021 13:55:26 +0000 Subject: [PATCH 100/447] feat: add UPnP NAT manager (#810) * feat: add uPnP nat manager Adds a really basic nat manager that attempts to use UPnP to punch a hole through your router for any IPV4 tcp addresses you have configured. Adds any configured addresses to the node's observed addresses list and adds observed addresses to `libp2p.multiaddrs` so we exchange them with peers when performing `identify` and people can dial you. Adds configuration options under `config.nat` Hole punching is async to not affect start up time. Co-authored-by: Vasco Santos --- .aegir.js | 5 +- doc/API.md | 9 + doc/CONFIGURATION.md | 37 ++++ package.json | 8 +- src/address-manager/index.js | 64 ++++++- src/config.js | 10 + src/identify/index.js | 4 +- src/index.js | 48 ++++- src/nat-manager.js | 168 +++++++++++++++++ test/addresses/address-manager.spec.js | 84 ++++++++- test/addresses/addresses.node.js | 22 +++ test/core/consume-peer-record.spec.js | 46 +++++ test/dialing/direct.node.js | 2 +- test/identify/index.spec.js | 10 +- test/nat-manager/nat-manager.node.js | 244 +++++++++++++++++++++++++ test/utils/base-options.js | 3 + 16 files changed, 742 insertions(+), 22 deletions(-) create mode 100644 src/nat-manager.js create mode 100644 test/core/consume-peer-record.spec.js create mode 100644 test/nat-manager/nat-manager.node.js diff --git a/.aegir.js b/.aegir.js index a727c36e91..0e01a44180 100644 --- a/.aegir.js +++ b/.aegir.js @@ -31,6 +31,9 @@ const before = async () => { enabled: true, active: false } + }, + nat: { + enabled: false } } }) @@ -45,7 +48,7 @@ const after = async () => { } module.exports = { - bundlesize: { maxSize: '260kB' }, + bundlesize: { maxSize: '215kB' }, hooks: { pre: before, post: after diff --git a/doc/API.md b/doc/API.md index 222ef660eb..21e144b0ac 100644 --- a/doc/API.md +++ b/doc/API.md @@ -2055,6 +2055,15 @@ This event will be triggered anytime we are disconnected from another peer, rega - `peerId`: instance of [`PeerId`][peer-id] - `protocols`: array of known, supported protocols for the peer (string identifiers) +### libp2p.addressManager + +#### Our addresses have changed + +This could be in response to a peer telling us about addresses they have observed, or +the NatManager performing NAT hole punching. + +`libp2p.addressManager.on('change:addresses', () => {})` + ## Types ### Stats diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index f9e8019410..04993dc969 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -28,6 +28,9 @@ - [Configuring Metrics](#configuring-metrics) - [Configuring PeerStore](#configuring-peerstore) - [Customizing Transports](#customizing-transports) + - [Configuring the NAT Manager](#configuring-the-nat-manager) + - [Browser support](#browser-support) + - [UPnP and NAT-PMP](#upnp-and-nat-pmp) - [Configuration examples](#configuration-examples) ## Overview @@ -733,6 +736,40 @@ const node = await Libp2p.create({ }) ``` +#### Configuring the NAT Manager + +Network Address Translation (NAT) is a function performed by your router to enable multiple devices on your local network to share a single IPv4 address. It's done transparently for outgoing connections, ensuring the correct response traffic is routed to your computer, but if you wish to accept incoming connections some configuration is necessary. + +The NAT manager can be configured as follows: + +```js +const node = await Libp2p.create({ + config: { + nat: { + description: 'my-node', // set as the port mapping description on the router, defaults the current libp2p version and your peer id + enabled: true, // defaults to true + gateway: '192.168.1.1', // leave unset to auto-discover + externalIp: '80.1.1.1', // leave unset to auto-discover + ttl: 7200, // TTL for port mappings (min 20 minutes) + keepAlive: true, // Refresh port mapping after TTL expires + pmp: { + enabled: false, // defaults to false + } + } + } +}) +``` + +##### Browser support + +Browsers cannot open TCP ports or send the UDP datagrams necessary to configure external port mapping - to accept incoming connections in the browser please use a WebRTC transport. + +##### UPnP and NAT-PMP + +By default under nodejs libp2p will attempt to use [UPnP](https://en.wikipedia.org/wiki/Universal_Plug_and_Play) to configure your router to allow incoming connections to any TCP transports that have been configured. + +[NAT-PMP](http://miniupnp.free.fr/nat-pmp.html) is a feature of some modern routers which performs a similar job to UPnP. NAT-PMP is disabled by default, if enabled libp2p will try to use NAT-PMP and will fall back to UPnP if it fails. + ## Configuration examples As libp2p is designed to be a modular networking library, its usage will vary based on individual project needs. We've included links to some existing project configurations for your reference, in case you wish to replicate their configuration: diff --git a/package.json b/package.json index 3dd149c665..183df83685 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,11 @@ "node": ">=12.0.0", "npm": ">=6.0.0" }, + "browser": { + "@motrix/nat-api": false + }, "dependencies": { + "@motrix/nat-api": "^0.3.1", "abort-controller": "^3.0.0", "aggregate-error": "^3.1.0", "any-signal": "^2.1.1", @@ -89,8 +93,11 @@ "node-forge": "^0.10.0", "p-any": "^3.0.0", "p-fifo": "^1.0.0", + "p-retry": "^4.2.0", "p-settle": "^4.0.1", "peer-id": "^0.14.2", + "private-ip": "^2.0.0", + "promisify-es6": "^1.0.3", "protons": "^2.0.0", "retimer": "^2.0.0", "sanitize-filename": "^1.6.3", @@ -132,7 +139,6 @@ "p-defer": "^3.0.0", "p-times": "^3.0.0", "p-wait-for": "^3.2.0", - "promisify-es6": "^1.0.3", "rimraf": "^3.0.2", "sinon": "^9.2.4", "uint8arrays": "^2.0.5" diff --git a/src/address-manager/index.js b/src/address-manager/index.js index 5c9874af33..625432252e 100644 --- a/src/address-manager/index.js +++ b/src/address-manager/index.js @@ -1,6 +1,10 @@ 'use strict' +/** @typedef {import('../types').EventEmitterFactory} Events */ +/** @type Events */ +const EventEmitter = require('events') const multiaddr = require('multiaddr') +const PeerId = require('peer-id') /** * @typedef {import('multiaddr')} Multiaddr @@ -11,7 +15,11 @@ const multiaddr = require('multiaddr') * @property {string[]} [listen = []] - list of multiaddrs string representation to listen. * @property {string[]} [announce = []] - list of multiaddrs string representation to announce. */ -class AddressManager { + +/** + * @fires AddressManager#change:addresses Emitted when a addresses change. + */ +class AddressManager extends EventEmitter { /** * Responsible for managing the peer addresses. * Peers can specify their listen and announce addresses. @@ -19,11 +27,18 @@ class AddressManager { * while the announce addresses will be used for the peer addresses' to other peers in the network. * * @class - * @param {AddressManagerOptions} [options] + * @param {PeerId} peerId - The Peer ID of the node + * @param {object} [options] + * @param {Array} [options.listen = []] - list of multiaddrs string representation to listen. + * @param {Array} [options.announce = []] - list of multiaddrs string representation to announce. */ - constructor ({ listen = [], announce = [] } = {}) { - this.listen = new Set(listen) - this.announce = new Set(announce) + constructor (peerId, { listen = [], announce = [] } = {}) { + super() + + this.peerId = peerId + this.listen = new Set(listen.map(ma => ma.toString())) + this.announce = new Set(announce.map(ma => ma.toString())) + this.observed = new Set() } /** @@ -43,6 +58,45 @@ class AddressManager { getAnnounceAddrs () { return Array.from(this.announce).map((a) => multiaddr(a)) } + + /** + * Get observed multiaddrs. + * + * @returns {Array} + */ + getObservedAddrs () { + return Array.from(this.observed).map((a) => multiaddr(a)) + } + + /** + * Add peer observed addresses + * + * @param {string | Multiaddr} addr + */ + addObservedAddr (addr) { + let ma = multiaddr(addr) + const remotePeer = ma.getPeerId() + + // strip our peer id if it has been passed + if (remotePeer) { + const remotePeerId = PeerId.createFromB58String(remotePeer) + + // use same encoding for comparison + if (remotePeerId.equals(this.peerId)) { + ma = ma.decapsulate(multiaddr(`/p2p/${this.peerId}`)) + } + } + + const addrString = ma.toString() + + // do not trigger the change:addresses event if we already know about this address + if (this.observed.has(addrString)) { + return + } + + this.observed.add(addrString) + this.emit('change:addresses') + } } module.exports = AddressManager diff --git a/src/config.js b/src/config.js index a2334f52e7..6eefa42556 100644 --- a/src/config.js +++ b/src/config.js @@ -59,6 +59,16 @@ const DefaultConfig = { timeout: 10e3 } }, + nat: { + enabled: true, + ttl: 7200, + keepAlive: true, + gateway: null, + externalIp: null, + pmp: { + enabled: false + } + }, peerDiscovery: { autoDial: true }, diff --git a/src/identify/index.js b/src/identify/index.js index d118872c2f..f5d8d4678d 100644 --- a/src/identify/index.js +++ b/src/identify/index.js @@ -43,6 +43,7 @@ class IdentifyService { constructor ({ libp2p }) { this._libp2p = libp2p this.peerStore = libp2p.peerStore + this.addressManager = libp2p.addressManager this.connectionManager = libp2p.connectionManager this.peerId = libp2p.peerId @@ -201,8 +202,9 @@ class IdentifyService { this.peerStore.protoBook.set(id, protocols) this.peerStore.metadataBook.set(id, 'AgentVersion', uint8ArrayFromString(message.agentVersion)) - // TODO: Track our observed address so that we can score it + // TODO: Score our observed addr log('received observed address of %s', observedAddr) + this.addressManager.addObservedAddr(observedAddr) } /** diff --git a/src/index.js b/src/index.js index 86dbc4ce93..16cdddc1c6 100644 --- a/src/index.js +++ b/src/index.js @@ -10,6 +10,7 @@ const EventEmitter = require('events') const errCode = require('err-code') const PeerId = require('peer-id') +const multiaddr = require('multiaddr') const PeerRouting = require('./peer-routing') const ContentRouting = require('./content-routing') @@ -33,6 +34,8 @@ const Registrar = require('./registrar') const ping = require('./ping') const IdentifyService = require('./identify') const IDENTIFY_PROTOCOLS = IdentifyService.multicodecs +const NatManager = require('./nat-manager') +const { updateSelfPeerRecord } = require('./record/utils') /** * @typedef {import('multiaddr')} Multiaddr @@ -133,7 +136,14 @@ class Libp2p extends EventEmitter { // Addresses {listen, announce, noAnnounce} this.addresses = this._options.addresses - this.addressManager = new AddressManager(this._options.addresses) + this.addressManager = new AddressManager(this.peerId, this._options.addresses) + + // when addresses change, update our peer record + this.addressManager.on('change:addresses', () => { + updateSelfPeerRecord(this).catch(err => { + log.error('Error updating self peer record', err) + }) + }) this._modules = this._options.modules this._config = this._options.config @@ -187,6 +197,14 @@ class Libp2p extends EventEmitter { faultTolerance: this._options.transportManager.faultTolerance }) + // Create the Nat Manager + this.natManager = new NatManager({ + peerId: this.peerId, + addressManager: this.addressManager, + transportManager: this.transportManager, + ...this._options.config.nat + }) + // Create the Registrar this.registrar = new Registrar({ peerStore: this.peerStore, @@ -350,6 +368,7 @@ class Libp2p extends EventEmitter { this.metrics && this.metrics.stop() ]) + await this.natManager.stop() await this.transportManager.close() ping.unmount(this) @@ -445,22 +464,32 @@ 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. + * Get a deduplicated list of peer advertising multiaddrs by concatenating + * the listen addresses used by transports with any configured + * announce addresses as well as observed addresses reported by peers. + * + * If Announce addrs are specified, configured listen addresses will be + * ignored though observed addresses will still be included. * * @returns {Multiaddr[]} */ get multiaddrs () { - const announceAddrs = this.addressManager.getAnnounceAddrs() - if (announceAddrs.length) { - return announceAddrs + let addrs = this.addressManager.getAnnounceAddrs().map(ma => ma.toString()) + + if (!addrs.length) { + // no configured announce addrs, add configured listen addresses + addrs = this.transportManager.getAddrs().map(ma => ma.toString()) } + addrs = addrs.concat(this.addressManager.getObservedAddrs().map(ma => ma.toString())) + const announceFilter = this._options.addresses.announceFilter || ((multiaddrs) => multiaddrs) + // dedupe multiaddrs + const addrSet = new Set(addrs) + // Create advertising list - return announceFilter(this.transportManager.getAddrs()) + return announceFilter(Array.from(addrSet).map(str => multiaddr(str))) } /** @@ -539,6 +568,9 @@ class Libp2p extends EventEmitter { const addrs = this.addressManager.getListenAddrs() await this.transportManager.listen(addrs) + // Manage your NATs + this.natManager.start() + // Start PeerStore await this.peerStore.start() diff --git a/src/nat-manager.js b/src/nat-manager.js new file mode 100644 index 0000000000..760a3f7198 --- /dev/null +++ b/src/nat-manager.js @@ -0,0 +1,168 @@ +'use strict' + +const NatAPI = require('@motrix/nat-api') +const debug = require('debug') +const promisify = require('promisify-es6') +const Multiaddr = require('multiaddr') +const log = Object.assign(debug('libp2p:nat'), { + error: debug('libp2p:nat:err') +}) +const { isBrowser } = require('ipfs-utils/src/env') +const retry = require('p-retry') +const isPrivateIp = require('private-ip') +const pkg = require('../package.json') +const errcode = require('err-code') +const { + codes: { ERR_INVALID_PARAMETERS } +} = require('./errors') +const isLoopback = require('libp2p-utils/src/multiaddr/is-loopback') + +/** + * @typedef {import('peer-id')} PeerId + * @typedef {import('./transport-manager')} TransportManager + * @typedef {import('./address-manager')} AddressManager + */ + +function highPort (min = 1024, max = 65535) { + return Math.floor(Math.random() * (max - min + 1) + min) +} + +const DEFAULT_TTL = 7200 + +class NatManager { + /** + * @class + * @param {object} options + * @param {PeerId} options.peerId - The peer ID of the current node + * @param {TransportManager} options.transportManager - A transport manager + * @param {AddressManager} options.addressManager - An address manager + * @param {boolean} options.enabled - Whether to enable the NAT manager + * @param {string} [options.externalIp] - Pass a value to use instead of auto-detection + * @param {string} [options.description] - A string value to use for the port mapping description on the gateway + * @param {number} [options.ttl] - How long UPnP port mappings should last for in seconds (minimum 1200) + * @param {boolean} [options.keepAlive] - Whether to automatically refresh UPnP port mappings when their TTL is reached + * @param {string} [options.gateway] - Pass a value to use instead of auto-detection + * @param {object} [options.pmp] - PMP options + * @param {boolean} [options.pmp.enabled] - Whether to enable PMP as well as UPnP + */ + constructor ({ peerId, addressManager, transportManager, ...options }) { + this._peerId = peerId + this._addressManager = addressManager + this._transportManager = transportManager + + this._enabled = options.enabled + this._externalIp = options.externalIp + this._options = { + description: options.description || `${pkg.name}@${pkg.version} ${this._peerId}`, + ttl: options.ttl || DEFAULT_TTL, + autoUpdate: options.keepAlive || true, + gateway: options.gateway, + enablePMP: Boolean(options.pmp && options.pmp.enabled) + } + + if (this._options.ttl < DEFAULT_TTL) { + throw errcode(new Error(`NatManager ttl should be at least ${DEFAULT_TTL} seconds`), ERR_INVALID_PARAMETERS) + } + } + + /** + * Starts the NAT manager + */ + start () { + if (isBrowser || !this._enabled) { + return + } + + // done async to not slow down startup + this._start().catch((err) => { + // hole punching errors are non-fatal + log.error(err) + }) + } + + async _start () { + const addrs = this._transportManager.getAddrs() + + for (const addr of addrs) { + // try to open uPnP ports for each thin waist address + const { family, host, port, transport } = addr.toOptions() + + if (!addr.isThinWaistAddress() || transport !== 'tcp') { + // only bare tcp addresses + continue + } + + if (isLoopback(addr)) { + continue + } + + if (family !== 'ipv4') { + // ignore ipv6 + continue + } + + const client = this._getClient() + const publicIp = this._externalIp || await client.externalIp() + + if (isPrivateIp(publicIp)) { + throw new Error(`${publicIp} is private - please set config.nat.externalIp to an externally routable IP or ensure you are not behind a double NAT`) + } + + const publicPort = highPort() + + log(`opening uPnP connection from ${publicIp}:${publicPort} to ${host}:${port}`) + + await client.map({ + publicPort, + privatePort: port, + protocol: transport.toUpperCase() + }) + + this._addressManager.addObservedAddr(Multiaddr.fromNodeAddress({ + family: 'IPv4', + address: publicIp, + port: `${publicPort}` + }, transport)) + } + } + + _getClient () { + if (this._client) { + return this._client + } + + const client = new NatAPI(this._options) + const map = promisify(client.map, { context: client }) + const destroy = promisify(client.destroy, { context: client }) + const externalIp = promisify(client.externalIp, { context: client }) + + this._client = { + // these are all network operations so add a retry + map: (...args) => retry(() => map(...args), { onFailedAttempt: log.error }), + destroy: (...args) => retry(() => destroy(...args), { onFailedAttempt: log.error }), + externalIp: (...args) => retry(() => externalIp(...args), { onFailedAttempt: log.error }) + } + + return this._client + } + + /** + * Stops the NAT manager + * + * @async + */ + async stop () { + if (isBrowser || !this._client) { + return + } + + try { + await this._client.destroy() + this._client = null + } catch (err) { + log.error(err) + } + } +} + +module.exports = NatManager diff --git a/test/addresses/address-manager.spec.js b/test/addresses/address-manager.spec.js index 4d58387a23..c27ef2992d 100644 --- a/test/addresses/address-manager.spec.js +++ b/test/addresses/address-manager.spec.js @@ -3,23 +3,32 @@ const { expect } = require('aegir/utils/chai') const multiaddr = require('multiaddr') +const PeerId = require('peer-id') const AddressManager = require('../../src/address-manager') const peerUtils = require('../utils/creators/peer') +const Peers = require('../fixtures/peers') + const listenAddresses = ['/ip4/127.0.0.1/tcp/15006/ws', '/ip4/127.0.0.1/tcp/15008/ws'] const announceAddreses = ['/dns4/peer.io'] describe('Address Manager', () => { + let peerId + + before(async () => { + peerId = await PeerId.createFromJSON(Peers[0]) + }) + it('should not need any addresses', () => { - const am = new AddressManager() + const am = new AddressManager(peerId) expect(am.listen.size).to.equal(0) expect(am.announce.size).to.equal(0) }) it('should return listen multiaddrs on get', () => { - const am = new AddressManager({ + const am = new AddressManager(peerId, { listen: listenAddresses }) @@ -33,7 +42,7 @@ describe('Address Manager', () => { }) it('should return announce multiaddrs on get', () => { - const am = new AddressManager({ + const am = new AddressManager(peerId, { listen: listenAddresses, announce: announceAddreses }) @@ -45,6 +54,75 @@ describe('Address Manager', () => { expect(announceMultiaddrs.length).to.equal(1) expect(announceMultiaddrs[0].equals(multiaddr(announceAddreses[0]))).to.equal(true) }) + + it('should add observed addresses', () => { + const am = new AddressManager(peerId) + + expect(am.observed).to.be.empty() + + am.addObservedAddr('/ip4/123.123.123.123/tcp/39201') + + expect(am.observed).to.have.property('size', 1) + }) + + it('should dedupe added observed addresses', () => { + const ma = '/ip4/123.123.123.123/tcp/39201' + const am = new AddressManager(peerId) + + expect(am.observed).to.be.empty() + + am.addObservedAddr(ma) + am.addObservedAddr(ma) + am.addObservedAddr(ma) + + expect(am.observed).to.have.property('size', 1) + expect(am.observed).to.include(ma) + }) + + it('should only emit one change:addresses event', () => { + const ma = '/ip4/123.123.123.123/tcp/39201' + const am = new AddressManager(peerId) + let eventCount = 0 + + am.on('change:addresses', () => { + eventCount++ + }) + + am.addObservedAddr(ma) + am.addObservedAddr(ma) + am.addObservedAddr(ma) + am.addObservedAddr(`${ma}/p2p/${peerId}`) + am.addObservedAddr(`${ma}/p2p/${peerId.toB58String()}`) + + expect(eventCount).to.equal(1) + }) + + it('should strip our peer address from added observed addresses', () => { + const ma = '/ip4/123.123.123.123/tcp/39201' + const am = new AddressManager(peerId) + + expect(am.observed).to.be.empty() + + am.addObservedAddr(ma) + am.addObservedAddr(`${ma}/p2p/${peerId}`) + + expect(am.observed).to.have.property('size', 1) + expect(am.observed).to.include(ma) + }) + + it('should strip our peer address from added observed addresses in difference formats', () => { + const ma = '/ip4/123.123.123.123/tcp/39201' + const am = new AddressManager(peerId) + + expect(am.observed).to.be.empty() + + am.addObservedAddr(ma) + am.addObservedAddr(`${ma}/p2p/${peerId}`) // base32 CID + am.addObservedAddr(`${ma}/p2p/${peerId.toB58String()}`) // base58btc + + expect(am.observed).to.have.property('size', 1) + expect(am.observed).to.include(ma) + }) }) describe('libp2p.addressManager', () => { diff --git a/test/addresses/addresses.node.js b/test/addresses/addresses.node.js index 2d6bceade5..719e52721a 100644 --- a/test/addresses/addresses.node.js +++ b/test/addresses/addresses.node.js @@ -147,4 +147,26 @@ describe('libp2p.multiaddrs', () => { expect(multiaddrs.includes(listenAddresses[0])).to.equal(false) expect(multiaddrs.includes(listenAddresses[1])).to.equal(false) }) + + it('should include observed addresses in returned multiaddrs', async () => { + [libp2p] = await peerUtils.createPeer({ + started: false, + config: { + ...AddressesOptions, + addresses: { + listen: listenAddresses + } + } + }) + const ma = '/ip4/83.32.123.53/tcp/43928' + + await libp2p.start() + + expect(libp2p.multiaddrs).to.have.lengthOf(listenAddresses.length) + + libp2p.addressManager.addObservedAddr(ma) + + expect(libp2p.multiaddrs).to.have.lengthOf(listenAddresses.length + 1) + expect(libp2p.multiaddrs.map(ma => ma.toString())).to.include(ma) + }) }) diff --git a/test/core/consume-peer-record.spec.js b/test/core/consume-peer-record.spec.js new file mode 100644 index 0000000000..c20a5efad6 --- /dev/null +++ b/test/core/consume-peer-record.spec.js @@ -0,0 +1,46 @@ +'use strict' +/* eslint-env mocha */ + +const Transport = require('libp2p-websockets') +const { NOISE: Crypto } = require('libp2p-noise') + +const Libp2p = require('../../src') +const { createPeerId } = require('../utils/creators/peer') + +describe('Consume peer record', () => { + let libp2p + + beforeEach(async () => { + const [peerId] = await createPeerId() + const config = { + peerId, + modules: { + transport: [Transport], + connEncryption: [Crypto] + } + } + libp2p = await Libp2p.create(config) + }) + + afterEach(async () => { + await libp2p.stop() + }) + + it('should consume peer record when observed addrs are added', async () => { + let done + + libp2p.peerStore.addressBook.consumePeerRecord = () => { + done() + } + + const p = new Promise(resolve => { + done = resolve + }) + + libp2p.addressManager.addObservedAddr('/ip4/123.123.123.123/tcp/3983') + + await p + + libp2p.stop() + }) +}) diff --git a/test/dialing/direct.node.js b/test/dialing/direct.node.js index 0d9d1dd718..33a73d0fbe 100644 --- a/test/dialing/direct.node.js +++ b/test/dialing/direct.node.js @@ -51,7 +51,7 @@ describe('Dialing (direct, TCP)', () => { peerStore = new PeerStore({ peerId: remotePeerId }) remoteTM = new TransportManager({ libp2p: { - addressManager: new AddressManager({ listen: [listenAddr] }), + addressManager: new AddressManager(remotePeerId, { listen: [listenAddr] }), peerId: remotePeerId, peerStore }, diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js index b431c8921b..d7b0d8b2bf 100644 --- a/test/identify/index.spec.js +++ b/test/identify/index.spec.js @@ -21,14 +21,15 @@ 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 AddressManager = require('../../src/address-manager') const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser') const remoteAddr = MULTIADDRS_WEBSOCKETS[0] const listenMaddrs = [multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] describe('Identify', () => { - let localPeer, localPeerStore - let remotePeer, remotePeerStore + let localPeer, localPeerStore, localAddressManager + let remotePeer, remotePeerStore, remoteAddressManager const protocols = [multicodecs.IDENTIFY, multicodecs.IDENTIFY_PUSH] before(async () => { @@ -42,6 +43,9 @@ describe('Identify', () => { remotePeerStore = new PeerStore({ peerId: remotePeer }) remotePeerStore.protoBook.set(remotePeer, protocols) + + localAddressManager = new AddressManager(localPeer) + remoteAddressManager = new AddressManager(remotePeer) }) afterEach(() => { @@ -110,6 +114,7 @@ describe('Identify', () => { libp2p: { peerId: localPeer, connectionManager: new EventEmitter(), + addressManager: localAddressManager, peerStore: localPeerStore, multiaddrs: listenMaddrs, isStarted: () => true, @@ -121,6 +126,7 @@ describe('Identify', () => { libp2p: { peerId: remotePeer, connectionManager: new EventEmitter(), + addressManager: remoteAddressManager, peerStore: remotePeerStore, multiaddrs: listenMaddrs, isStarted: () => true, diff --git a/test/nat-manager/nat-manager.node.js b/test/nat-manager/nat-manager.node.js new file mode 100644 index 0000000000..9bf922e505 --- /dev/null +++ b/test/nat-manager/nat-manager.node.js @@ -0,0 +1,244 @@ +'use strict' +/* eslint-env mocha */ + +const { expect } = require('aegir/utils/chai') +const sinon = require('sinon') +const AddressManager = require('../../src/address-manager') +const TransportManager = require('../../src/transport-manager') +const Transport = require('libp2p-tcp') +const mockUpgrader = require('../utils/mockUpgrader') +const NatManager = require('../../src/nat-manager') +const delay = require('delay') +const peers = require('../fixtures/peers') +const PeerId = require('peer-id') +const { + codes: { ERR_INVALID_PARAMETERS } +} = require('../../src/errors') + +const DEFAULT_ADDRESSES = [ + '/ip4/127.0.0.1/tcp/0', + '/ip4/0.0.0.0/tcp/0' +] + +describe('Nat Manager (TCP)', () => { + const teardown = [] + + async function createNatManager (addrs = DEFAULT_ADDRESSES, natManagerOptions = {}) { + const peerId = await PeerId.createFromJSON(peers[0]) + const addressManager = new AddressManager(peerId, { listen: addrs }) + const transportManager = new TransportManager({ + libp2p: { + peerId, + addressManager, + peerStore: { + addressBook: { + consumePeerRecord: sinon.stub() + } + } + }, + upgrader: mockUpgrader, + onConnection: () => {}, + faultTolerance: TransportManager.FaultTolerance.NO_FATAL + }) + const natManager = new NatManager({ + peerId, + addressManager, + transportManager, + enabled: true, + ...natManagerOptions + }) + + natManager._client = { + externalIp: sinon.stub().resolves('82.3.1.5'), + map: sinon.stub(), + destroy: sinon.stub() + } + + transportManager.add(Transport.prototype[Symbol.toStringTag], Transport) + await transportManager.listen(addressManager.getListenAddrs()) + + teardown.push(async () => { + await natManager.stop() + await transportManager.removeAll() + expect(transportManager._transports.size).to.equal(0) + }) + + return { + natManager, + addressManager, + transportManager + } + } + + afterEach(() => Promise.all(teardown)) + + it('should map TCP connections to external ports', async () => { + const { + natManager, + addressManager, + transportManager + } = await createNatManager() + + let addressChangedEventFired = false + + addressManager.on('change:addresses', () => { + addressChangedEventFired = true + }) + + natManager._client = { + externalIp: sinon.stub().resolves('82.3.1.5'), + map: sinon.stub(), + destroy: sinon.stub() + } + + let observed = addressManager.getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + + await natManager._start() + + observed = addressManager.getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.not.be.empty() + + const internalPorts = transportManager.getAddrs() + .filter(ma => ma.isThinWaistAddress()) + .map(ma => ma.toOptions()) + .filter(({ host, transport }) => host !== '127.0.0.1' && transport === 'tcp') + .map(({ port }) => port) + + expect(natManager._client.map.called).to.be.true() + + internalPorts.forEach(port => { + expect(natManager._client.map.getCall(0).args[0]).to.include({ + privatePort: port, + protocol: 'TCP' + }) + }) + + expect(addressChangedEventFired).to.be.true() + }) + + it('should not map TCP connections when double-natted', async () => { + const { + natManager, + addressManager + } = await createNatManager() + + natManager._client.externalIp = sinon.stub().resolves('192.168.1.1') + + let observed = addressManager.getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + + await expect(natManager._start()).to.eventually.be.rejectedWith(/double NAT/) + + observed = addressManager.getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + + expect(natManager._client.map.called).to.be.false() + }) + + it('should do nothing when disabled', async () => { + const { + natManager + } = await createNatManager(DEFAULT_ADDRESSES, { + enabled: false + }) + + natManager.start() + + await delay(100) + + expect(natManager._client.externalIp.called).to.be.false() + expect(natManager._client.map.called).to.be.false() + }) + + it('should not map non-ipv4 connections to external ports', async () => { + const { + natManager, + addressManager + } = await createNatManager([ + '/ip6/::/tcp/5001' + ]) + + let observed = addressManager.getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + + await natManager._start() + + observed = addressManager.getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + }) + + it('should not map non-ipv6 loopback connections to external ports', async () => { + const { + natManager, + addressManager + } = await createNatManager([ + '/ip6/::1/tcp/5001' + ]) + + let observed = addressManager.getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + + await natManager._start() + + observed = addressManager.getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + }) + + it('should not map non-TCP connections to external ports', async () => { + const { + natManager, + addressManager + } = await createNatManager([ + '/ip4/0.0.0.0/utp' + ]) + + let observed = addressManager.getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + + await natManager._start() + + observed = addressManager.getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + }) + + it('should not map loopback connections to external ports', async () => { + const { + natManager, + addressManager + } = await createNatManager([ + '/ip4/127.0.0.1/tcp/5900' + ]) + + let observed = addressManager.getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + + await natManager._start() + + observed = addressManager.getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + }) + + it('should not map non-thin-waist connections to external ports', async () => { + const { + natManager, + addressManager + } = await createNatManager([ + '/ip4/0.0.0.0/tcp/5900/sctp/49832' + ]) + + let observed = addressManager.getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + + await natManager._start() + + observed = addressManager.getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + }) + + it('should specify large enough TTL', () => { + expect(() => { + new NatManager({ ttl: 5 }) // eslint-disable-line no-new + }).to.throw().with.property('code', ERR_INVALID_PARAMETERS) + }) +}) diff --git a/test/utils/base-options.js b/test/utils/base-options.js index 25ea441bcd..e2db4f85a2 100644 --- a/test/utils/base-options.js +++ b/test/utils/base-options.js @@ -16,6 +16,9 @@ module.exports = { hop: { enabled: false } + }, + nat: { + enabled: false } } } From ed5f8f853f5d8064552f1876db757e4211aafac6 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 27 Jan 2021 15:23:56 +0100 Subject: [PATCH 101/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 183df83685..42b03ff0d5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.30.3", + "version": "0.30.4", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From 28f52bbf7514e3b2cca95ce7193aca97fc8352be Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 27 Jan 2021 15:23:56 +0100 Subject: [PATCH 102/447] chore: release version v0.30.4 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 851076c0c9..6f91d07033 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.30.4](https://github.com/libp2p/js-libp2p/compare/v0.30.3...v0.30.4) (2021-01-27) + + +### Features + +* add UPnP NAT manager ([#810](https://github.com/libp2p/js-libp2p/issues/810)) ([0a6bc0d](https://github.com/libp2p/js-libp2p/commit/0a6bc0d1013dfd80ab600e8f74c1544b433ece29)) + + + ## [0.30.3](https://github.com/libp2p/js-libp2p/compare/v0.30.2...v0.30.3) (2021-01-27) From f06e06a0065e491f33007ac25915c42d03a42055 Mon Sep 17 00:00:00 2001 From: Kevin Lacker Date: Thu, 28 Jan 2021 02:06:11 -0800 Subject: [PATCH 103/447] chore: update bootstrapers example url --- examples/discovery-mechanisms/bootstrapers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/discovery-mechanisms/bootstrapers.js b/examples/discovery-mechanisms/bootstrapers.js index 50da9eb4fa..384f0b5eec 100644 --- a/examples/discovery-mechanisms/bootstrapers.js +++ b/examples/discovery-mechanisms/bootstrapers.js @@ -1,6 +1,6 @@ 'use strict' -// Find this list at: https://github.com/ipfs/js-ipfs/blob/master/src/core/runtime/config-nodejs.json +// Find this list at: https://github.com/ipfs/js-ipfs/blob/master/packages/ipfs-core/src/runtime/config-nodejs.js const bootstrapers = [ '/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ', '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', From eeda05688330c17b810bf47544ef977386623317 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 28 Jan 2021 16:41:04 +0100 Subject: [PATCH 104/447] fix: create has optional peer id type (#875) --- src/index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index 16cdddc1c6..07d5837914 100644 --- a/src/index.js +++ b/src/index.js @@ -82,10 +82,12 @@ const { updateSelfPeerRecord } = require('./record/utils') * @property {import('./transport-manager').TransportManagerOptions} [transportManager] * @property {PeerStoreOptions & import('./peer-store/persistent').PersistentPeerStoreOptions} [peerStore] * @property {Libp2pConfig} [config] + * + * @typedef {Object} constructorOptions * @property {PeerId} peerId * * @typedef {Object} CreateOptions - * @property {PeerId} peerId + * @property {PeerId} [peerId] * * @extends {EventEmitter} * @fires Libp2p#error Emitted when an error occurs @@ -101,12 +103,14 @@ class Libp2p extends EventEmitter { */ static async create (options) { if (options.peerId) { + // @ts-ignore 'Libp2pOptions & CreateOptions' is not assignable to 'Libp2pOptions & constructorOptions' return new Libp2p(options) } const peerId = await PeerId.create() options.peerId = peerId + // @ts-ignore 'Libp2pOptions & CreateOptions' is not assignable to 'Libp2pOptions & constructorOptions' return new Libp2p(options) } @@ -114,7 +118,7 @@ class Libp2p extends EventEmitter { * Libp2p node. * * @class - * @param {Libp2pOptions} _options + * @param {Libp2pOptions & constructorOptions} _options */ constructor (_options) { super() From 74d07e5e8c2852736dfc1f4a22421f1677d5677d Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 28 Jan 2021 16:50:46 +0100 Subject: [PATCH 105/447] chore: update contributors --- package.json | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 42b03ff0d5..2d75c07f36 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.30.4", + "version": "0.30.5", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", @@ -154,14 +154,14 @@ "Friedel Ziegelmayer ", "Maciej Krüger ", "Hugo Dias ", - "dirkmc ", "Volker Mische ", + "dirkmc ", "Richard Littauer ", "a1300 ", - "Elven ", + "Ryan Bell ", "Andrew Nesbitt ", + "Elven ", "Giovanni T. Parra ", - "Ryan Bell ", "Samlior ", "Thomas Eizinger ", "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", @@ -170,11 +170,12 @@ "Joel Gustafson ", "Julien Bouquillon ", "Kevin Kwok ", + "Kevin Lacker ", + "Ethan Lam ", "Nuno Nogueira ", "Dmitriy Ryajov ", "RasmusErik Voel Jensen ", "Diogo Silva ", - "isan_rivkin ", "Smite Chow ", "Soeren ", "Sönke Hahn ", @@ -185,13 +186,13 @@ "robertkiel ", "Cindy Wu ", "Chris Bratlien ", - "Florian-Merle ", - "Francis Gulotta ", - "Felipe Martins ", "ebinks ", - "Henrique Dias ", + "Francis Gulotta ", + "Florian-Merle ", "Bernd Strehl ", - "Fei Liu ", - "Ethan Lam " + "Henrique Dias ", + "isan_rivkin ", + "Felipe Martins ", + "Fei Liu " ] } From a64c02838c9f45996d9c3b584441badd662b0f64 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 28 Jan 2021 16:50:46 +0100 Subject: [PATCH 106/447] chore: release version v0.30.5 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f91d07033..fccf604470 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.30.5](https://github.com/libp2p/js-libp2p/compare/v0.30.4...v0.30.5) (2021-01-28) + + +### Bug Fixes + +* create has optional peer id type ([#875](https://github.com/libp2p/js-libp2p/issues/875)) ([eeda056](https://github.com/libp2p/js-libp2p/commit/eeda05688330c17b810bf47544ef977386623317)) + + + ## [0.30.4](https://github.com/libp2p/js-libp2p/compare/v0.30.3...v0.30.4) (2021-01-27) From ce2a624a09b3107c0b2b4752e666804ecea54fb5 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 29 Jan 2021 13:09:59 +0000 Subject: [PATCH 107/447] fix: unref nat manager retries (#877) The retry operation in the NAT Manager can prevent node from shutting down, so unref the retries so they don't keep adding work to the event loop. --- src/nat-manager.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nat-manager.js b/src/nat-manager.js index 760a3f7198..93c0251f81 100644 --- a/src/nat-manager.js +++ b/src/nat-manager.js @@ -138,9 +138,9 @@ class NatManager { this._client = { // these are all network operations so add a retry - map: (...args) => retry(() => map(...args), { onFailedAttempt: log.error }), - destroy: (...args) => retry(() => destroy(...args), { onFailedAttempt: log.error }), - externalIp: (...args) => retry(() => externalIp(...args), { onFailedAttempt: log.error }) + map: (...args) => retry(() => map(...args), { onFailedAttempt: log.error, unref: true }), + destroy: (...args) => retry(() => destroy(...args), { onFailedAttempt: log.error, unref: true }), + externalIp: (...args) => retry(() => externalIp(...args), { onFailedAttempt: log.error, unref: true }) } return this._client From 3e7594f69733bf374b374a6065458fa6cae81c5f Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 29 Jan 2021 14:32:13 +0100 Subject: [PATCH 108/447] fix: peer discovery type in config (#878) to any --- src/index.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/index.js b/src/index.js index 07d5837914..034aaa5006 100644 --- a/src/index.js +++ b/src/index.js @@ -51,9 +51,6 @@ const { updateSelfPeerRecord } = require('./record/utils') * @typedef {Object} PeerStoreOptions * @property {boolean} persistence * - * @typedef {Object} PeerDiscoveryOptions - * @property {boolean} autoDial - * * @typedef {Object} RelayOptions * @property {boolean} enabled * @property {import('./circuit').RelayAdvertiseOptions} advertise @@ -62,7 +59,7 @@ const { updateSelfPeerRecord } = require('./record/utils') * * @typedef {Object} Libp2pConfig * @property {Object} [dht] dht module options - * @property {PeerDiscoveryOptions} [peerDiscovery] + * @property {Object} [peerDiscovery] * @property {Pubsub} [pubsub] pubsub module options * @property {RelayOptions} [relay] * @property {Record} [transport] transport options indexed by transport key From 5dbbeef3113bc8f918042b554d1725de2d03d94c Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 29 Jan 2021 14:39:41 +0100 Subject: [PATCH 109/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2d75c07f36..daca96438f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.30.5", + "version": "0.30.6", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From 8d3b61710ab75a16742f018fb09bfa0cee31e00d Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 29 Jan 2021 14:39:41 +0100 Subject: [PATCH 110/447] chore: release version v0.30.6 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fccf604470..03b89896ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## [0.30.6](https://github.com/libp2p/js-libp2p/compare/v0.30.5...v0.30.6) (2021-01-29) + + +### Bug Fixes + +* peer discovery type in config ([#878](https://github.com/libp2p/js-libp2p/issues/878)) ([3e7594f](https://github.com/libp2p/js-libp2p/commit/3e7594f69733bf374b374a6065458fa6cae81c5f)) +* unref nat manager retries ([#877](https://github.com/libp2p/js-libp2p/issues/877)) ([ce2a624](https://github.com/libp2p/js-libp2p/commit/ce2a624a09b3107c0b2b4752e666804ecea54fb5)) + + + ## [0.30.5](https://github.com/libp2p/js-libp2p/compare/v0.30.4...v0.30.5) (2021-01-28) From a36b2112aafcee309a02de0cff5440cf69cd53a7 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Mon, 1 Feb 2021 17:32:57 +0000 Subject: [PATCH 111/447] fix: do not add observed address received from peers (#882) --- src/identify/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/identify/index.js b/src/identify/index.js index f5d8d4678d..127891e845 100644 --- a/src/identify/index.js +++ b/src/identify/index.js @@ -202,9 +202,9 @@ class IdentifyService { this.peerStore.protoBook.set(id, protocols) this.peerStore.metadataBook.set(id, 'AgentVersion', uint8ArrayFromString(message.agentVersion)) - // TODO: Score our observed addr + // TODO: Add and score our observed addr log('received observed address of %s', observedAddr) - this.addressManager.addObservedAddr(observedAddr) + // this.addressManager.addObservedAddr(observedAddr) } /** From 3abf4aeb351a35c09e22fe2b45be0b1af348f15f Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 1 Feb 2021 18:40:05 +0100 Subject: [PATCH 112/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index daca96438f..1469115730 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.30.6", + "version": "0.30.7", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From aec8e3d3bb1b245051b60c2a890550d262d5b062 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 1 Feb 2021 18:40:05 +0100 Subject: [PATCH 113/447] chore: release version v0.30.7 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03b89896ab..b539ab550c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.30.7](https://github.com/libp2p/js-libp2p/compare/v0.30.6...v0.30.7) (2021-02-01) + + +### Bug Fixes + +* do not add observed address received from peers ([#882](https://github.com/libp2p/js-libp2p/issues/882)) ([a36b211](https://github.com/libp2p/js-libp2p/commit/a36b2112aafcee309a02de0cff5440cf69cd53a7)) + + + ## [0.30.6](https://github.com/libp2p/js-libp2p/compare/v0.30.5...v0.30.6) (2021-01-29) From a150ea60c546634835e1031509fb37c5da1c8d42 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 8 Feb 2021 11:03:42 +0100 Subject: [PATCH 114/447] chore: add peer and content routing example tests (#848) --- .github/workflows/main.yml | 7 ++++ examples/peer-and-content-routing/test-1.js | 36 +++++++++++++++++++ examples/peer-and-content-routing/test-2.js | 40 +++++++++++++++++++++ examples/peer-and-content-routing/test.js | 11 ++++++ 4 files changed, 94 insertions(+) create mode 100644 examples/peer-and-content-routing/test-1.js create mode 100644 examples/peer-and-content-routing/test-2.js create mode 100644 examples/peer-and-content-routing/test.js diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a9f3143036..c093c64ed6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -100,6 +100,13 @@ jobs: - uses: actions/checkout@v2 - run: yarn - run: cd examples && yarn && npm run test -- discovery-mechanisms + test-peer-and-content-routing-example: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: cd examples && yarn && npm run test -- peer-and-content-routing test-pnet-example: needs: check runs-on: ubuntu-latest diff --git a/examples/peer-and-content-routing/test-1.js b/examples/peer-and-content-routing/test-1.js new file mode 100644 index 0000000000..72f3ba70f9 --- /dev/null +++ b/examples/peer-and-content-routing/test-1.js @@ -0,0 +1,36 @@ +'use strict' + +const path = require('path') +const execa = require('execa') +const pWaitFor = require('p-wait-for') +const uint8ArrayToString = require('uint8arrays/to-string') + +async function test() { + process.stdout.write('1.js\n') + + const addrs = [] + let foundIt = false + const proc = execa('node', [path.join(__dirname, '1.js')], { + cwd: path.resolve(__dirname), + all: true + }) + + proc.all.on('data', async (data) => { + process.stdout.write(data) + + const line = uint8ArrayToString(data) + + // Discovered peer + if (!foundIt && line.includes('Found it, multiaddrs are:')) { + foundIt = true + } + + addrs.push(line) + }) + + await pWaitFor(() => addrs.length === 2) + + proc.kill() +} + +module.exports = test diff --git a/examples/peer-and-content-routing/test-2.js b/examples/peer-and-content-routing/test-2.js new file mode 100644 index 0000000000..546c3cddc6 --- /dev/null +++ b/examples/peer-and-content-routing/test-2.js @@ -0,0 +1,40 @@ +'use strict' + +const path = require('path') +const execa = require('execa') +const pDefer = require('p-defer') +const uint8ArrayToString = require('uint8arrays/to-string') + +const providedCopy = 'is providing' +const foundCopy = 'Found provider:' + +async function test() { + process.stdout.write('2.js\n') + const providedDefer = pDefer() + const foundDefer = pDefer() + + const proc = execa('node', [path.join(__dirname, '2.js')], { + cwd: path.resolve(__dirname), + all: true + }) + + proc.all.on('data', async (data) => { + process.stdout.write(data) + + const line = uint8ArrayToString(data) + + if (line.includes(providedCopy)) { + providedDefer.resolve() + } else if (line.includes(foundCopy)) { + foundDefer.resolve() + } + }) + + await Promise.all([ + providedDefer.promise, + foundDefer.promise + ]) + proc.kill() +} + +module.exports = test diff --git a/examples/peer-and-content-routing/test.js b/examples/peer-and-content-routing/test.js new file mode 100644 index 0000000000..1ccbda6d0b --- /dev/null +++ b/examples/peer-and-content-routing/test.js @@ -0,0 +1,11 @@ +'use strict' + +const test1 = require('./test-1') +const test2 = require('./test-2') + +async function test() { + await test1() + await test2() +} + +module.exports = test From b1079474de108d383913992e0b5d55ff9b2ae275 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 10 Feb 2021 15:40:19 +0100 Subject: [PATCH 115/447] chore: add protocol and stream muxing example tests (#849) --- .github/workflows/main.yml | 7 ++++ examples/protocol-and-stream-muxing/test-1.js | 31 +++++++++++++++ examples/protocol-and-stream-muxing/test-2.js | 38 +++++++++++++++++++ examples/protocol-and-stream-muxing/test-3.js | 37 ++++++++++++++++++ examples/protocol-and-stream-muxing/test.js | 13 +++++++ 5 files changed, 126 insertions(+) create mode 100644 examples/protocol-and-stream-muxing/test-1.js create mode 100644 examples/protocol-and-stream-muxing/test-2.js create mode 100644 examples/protocol-and-stream-muxing/test-3.js create mode 100644 examples/protocol-and-stream-muxing/test.js diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c093c64ed6..b2b27ad0fe 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -114,3 +114,10 @@ jobs: - uses: actions/checkout@v2 - run: yarn - run: cd examples && yarn && npm run test -- pnet + test-protocol-and-stream-muxing-example: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: cd examples && yarn && npm run test -- protocol-and-stream-muxing diff --git a/examples/protocol-and-stream-muxing/test-1.js b/examples/protocol-and-stream-muxing/test-1.js new file mode 100644 index 0000000000..35314ab9c6 --- /dev/null +++ b/examples/protocol-and-stream-muxing/test-1.js @@ -0,0 +1,31 @@ +'use strict' + +const path = require('path') +const execa = require('execa') +const pDefer = require('p-defer') +const uint8ArrayToString = require('uint8arrays/to-string') + +async function test() { + const messageDefer = pDefer() + process.stdout.write('1.js\n') + + const proc = execa('node', [path.join(__dirname, '1.js')], { + cwd: path.resolve(__dirname), + all: true + }) + + proc.all.on('data', async (data) => { + process.stdout.write(data) + + const line = uint8ArrayToString(data) + + if (line.includes('my own protocol, wow!')) { + messageDefer.resolve() + } + }) + + await messageDefer.promise + proc.kill() +} + +module.exports = test diff --git a/examples/protocol-and-stream-muxing/test-2.js b/examples/protocol-and-stream-muxing/test-2.js new file mode 100644 index 0000000000..e8d934cb71 --- /dev/null +++ b/examples/protocol-and-stream-muxing/test-2.js @@ -0,0 +1,38 @@ +'use strict' + +const path = require('path') +const execa = require('execa') +const pWaitFor = require('p-wait-for') +const uint8ArrayToString = require('uint8arrays/to-string') + +const messages = [ + 'protocol (a)', + 'protocol (b)', + 'another stream on protocol (b)' +] + +async function test() { + process.stdout.write('2.js\n') + + let count = 0 + const proc = execa('node', [path.join(__dirname, '2.js')], { + cwd: path.resolve(__dirname), + all: true + }) + + proc.all.on('data', async (data) => { + process.stdout.write(data) + + const line = uint8ArrayToString(data) + + if (messages.find((m) => line.includes(m))) { + count += 1 + } + }) + + await pWaitFor(() => count === messages.length) + + proc.kill() +} + +module.exports = test diff --git a/examples/protocol-and-stream-muxing/test-3.js b/examples/protocol-and-stream-muxing/test-3.js new file mode 100644 index 0000000000..e6dff441f2 --- /dev/null +++ b/examples/protocol-and-stream-muxing/test-3.js @@ -0,0 +1,37 @@ +'use strict' + +const path = require('path') +const execa = require('execa') +const pWaitFor = require('p-wait-for') +const uint8ArrayToString = require('uint8arrays/to-string') + +const messages = [ + 'from 1 to 2', + 'from 2 to 1' +] + +async function test() { + process.stdout.write('3.js\n') + + let count = 0 + const proc = execa('node', [path.join(__dirname, '3.js')], { + cwd: path.resolve(__dirname), + all: true + }) + + proc.all.on('data', async (data) => { + process.stdout.write(data) + + const line = uint8ArrayToString(data) + + if (messages.find((m) => line.includes(m))) { + count += 1 + } + }) + + await pWaitFor(() => count === messages.length) + + proc.kill() +} + +module.exports = test diff --git a/examples/protocol-and-stream-muxing/test.js b/examples/protocol-and-stream-muxing/test.js new file mode 100644 index 0000000000..72fa27ee7c --- /dev/null +++ b/examples/protocol-and-stream-muxing/test.js @@ -0,0 +1,13 @@ +'use strict' + +const test1 = require('./test-1') +const test2 = require('./test-2') +const test3 = require('./test-3') + +async function test() { + await test1() + await test2() + await test3() +} + +module.exports = test From f6a4cad827c915df63422a1b18642181ebb68560 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 10 Feb 2021 21:00:40 +0100 Subject: [PATCH 116/447] chore: add pubsub example tests (#850) --- .github/workflows/main.yml | 7 +++ examples/pubsub/message-filtering/test.js | 67 +++++++++++++++++++++++ examples/pubsub/test-1.js | 30 ++++++++++ examples/pubsub/test.js | 11 ++++ 4 files changed, 115 insertions(+) create mode 100644 examples/pubsub/message-filtering/test.js create mode 100644 examples/pubsub/test-1.js create mode 100644 examples/pubsub/test.js diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b2b27ad0fe..e4fe13d5bd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -121,3 +121,10 @@ jobs: - uses: actions/checkout@v2 - run: yarn - run: cd examples && yarn && npm run test -- protocol-and-stream-muxing + test-pubsub-example: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: cd examples && yarn && npm run test -- pubsub diff --git a/examples/pubsub/message-filtering/test.js b/examples/pubsub/message-filtering/test.js new file mode 100644 index 0000000000..229e226909 --- /dev/null +++ b/examples/pubsub/message-filtering/test.js @@ -0,0 +1,67 @@ +'use strict' + +const path = require('path') +const execa = require('execa') +const pDefer = require('p-defer') +const uint8ArrayToString = require('uint8arrays/to-string') + +const stdout = [ + { + topic: 'banana', + messageCount: 2 + }, + { + topic: 'apple', + messageCount: 2 + }, + { + topic: 'car', + messageCount: 0 + }, + { + topic: 'orange', + messageCount: 2 + }, +] + +async function test () { + const defer = pDefer() + let topicCount = 0 + let topicMessageCount = 0 + + process.stdout.write('message-filtering/1.js\n') + + const proc = execa('node', [path.join(__dirname, '1.js')], { + cwd: path.resolve(__dirname), + all: true + }) + + proc.all.on('data', async (data) => { + // End + if (topicCount === stdout.length) { + defer.resolve() + proc.all.removeAllListeners('data') + } + + process.stdout.write(data) + const line = uint8ArrayToString(data) + + if (stdout[topicCount] && line.includes(stdout[topicCount].topic)) { + // Validate previous number of messages + if (topicCount > 0 && topicMessageCount > stdout[topicCount - 1].messageCount) { + defer.reject() + throw new Error(`topic ${stdout[topicCount - 1].topic} had ${topicMessageCount} messages instead of ${stdout[topicCount - 1].messageCount}`) + } + + topicCount++ + topicMessageCount = 0 + } else { + topicMessageCount++ + } + }) + + await defer.promise + proc.kill() +} + +module.exports = test diff --git a/examples/pubsub/test-1.js b/examples/pubsub/test-1.js new file mode 100644 index 0000000000..708b203906 --- /dev/null +++ b/examples/pubsub/test-1.js @@ -0,0 +1,30 @@ +'use strict' + +const path = require('path') +const execa = require('execa') +const pDefer = require('p-defer') +const uint8ArrayToString = require('uint8arrays/to-string') + +async function test () { + const defer = pDefer() + process.stdout.write('1.js\n') + + const proc = execa('node', [path.join(__dirname, '1.js')], { + cwd: path.resolve(__dirname), + all: true + }) + + proc.all.on('data', async (data) => { + process.stdout.write(data) + const line = uint8ArrayToString(data) + + if (line.includes('node1 received: Bird bird bird, bird is the word!')) { + defer.resolve() + } + }) + + await defer.promise + proc.kill() +} + +module.exports = test diff --git a/examples/pubsub/test.js b/examples/pubsub/test.js new file mode 100644 index 0000000000..987c351a4d --- /dev/null +++ b/examples/pubsub/test.js @@ -0,0 +1,11 @@ +'use strict' + +const test1 = require('./test-1') +const testMessageFiltering = require('./message-filtering/test') + +async function test() { + await test1() + await testMessageFiltering() +} + +module.exports = test From 1af8472dc6bbea24b20fbc54755d7cfbf9a2775d Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 11 Feb 2021 11:12:23 +0100 Subject: [PATCH 117/447] chore: add transports example (#851) --- .aegir.js | 2 +- .github/workflows/main.yml | 17 ++++++++++----- examples/transports/test-1.js | 38 ++++++++++++++++++++++++++++++++ examples/transports/test-2.js | 30 +++++++++++++++++++++++++ examples/transports/test-3.js | 41 +++++++++++++++++++++++++++++++++++ examples/transports/test.js | 13 +++++++++++ test/dialing/direct.spec.js | 2 -- 7 files changed, 135 insertions(+), 8 deletions(-) create mode 100644 examples/transports/test-1.js create mode 100644 examples/transports/test-2.js create mode 100644 examples/transports/test-3.js create mode 100644 examples/transports/test.js diff --git a/.aegir.js b/.aegir.js index 0e01a44180..656414f86a 100644 --- a/.aegir.js +++ b/.aegir.js @@ -48,7 +48,7 @@ const after = async () => { } module.exports = { - bundlesize: { maxSize: '215kB' }, + bundlesize: { maxSize: '220kB' }, hooks: { pre: before, post: after diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e4fe13d5bd..51a06d4968 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -102,11 +102,11 @@ jobs: - run: cd examples && yarn && npm run test -- discovery-mechanisms test-peer-and-content-routing-example: needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - run: yarn - - run: cd examples && yarn && npm run test -- peer-and-content-routing + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: cd examples && yarn && npm run test -- peer-and-content-routing test-pnet-example: needs: check runs-on: ubuntu-latest @@ -128,3 +128,10 @@ jobs: - uses: actions/checkout@v2 - run: yarn - run: cd examples && yarn && npm run test -- pubsub + test-transports-example: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: cd examples && yarn && npm run test -- transports diff --git a/examples/transports/test-1.js b/examples/transports/test-1.js new file mode 100644 index 0000000000..ce4a37a2f2 --- /dev/null +++ b/examples/transports/test-1.js @@ -0,0 +1,38 @@ +'use strict' + +const path = require('path') +const execa = require('execa') +const pDefer = require('p-defer') +const uint8ArrayToString = require('uint8arrays/to-string') + +async function test () { + const deferStarted = pDefer() + const deferListen = pDefer() + + process.stdout.write('1.js\n') + + const proc = execa('node', [path.join(__dirname, '1.js')], { + cwd: path.resolve(__dirname), + all: true + }) + + proc.all.on('data', async (data) => { + process.stdout.write(data) + const line = uint8ArrayToString(data) + + + if (line.includes('node has started (true/false): true')) { + deferStarted.resolve() + } else if (line.includes('p2p')) { + deferListen.resolve() + } + }) + + await Promise.all([ + deferStarted.promise, + deferListen.promise + ]) + proc.kill() +} + +module.exports = test diff --git a/examples/transports/test-2.js b/examples/transports/test-2.js new file mode 100644 index 0000000000..fcef26e590 --- /dev/null +++ b/examples/transports/test-2.js @@ -0,0 +1,30 @@ +'use strict' + +const path = require('path') +const execa = require('execa') +const pDefer = require('p-defer') +const uint8ArrayToString = require('uint8arrays/to-string') + +async function test () { + const defer = pDefer() + process.stdout.write('2.js\n') + + const proc = execa('node', [path.join(__dirname, '2.js')], { + cwd: path.resolve(__dirname), + all: true + }) + + proc.all.on('data', async (data) => { + process.stdout.write(data) + const line = uint8ArrayToString(data) + + if (line.includes('Hello p2p world!')) { + defer.resolve() + } + }) + + await defer.promise + proc.kill() +} + +module.exports = test diff --git a/examples/transports/test-3.js b/examples/transports/test-3.js new file mode 100644 index 0000000000..d52fb951e5 --- /dev/null +++ b/examples/transports/test-3.js @@ -0,0 +1,41 @@ +'use strict' + +const path = require('path') +const execa = require('execa') +const pDefer = require('p-defer') +const uint8ArrayToString = require('uint8arrays/to-string') + +async function test () { + const deferNode1 = pDefer() + const deferNode2 = pDefer() + const deferNode3 = pDefer() + + process.stdout.write('3.js\n') + + const proc = execa('node', [path.join(__dirname, '3.js')], { + cwd: path.resolve(__dirname), + all: true + }) + + proc.all.on('data', async (data) => { + process.stdout.write(data) + const line = uint8ArrayToString(data) + + if (line.includes('node 1 dialed to node 2 successfully')) { + deferNode1.resolve() + } else if (line.includes('node 2 dialed to node 3 successfully')) { + deferNode2.resolve() + } else if (line.includes('node 3 failed to dial to node 1 with:')) { + deferNode3.resolve() + } + }) + + await Promise.all([ + deferNode1.promise, + deferNode2.promise, + deferNode3.promise + ]) + proc.kill() +} + +module.exports = test diff --git a/examples/transports/test.js b/examples/transports/test.js new file mode 100644 index 0000000000..72fa27ee7c --- /dev/null +++ b/examples/transports/test.js @@ -0,0 +1,13 @@ +'use strict' + +const test1 = require('./test-1') +const test2 = require('./test-2') +const test3 = require('./test-3') + +async function test() { + await test1() + await test2() + await test3() +} + +module.exports = test diff --git a/test/dialing/direct.spec.js b/test/dialing/direct.spec.js index 5ccd008ef7..782d947164 100644 --- a/test/dialing/direct.spec.js +++ b/test/dialing/direct.spec.js @@ -119,7 +119,6 @@ describe('Dialing (direct, WebSockets)', () => { await expect(dialer.connectToPeer(unsupportedAddr)) .to.eventually.be.rejectedWith(AggregateError) - .and.to.have.nested.property('._errors[0].code', ErrorCodes.ERR_TRANSPORT_DIAL_FAILED) }) it('should be able to connect to a given peer', async () => { @@ -151,7 +150,6 @@ describe('Dialing (direct, WebSockets)', () => { await expect(dialer.connectToPeer(peerId)) .to.eventually.be.rejectedWith(AggregateError) - .and.to.have.nested.property('._errors[0].code', ErrorCodes.ERR_TRANSPORT_DIAL_FAILED) }) it('should abort dials on queue task timeout', async () => { From 46cb46188ad905f0fdf2bb2edddfdf609005a4f6 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 11 Feb 2021 11:37:11 +0100 Subject: [PATCH 118/447] chore: add discovery example with relay and pubsub discovery (#855) --- .github/workflows/main.yml | 14 ++-- examples/discovery-mechanisms/3.js | 68 ++++++++++++++++++ examples/discovery-mechanisms/README.md | 95 ++++++++++++++++++++++++- examples/discovery-mechanisms/test-3.js | 35 +++++++++ examples/discovery-mechanisms/test.js | 2 + examples/package.json | 2 + 6 files changed, 208 insertions(+), 8 deletions(-) create mode 100644 examples/discovery-mechanisms/3.js create mode 100644 examples/discovery-mechanisms/test-3.js diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 51a06d4968..375e35e12c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -79,6 +79,13 @@ jobs: - uses: actions/checkout@v2 - run: yarn - run: cd examples && yarn && npm run test -- connection-encryption + test-discovery-mechanisms-example: + needs: check + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: cd examples && yarn && npm run test -- discovery-mechanisms test-echo-example: needs: check runs-on: ubuntu-latest @@ -93,13 +100,6 @@ jobs: - uses: actions/checkout@v2 - run: yarn - run: cd examples && yarn && npm run test -- libp2p-in-the-browser - test-discovery-mechanisms-example: - needs: check - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - run: yarn - - run: cd examples && yarn && npm run test -- discovery-mechanisms test-peer-and-content-routing-example: needs: check runs-on: ubuntu-latest diff --git a/examples/discovery-mechanisms/3.js b/examples/discovery-mechanisms/3.js new file mode 100644 index 0000000000..6eaa96a91f --- /dev/null +++ b/examples/discovery-mechanisms/3.js @@ -0,0 +1,68 @@ +/* eslint-disable no-console */ +'use strict' + +const Libp2p = require('../../') +const TCP = require('libp2p-tcp') +const Mplex = require('libp2p-mplex') +const { NOISE } = require('libp2p-noise') +const Gossipsub = require('libp2p-gossipsub') +const Bootstrap = require('libp2p-bootstrap') +const PubsubPeerDiscovery = require('libp2p-pubsub-peer-discovery') + +const createRelayServer = require('libp2p-relay-server') + +const createNode = async (bootstrapers) => { + const node = await Libp2p.create({ + addresses: { + listen: ['/ip4/0.0.0.0/tcp/0'] + }, + modules: { + transport: [TCP], + streamMuxer: [Mplex], + connEncryption: [NOISE], + pubsub: Gossipsub, + peerDiscovery: [Bootstrap, PubsubPeerDiscovery] + }, + config: { + peerDiscovery: { + [PubsubPeerDiscovery.tag]: { + interval: 1000, + enabled: true + }, + [Bootstrap.tag]: { + enabled: true, + list: bootstrapers + } + } + } + }) + + return node +} + +;(async () => { + const relay = await createRelayServer({ + listenAddresses: ['/ip4/0.0.0.0/tcp/0'] + }) + console.log(`libp2p relay starting with id: ${relay.peerId.toB58String()}`) + await relay.start() + const relayMultiaddrs = relay.multiaddrs.map((m) => `${m.toString()}/p2p/${relay.peerId.toB58String()}`) + + const [node1, node2] = await Promise.all([ + createNode(relayMultiaddrs), + createNode(relayMultiaddrs) + ]) + + node1.on('peer:discovery', (peerId) => { + console.log(`Peer ${node1.peerId.toB58String()} discovered: ${peerId.toB58String()}`) + }) + node2.on('peer:discovery', (peerId) => { + console.log(`Peer ${node2.peerId.toB58String()} discovered: ${peerId.toB58String()}`) + }) + + ;[node1, node2].forEach((node, index) => console.log(`Node ${index} starting with id: ${node.peerId.toB58String()}`)) + await Promise.all([ + node1.start(), + node2.start() + ]) +})(); diff --git a/examples/discovery-mechanisms/README.md b/examples/discovery-mechanisms/README.md index 3b391ca3b5..180f2a9e5e 100644 --- a/examples/discovery-mechanisms/README.md +++ b/examples/discovery-mechanisms/README.md @@ -156,7 +156,100 @@ Discovered: QmSSbQpuKrxkoXHm1v4Pi35hPN5hUHMZoBoawEs2Nhvi8m Discovered: QmRcXXhtG8vTqwVBRonKWtV4ovDoC1Fe56WYtcrw694eiJ ``` -## 3. Where to find other Peer Discovery Mechanisms +## 3. Pubsub based Peer Discovery + +For this example, we need [`libp2p-pubsub-peer-discovery`](https://github.com/libp2p/js-libp2p-pubsub-peer-discovery/), go ahead and `npm install` it. You also need to spin up a set of [`libp2p-relay-servers`](https://github.com/libp2p/js-libp2p-relay-server). These servers act as relay servers and a peer discovery source. + +In the context of this example, we will create and run the `libp2p-relay-server` in the same code snippet. You can find the complete solution at [3.js](./3.js). + +You can create your libp2p nodes as follows: + +```js +const Libp2p = require('libp2p') +const TCP = require('libp2p-tcp') +const Mplex = require('libp2p-mplex') +const { NOISE } = require('libp2p-noise') +const Gossipsub = require('libp2p-gossipsub') +const Bootstrap = require('libp2p-bootstrap') +const PubsubPeerDiscovery = require('libp2p-pubsub-peer-discovery') + +const createNode = async (bootstrapers) => { + const node = await Libp2p.create({ + addresses: { + listen: ['/ip4/0.0.0.0/tcp/0'] + }, + modules: { + transport: [TCP], + streamMuxer: [Mplex], + connEncryption: [NOISE], + pubsub: Gossipsub, + peerDiscovery: [Bootstrap, PubsubPeerDiscovery] + }, + config: { + peerDiscovery: { + [PubsubPeerDiscovery.tag]: { + interval: 1000, + enabled: true + }, + [Bootstrap.tag]: { + enabled: true, + list: bootstrapers + } + } + } + }) + + return node +} +``` + +We will use the `libp2p-relay-server` as bootstrap nodes for the libp2p nodes, so that they establish a connection with the relay after starting. As a result, after they establish a connection with the relay, the pubsub discovery will kick in an the relay will advertise them. + +```js +const relay = await createRelayServer({ + listenAddresses: ['/ip4/0.0.0.0/tcp/0'] +}) +console.log(`libp2p relay starting with id: ${relay.peerId.toB58String()}`) +await relay.start() +const relayMultiaddrs = relay.multiaddrs.map((m) => `${m.toString()}/p2p/${relay.peerId.toB58String()}`) + +const [node1, node2] = await Promise.all([ + createNode(relayMultiaddrs), + createNode(relayMultiaddrs) +]) + +node1.on('peer:discovery', (peerId) => { + console.log(`Peer ${node1.peerId.toB58String()} discovered: ${peerId.toB58String()}`) +}) +node2.on('peer:discovery', (peerId) => { + console.log(`Peer ${node2.peerId.toB58String()} discovered: ${peerId.toB58String()}`) +}) + +;[node1, node2].forEach((node, index) => console.log(`Node ${index} starting with id: ${node.peerId.toB58String()}`)) +await Promise.all([ + node1.start(), + node2.start() +]) +``` + +If you run this example, you will see the other peers being discovered. + +```bash +> node 3.js +libp2p relay starting with id: QmW6FqVV6RsyoGC5zaeFGW9gSWA3LcBRVZrjkKMruh38Bo +Node 0 starting with id: QmezqDTmEjZ5BfMgVqjSpLY19mVVLTQ9bE9mRpZwtGxL8N +Node 1 starting with id: QmYWeom2odTkm79DzB68NHULqVHDaNDqHhoyqLdcV1fqdv +Peer QmezqDTmEjZ5BfMgVqjSpLY19mVVLTQ9bE9mRpZwtGxL8N discovered: QmW6FqVV6RsyoGC5zaeFGW9gSWA3LcBRVZrjkKMruh38Bo +Peer QmYWeom2odTkm79DzB68NHULqVHDaNDqHhoyqLdcV1fqdv discovered: QmW6FqVV6RsyoGC5zaeFGW9gSWA3LcBRVZrjkKMruh38Bo +Peer QmYWeom2odTkm79DzB68NHULqVHDaNDqHhoyqLdcV1fqdv discovered: QmezqDTmEjZ5BfMgVqjSpLY19mVVLTQ9bE9mRpZwtGxL8N +Peer QmezqDTmEjZ5BfMgVqjSpLY19mVVLTQ9bE9mRpZwtGxL8N discovered: QmYWeom2odTkm79DzB68NHULqVHDaNDqHhoyqLdcV1fqdv +``` + +Taking into account the output, after the relay and both libp2p nodes start, both libp2p nodes will discover the bootstrap node (relay) and connect with it. After establishing a connection with the relay, they will discover each other. + +This is really useful when running libp2p in constrained environments like a browser. You can run a set of `libp2p-relay-server` nodes that will be responsible for both relaying websocket connections between browser nodes and for discovering other browser peers. + +## 4. Where to find other Peer Discovery Mechanisms There are plenty more Peer Discovery Mechanisms out there, you can: diff --git a/examples/discovery-mechanisms/test-3.js b/examples/discovery-mechanisms/test-3.js new file mode 100644 index 0000000000..cbb4b74a60 --- /dev/null +++ b/examples/discovery-mechanisms/test-3.js @@ -0,0 +1,35 @@ +'use strict' + +const path = require('path') +const execa = require('execa') +const pWaitFor = require('p-wait-for') +const uint8ArrayToString = require('uint8arrays/to-string') + +const discoveredCopy = 'discovered:' + +async function test() { + let discoverCount = 0 + + process.stdout.write('3.js\n') + + const proc = execa('node', [path.join(__dirname, '3.js')], { + cwd: path.resolve(__dirname), + all: true + }) + + proc.all.on('data', async (data) => { + process.stdout.write(data) + const line = uint8ArrayToString(data) + + // Discovered or Connected + if (line.includes(discoveredCopy)) { + discoverCount++ + } + }) + + await pWaitFor(() => discoverCount === 4) + + proc.kill() +} + +module.exports = test diff --git a/examples/discovery-mechanisms/test.js b/examples/discovery-mechanisms/test.js index 38b2a347fd..d9faeb2e9e 100644 --- a/examples/discovery-mechanisms/test.js +++ b/examples/discovery-mechanisms/test.js @@ -2,10 +2,12 @@ const test1 = require('./test-1') const test2 = require('./test-2') +const test3 = require('./test-3') async function test () { await test1() await test2() + await test3() } module.exports = test diff --git a/examples/package.json b/examples/package.json index b8c0a46a54..7746ce4424 100644 --- a/examples/package.json +++ b/examples/package.json @@ -10,6 +10,8 @@ "dependencies": { "execa": "^2.1.0", "fs-extra": "^8.1.0", + "libp2p-pubsub-peer-discovery": "^3.0.0", + "libp2p-relay-server": "^0.1.2", "p-defer": "^3.0.0", "which": "^2.0.1" }, From 9941414a917f7edb8572015a3360cdc6ef78ad69 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 11 Feb 2021 11:42:10 +0100 Subject: [PATCH 119/447] chore: update delegates config docs to use http client (#853) --- doc/CONFIGURATION.md | 21 ++++++++++++++------ test/content-routing/content-routing.node.js | 8 ++------ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 04993dc969..ce09f1e053 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -391,6 +391,7 @@ const Libp2p = require('libp2p') const TCP = require('libp2p-tcp') const MPLEX = require('libp2p-mplex') const { NOISE } = require('libp2p-noise') +const ipfsHttpClient = require('ipfs-http-client') const DelegatedPeerRouter = require('libp2p-delegated-peer-routing') const DelegatedContentRouter = require('libp2p-delegated-content-routing') const PeerId = require('peer-id') @@ -398,17 +399,25 @@ const PeerId = require('peer-id') // create a peerId const peerId = await PeerId.create() +const delegatedPeerRouting = new DelegatedPeerRouter(ipfsHttpClient({ + host: 'node0.delegate.ipfs.io' // In production you should setup your own delegates + protocol: 'https', + port: 443 +})) + +const delegatedContentRouting = new DelegatedContentRouter(peerId, ipfsHttpClient({ + host: 'node0.delegate.ipfs.io' // In production you should setup your own delegates + protocol: 'https', + port: 443 +})) + const node = await Libp2p.create({ modules: { transport: [TCP], streamMuxer: [MPLEX], connEncryption: [NOISE], - contentRouting: [ - new DelegatedContentRouter(peerId) - ], - peerRouting: [ - new DelegatedPeerRouter() - ], + contentRouting: [delegatedContentRouting], + peerRouting: [delegatedPeerRouting], }, peerId, peerRouting: { // Peer routing configuration diff --git a/test/content-routing/content-routing.node.js b/test/content-routing/content-routing.node.js index 30caf0d610..a747b179f9 100644 --- a/test/content-routing/content-routing.node.js +++ b/test/content-routing/content-routing.node.js @@ -107,9 +107,7 @@ describe('content-routing', () => { host: '0.0.0.0', protocol: 'http', port: 60197 - }), [ - multiaddr('/ip4/0.0.0.0/tcp/60197') - ]) + })) ;[node] = await peerUtils.createPeer({ config: mergeOptions(baseOptions, { @@ -253,9 +251,7 @@ describe('content-routing', () => { host: '0.0.0.0', protocol: 'http', port: 60197 - }), [ - multiaddr('/ip4/0.0.0.0/tcp/60197') - ]) + })) ;[node] = await peerUtils.createPeer({ config: mergeOptions(routingOptions, { From a34d2bbcc3d69ec3006137a909a7e8c53b9d378e Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 11 Feb 2021 14:37:34 +0100 Subject: [PATCH 120/447] fix: routers should only use dht if enabled (#885) --- src/content-routing/index.js | 2 +- src/peer-routing.js | 2 +- test/content-routing/content-routing.node.js | 4 ++++ test/peer-routing/peer-routing.node.js | 4 ++++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/content-routing/index.js b/src/content-routing/index.js index f5d999264d..f409daa3c4 100644 --- a/src/content-routing/index.js +++ b/src/content-routing/index.js @@ -35,7 +35,7 @@ class ContentRouting { this.dht = libp2p._dht // If we have the dht, add it to the available content routers - if (this.dht) { + if (this.dht && libp2p._config.dht.enabled) { this.routers.push(this.dht) } } diff --git a/src/peer-routing.js b/src/peer-routing.js index 637b8d2425..32c9cc20e8 100644 --- a/src/peer-routing.js +++ b/src/peer-routing.js @@ -36,7 +36,7 @@ class PeerRouting { this._routers = libp2p._modules.peerRouting || [] // If we have the dht, add it to the available peer routers - if (libp2p._dht) { + if (libp2p._dht && libp2p._config.dht.enabled) { this._routers.push(libp2p._dht) } diff --git a/test/content-routing/content-routing.node.js b/test/content-routing/content-routing.node.js index a747b179f9..09a11f250f 100644 --- a/test/content-routing/content-routing.node.js +++ b/test/content-routing/content-routing.node.js @@ -129,6 +129,10 @@ describe('content-routing', () => { afterEach(() => node.stop()) + it('should only have one router', () => { + expect(node.contentRouting.routers).to.have.lengthOf(1) + }) + it('should use the delegate router to provide', () => { const deferred = pDefer() diff --git a/test/peer-routing/peer-routing.node.js b/test/peer-routing/peer-routing.node.js index 3432cba8a4..2badd3597a 100644 --- a/test/peer-routing/peer-routing.node.js +++ b/test/peer-routing/peer-routing.node.js @@ -132,6 +132,10 @@ describe('peer-routing', () => { afterEach(() => node.stop()) + it('should only have one router', () => { + expect(node.peerRouting._routers).to.have.lengthOf(1) + }) + it('should use the delegate router to find peers', async () => { const deferred = pDefer() const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) From 3e302570e551220a2aedf282c3bbb9e3cabee756 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 11 Feb 2021 14:57:43 +0100 Subject: [PATCH 121/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1469115730..5b4fde6080 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.30.7", + "version": "0.30.8", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From fc6558b897c5223ff8e6347ad1618b6c5c3385e4 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 11 Feb 2021 14:57:43 +0100 Subject: [PATCH 122/447] chore: release version v0.30.8 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b539ab550c..c14936497a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.30.8](https://github.com/libp2p/js-libp2p/compare/v0.30.7...v0.30.8) (2021-02-11) + + +### Bug Fixes + +* routers should only use dht if enabled ([#885](https://github.com/libp2p/js-libp2p/issues/885)) ([a34d2bb](https://github.com/libp2p/js-libp2p/commit/a34d2bbcc3d69ec3006137a909a7e8c53b9d378e)) + + + ## [0.30.7](https://github.com/libp2p/js-libp2p/compare/v0.30.6...v0.30.7) (2021-02-01) From 4ee3e1973bcb9d40faaae060af0234825ad2937d Mon Sep 17 00:00:00 2001 From: Miguel Mota Date: Thu, 18 Feb 2021 02:36:35 -0800 Subject: [PATCH 123/447] chore: minor grammar fixes on discovery example (#890) --- examples/discovery-mechanisms/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/discovery-mechanisms/README.md b/examples/discovery-mechanisms/README.md index 180f2a9e5e..e2c9e0ff2a 100644 --- a/examples/discovery-mechanisms/README.md +++ b/examples/discovery-mechanisms/README.md @@ -2,13 +2,13 @@ A Peer Discovery module enables libp2p to find peers to connect to. Think of these mechanisms as ways to join the rest of the network, as railing points. -With these system, a libp2p node can both have a set of nodes to always connect on boot (bootstraper nodes), discover nodes through locality (e.g connected in the same LAN) or through serendipity (random walks on a DHT). +With this system, a libp2p node can both have a set of nodes to always connect on boot (bootstraper nodes), discover nodes through locality (e.g connected in the same LAN) or through serendipity (random walks on a DHT). These mechanisms save configuration and enable a node to operate without any explicit dials, it will just work. Once new peers are discovered, their known data is stored in the peer's PeerStore. ## 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 NOISE. 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. @@ -16,7 +16,7 @@ First, we create our libp2p node. const Libp2p = require('libp2p') const Bootstrap = require('libp2p-bootstrap') -const node = Libp2p.create({ +const node = await Libp2p.create({ modules: { transport: [ TCP ], streamMuxer: [ Mplex ], @@ -203,7 +203,7 @@ const createNode = async (bootstrapers) => { } ``` -We will use the `libp2p-relay-server` as bootstrap nodes for the libp2p nodes, so that they establish a connection with the relay after starting. As a result, after they establish a connection with the relay, the pubsub discovery will kick in an the relay will advertise them. +We will use the `libp2p-relay-server` as bootstrap nodes for the libp2p nodes, so that they establish a connection with the relay after starting. As a result, after they establish a connection with the relay, the pubsub discovery will kick in and the relay will advertise them. ```js const relay = await createRelayServer({ @@ -254,5 +254,5 @@ This is really useful when running libp2p in constrained environments like a bro 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. 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). +- 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 of 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! From 3f314d5e90f74583b721386d0c9c5d8363cd4de7 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 25 Feb 2021 15:23:07 +0100 Subject: [PATCH 124/447] fix: transport manager fault tolerance should include tolerance to transport listen fail (#893) --- src/transport-manager.js | 2 +- test/transports/transport-manager.spec.js | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/transport-manager.js b/src/transport-manager.js index 173e5f5de5..7b41b87d39 100644 --- a/src/transport-manager.js +++ b/src/transport-manager.js @@ -196,7 +196,7 @@ class TransportManager { // listening on remote addresses as they may be offline. We could then potentially // just wait for any (`p-any`) listener to succeed on each transport before returning const isListening = results.find(r => r.isFulfilled === true) - if (!isListening) { + if (!isListening && this.faultTolerance !== FAULT_TOLERANCE.NO_FATAL) { throw errCode(new Error(`Transport (${key}) could not listen on any available address`), codes.ERR_NO_VALID_ADDRESSES) } } diff --git a/test/transports/transport-manager.spec.js b/test/transports/transport-manager.spec.js index e1b13982b2..5c6c08b06e 100644 --- a/test/transports/transport-manager.spec.js +++ b/test/transports/transport-manager.spec.js @@ -209,7 +209,7 @@ describe('libp2p.transportManager (dial only)', () => { throw new Error('it should fail to start if multiaddr fails to listen') }) - it('does not fail to start if multiaddr fails to listen when supporting dial only mode', async () => { + it('does not fail to start if provided listen multiaddr are not compatible to configured transports (when supporting dial only mode)', async () => { libp2p = new Libp2p({ peerId, addresses: { @@ -226,4 +226,22 @@ describe('libp2p.transportManager (dial only)', () => { await libp2p.start() }) + + it('does not fail to start if provided listen multiaddr fail to listen on configured transports (when supporting dial only mode)', async () => { + libp2p = new Libp2p({ + peerId, + addresses: { + listen: [multiaddr('/ip4/127.0.0.1/tcp/12345/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3/p2p-circuit')] + }, + transportManager: { + faultTolerance: FaultTolerance.NO_FATAL + }, + modules: { + transport: [Transport], + connEncryption: [Crypto] + } + }) + + await libp2p.start() + }) }) From 3d5bba070b4a13f70d89021e1fe331a60c7a7a47 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 25 Feb 2021 15:31:26 +0100 Subject: [PATCH 125/447] chore: update contributors --- package.json | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 5b4fde6080..74fdbff2ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.30.8", + "version": "0.30.9", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", @@ -154,45 +154,46 @@ "Friedel Ziegelmayer ", "Maciej Krüger ", "Hugo Dias ", - "Volker Mische ", "dirkmc ", + "Volker Mische ", "Richard Littauer ", "a1300 ", + "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", "Ryan Bell ", + "Samlior ", "Andrew Nesbitt ", "Elven ", "Giovanni T. Parra ", - "Samlior ", "Thomas Eizinger ", - "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", "Didrik Nordström ", - "Irakli Gozalishvili ", - "Joel Gustafson ", "Julien Bouquillon ", "Kevin Kwok ", "Kevin Lacker ", - "Ethan Lam ", + "Diogo Silva ", + "Miguel Mota ", "Nuno Nogueira ", - "Dmitriy Ryajov ", "RasmusErik Voel Jensen ", - "Diogo Silva ", "Smite Chow ", "Soeren ", "Sönke Hahn ", "Tiago Alves ", "Daijiro Wachi ", + "Cindy Wu ", "Yusef Napora ", "Zane Starr ", - "robertkiel ", - "Cindy Wu ", "Chris Bratlien ", - "ebinks ", - "Francis Gulotta ", - "Florian-Merle ", "Bernd Strehl ", - "Henrique Dias ", + "ebinks ", "isan_rivkin ", + "robertkiel ", + "Fei Liu ", + "Ethan Lam ", "Felipe Martins ", - "Fei Liu " + "Florian-Merle ", + "Francis Gulotta ", + "Henrique Dias ", + "Irakli Gozalishvili ", + "Dmitriy Ryajov ", + "Joel Gustafson " ] } From a1424826e7cc351b995f1495d2a36c6dac937ed2 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 25 Feb 2021 15:31:26 +0100 Subject: [PATCH 126/447] chore: release version v0.30.9 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c14936497a..d9850827d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.30.9](https://github.com/libp2p/js-libp2p/compare/v0.30.8...v0.30.9) (2021-02-25) + + +### Bug Fixes + +* transport manager fault tolerance should include tolerance to transport listen fail ([#893](https://github.com/libp2p/js-libp2p/issues/893)) ([3f314d5](https://github.com/libp2p/js-libp2p/commit/3f314d5e90f74583b721386d0c9c5d8363cd4de7)) + + + ## [0.30.8](https://github.com/libp2p/js-libp2p/compare/v0.30.7...v0.30.8) (2021-02-11) From 9c67364caa1880782f01718ebb002e282e26bd3c Mon Sep 17 00:00:00 2001 From: Aleksei Date: Thu, 25 Feb 2021 16:34:02 +0100 Subject: [PATCH 127/447] Add an example of webrtc-direct (#868) Co-authored-by: Vasco Santos --- .github/workflows/main.yml | 9 ++- examples/webrtc-direct/README.md | 33 ++++++++++ examples/webrtc-direct/dialer.js | 57 ++++++++++++++++++ examples/webrtc-direct/index.html | 17 ++++++ examples/webrtc-direct/listener.js | 44 ++++++++++++++ examples/webrtc-direct/package.json | 31 ++++++++++ examples/webrtc-direct/test.js | 93 +++++++++++++++++++++++++++++ 7 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 examples/webrtc-direct/README.md create mode 100644 examples/webrtc-direct/dialer.js create mode 100644 examples/webrtc-direct/index.html create mode 100644 examples/webrtc-direct/listener.js create mode 100644 examples/webrtc-direct/package.json create mode 100644 examples/webrtc-direct/test.js diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 375e35e12c..7d5934910d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -92,7 +92,7 @@ jobs: steps: - uses: actions/checkout@v2 - run: yarn - - run: cd examples && yarn && npm run test -- echo + - run: cd examples && yarn && npm run test -- echo test-libp2p-in-the-browser-example: needs: check runs-on: macos-latest @@ -135,3 +135,10 @@ jobs: - uses: actions/checkout@v2 - run: yarn - run: cd examples && yarn && npm run test -- transports + test-webrtc-direct-example: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: cd examples && yarn && npm run test -- webrtc-direct diff --git a/examples/webrtc-direct/README.md b/examples/webrtc-direct/README.md new file mode 100644 index 0000000000..3eb406a66b --- /dev/null +++ b/examples/webrtc-direct/README.md @@ -0,0 +1,33 @@ +### Webrtc-direct example + +An example that uses [js-libp2p-webrtc-direct](https://github.com/libp2p/js-libp2p-webrtc-direct) for connecting +nodejs libp2p and browser libp2p clients. To run the example: + +## 0. Run a nodejs libp2p listener + +When in the root folder of this example, type `node listener.js` in terminal. You should see an address that listens for +incoming connections. Below is just an example of such address. In your case the suffix hash (`peerId`) will be different. + +```bash +$ node listener.js +Listening on: +/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct/p2p/QmUKQCzEUhhhobcNSrXU5uzxTqbvF1BjMCGNGZzZU14Kgd +``` + +## 1. Prepare a browser libp2p dialer +Confirm that the above address is the same as the field `list` in `public/dialer.js`: +```js + peerDiscovery: { + [Bootstrap.tag]: { + enabled: true, + // paste the address into `list` + list: ['/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct/p2p/QmUKQCzEUhhhobcNSrXU5uzxTqbvF1BjMCGNGZzZU14Kgd'] + } + } +``` + +## 2. Run a browser libp2p dialer +When in the root folder of this example, type `npm run dev` in terminal. You should see an address where you can browse +the running client. Open this address in your browser. In console +logs you should see logs about successful connection with the node client. In the output of node client you should see +a log message about successful connection as well. diff --git a/examples/webrtc-direct/dialer.js b/examples/webrtc-direct/dialer.js new file mode 100644 index 0000000000..a7ba855886 --- /dev/null +++ b/examples/webrtc-direct/dialer.js @@ -0,0 +1,57 @@ +import 'babel-polyfill' +const Libp2p = require('libp2p') +const WebRTCDirect = require('libp2p-webrtc-direct') +const Mplex = require('libp2p-mplex') +const {NOISE} = require('libp2p-noise') +const Bootstrap = require('libp2p-bootstrap') + +document.addEventListener('DOMContentLoaded', async () => { + // use the same peer id as in `listener.js` to avoid copy-pasting of listener's peer id into `peerDiscovery` + const hardcodedPeerId = '12D3KooWCuo3MdXfMgaqpLC5Houi1TRoFqgK9aoxok4NK5udMu8m' + const libp2p = await Libp2p.create({ + modules: { + transport: [WebRTCDirect], + streamMuxer: [Mplex], + connEncryption: [NOISE], + peerDiscovery: [Bootstrap] + }, + config: { + peerDiscovery: { + [Bootstrap.tag]: { + enabled: true, + list: [`/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct/p2p/${hardcodedPeerId}`] + } + } + } + }) + + const status = document.getElementById('status') + const output = document.getElementById('output') + + output.textContent = '' + + function log (txt) { + console.info(txt) + output.textContent += `${txt.trim()}\n` + } + + // Listen for new peers + libp2p.on('peer:discovery', (peerId) => { + log(`Found peer ${peerId.toB58String()}`) + }) + + // Listen for new connections to peers + libp2p.connectionManager.on('peer:connect', (connection) => { + log(`Connected to ${connection.remotePeer.toB58String()}`) + }) + + // Listen for peers disconnecting + libp2p.connectionManager.on('peer:disconnect', (connection) => { + log(`Disconnected from ${connection.remotePeer.toB58String()}`) + }) + + await libp2p.start() + status.innerText = 'libp2p started!' + log(`libp2p id is ${libp2p.peerId.toB58String()}`) + +}) diff --git a/examples/webrtc-direct/index.html b/examples/webrtc-direct/index.html new file mode 100644 index 0000000000..a29b43bf3a --- /dev/null +++ b/examples/webrtc-direct/index.html @@ -0,0 +1,17 @@ + + + + + js-libp2p parcel.js browser example + + + +

+

Starting libp2p...

+
+
+

+  
+ + + diff --git a/examples/webrtc-direct/listener.js b/examples/webrtc-direct/listener.js new file mode 100644 index 0000000000..47f3a97185 --- /dev/null +++ b/examples/webrtc-direct/listener.js @@ -0,0 +1,44 @@ +const Libp2p = require('libp2p') +const Bootstrap = require('libp2p-bootstrap') +const WebRTCDirect = require('libp2p-webrtc-direct') +const Mplex = require('libp2p-mplex') +const {NOISE} = require('libp2p-noise') +const PeerId = require('peer-id') + +;(async () => { + // hardcoded peer id to avoid copy-pasting of listener's peer id into the dialer's bootstrap list + // generated with cmd `peer-id --type=ed25519` + const hardcodedPeerId = await PeerId.createFromJSON({ + "id": "12D3KooWCuo3MdXfMgaqpLC5Houi1TRoFqgK9aoxok4NK5udMu8m", + "privKey": "CAESQAG6Ld7ev6nnD0FKPs033/j0eQpjWilhxnzJ2CCTqT0+LfcWoI2Vr+zdc1vwk7XAVdyoCa2nwUR3RJebPWsF1/I=", + "pubKey": "CAESIC33FqCNla/s3XNb8JO1wFXcqAmtp8FEd0SXmz1rBdfy" + }) + const node = await Libp2p.create({ + peerId: hardcodedPeerId, + addresses: { + listen: ['/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct'] + }, + modules: { + transport: [WebRTCDirect], + streamMuxer: [Mplex], + connEncryption: [NOISE] + }, + config: { + peerDiscovery: { + [Bootstrap.tag]: { + enabled: false, + } + } + } + }) + + node.connectionManager.on('peer:connect', (connection) => { + console.info(`Connected to ${connection.remotePeer.toB58String()}!`) + }) + + await node.start() + + console.log('Listening on:') + node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`)) + +})() diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json new file mode 100644 index 0000000000..33f2250d58 --- /dev/null +++ b/examples/webrtc-direct/package.json @@ -0,0 +1,31 @@ +{ + "name": "webrtc-direct", + "version": "0.0.1", + "private": true, + "description": "", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "parcel build index.html", + "start": "parcel index.html" + }, + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.8.3", + "@babel/core": "^7.8.3", + "babel-plugin-syntax-async-functions": "^6.13.0", + "babel-plugin-transform-regenerator": "^6.26.0", + "babel-polyfill": "^6.26.0", + "parcel-bundler": "^1.12.4" + }, + "dependencies": { + "libp2p": "../../", + "libp2p-bootstrap": "^0.12.1", + "libp2p-mplex": "^0.10.1", + "libp2p-noise": "^2.0.1", + "libp2p-webrtc-direct": "^0.5.0", + "peer-id": "^0.14.3" + }, + "browser": { + "ipfs": "ipfs/dist/index.min.js" + } +} diff --git a/examples/webrtc-direct/test.js b/examples/webrtc-direct/test.js new file mode 100644 index 0000000000..1768e1a082 --- /dev/null +++ b/examples/webrtc-direct/test.js @@ -0,0 +1,93 @@ +'use strict' + +const path = require('path') +const execa = require('execa') +const pDefer = require('p-defer') +const uint8ArrayToString = require('uint8arrays/to-string') +const { chromium } = require('playwright'); + +function startNode (name, args = []) { + return execa('node', [path.join(__dirname, name), ...args], { + cwd: path.resolve(__dirname), + all: true + }) +} + +function startBrowser (name, args = []) { + return execa('parcel', [path.join(__dirname, name), ...args], { + preferLocal: true, + localDir: __dirname, + cwd: __dirname, + all: true + }) +} + +async function test () { + // Step 1, listener process + const listenerProcReady = pDefer() + let listenerOutput = '' + process.stdout.write('listener.js\n') + const listenerProc = startNode('listener.js') + + listenerProc.all.on('data', async (data) => { + process.stdout.write(data) + listenerOutput += uint8ArrayToString(data) + if (listenerOutput.includes('Listening on:') && listenerOutput.includes('12D3KooWCuo3MdXfMgaqpLC5Houi1TRoFqgK9aoxok4NK5udMu8m')) { + listenerProcReady.resolve() + } + }) + + await listenerProcReady.promise + process.stdout.write('==================================================================\n') + + // Step 2, dialer process + process.stdout.write('dialer.js\n') + let dialerUrl = '' + const dialerProc = startBrowser('index.html') + + dialerProc.all.on('data', async (chunk) => { + /**@type {string} */ + const out = chunk.toString() + + if (out.includes('Server running at')) { + dialerUrl = out.replace('Server running at ', '') + } + + if (out.includes('✨ Built in ')) { + try { + const browser = await chromium.launch(); + const page = await browser.newPage(); + await page.goto(dialerUrl); + await page.waitForFunction(selector => document.querySelector(selector).innerText === 'libp2p started!', '#status') + await page.waitForFunction( + selector => { + const text = document.querySelector(selector).innerText + return text.includes('libp2p id is') && + text.includes('Found peer') && + text.includes('Connected to') + }, + '#output', + { timeout: 10000 } + ) + await browser.close(); + } catch (err) { + console.error(err) + process.exit(1) + } finally { + dialerProc.cancel() + listenerProc.kill() + } + } + }) + + await Promise.all([ + listenerProc, + dialerProc, + ]).catch((err) => { + if (err.signal !== 'SIGTERM') { + throw err + } + }) +} + +module.exports = test From 03b34cac7dbb5eb09c2d1a95c797aec63fcd9e22 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Tue, 2 Mar 2021 13:07:52 +0100 Subject: [PATCH 128/447] docs: fix link to connection encryption example (#894) --- examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index aeba02a41c..fecff8f761 100644 --- a/examples/README.md +++ b/examples/README.md @@ -8,7 +8,7 @@ Let us know if you find any issues, or if you want to contribute and add a new t - [Transports](./transports) - [Protocol and Stream Muxing](./protocol-and-stream-muxing) -- [Encrypted Communications](./encrypted-communications) +- [Connection Encryption](./connection-encryption) - [Discovery Mechanisms](./discovery-mechanisms) - [Peer and Content Routing](./peer-and-content-routing) - [PubSub](./pubsub) From 5f702f3481afd4ad4fbc89f0e9b75a6d56b03520 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 9 Mar 2021 16:51:41 +0100 Subject: [PATCH 129/447] fix: conn mgr access to moving averages record object (#897) * fix: conn mgr access to moving averages record object * chore: remove node 12 * chore: add parcel workaround --- .aegir.js | 2 +- .github/workflows/main.yml | 2 +- examples/libp2p-in-the-browser/package.json | 8 ++++---- examples/webrtc-direct/package.json | 6 +++--- src/metrics/stats.js | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.aegir.js b/.aegir.js index 656414f86a..7c482a515d 100644 --- a/.aegir.js +++ b/.aegir.js @@ -48,7 +48,7 @@ const after = async () => { } module.exports = { - bundlesize: { maxSize: '220kB' }, + bundlesize: { maxSize: '222kB' }, hooks: { pre: before, post: after diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7d5934910d..6a3a59b81f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,7 +27,7 @@ jobs: strategy: matrix: os: [windows-latest, ubuntu-latest, macos-latest] - node: [12, 14] + node: [14] fail-fast: true steps: - uses: actions/checkout@v2 diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json index b59c8cdbf9..5a93afd81a 100644 --- a/examples/libp2p-in-the-browser/package.json +++ b/examples/libp2p-in-the-browser/package.json @@ -15,7 +15,7 @@ "author": "", "license": "ISC", "dependencies": { - "@babel/preset-env": "^7.8.3", + "@babel/preset-env": "^7.13.0", "libp2p": "../../", "libp2p-bootstrap": "^0.12.1", "libp2p-mplex": "^0.10.0", @@ -24,11 +24,11 @@ "libp2p-websockets": "^0.14.0" }, "devDependencies": { - "@babel/cli": "^7.8.3", - "@babel/core": "^7.8.3", + "@babel/cli": "^7.13.10", + "@babel/core": "^7.13.0", "babel-plugin-syntax-async-functions": "^6.13.0", "babel-plugin-transform-regenerator": "^6.26.0", "babel-polyfill": "^6.26.0", - "parcel-bundler": "^1.12.4" + "parcel-bundler": "1.12.3" } } diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index 33f2250d58..f5491c7ba0 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -10,12 +10,12 @@ }, "license": "ISC", "devDependencies": { - "@babel/cli": "^7.8.3", - "@babel/core": "^7.8.3", + "@babel/cli": "^7.13.10", + "@babel/core": "^7.13.10", "babel-plugin-syntax-async-functions": "^6.13.0", "babel-plugin-transform-regenerator": "^6.26.0", "babel-polyfill": "^6.26.0", - "parcel-bundler": "^1.12.4" + "parcel-bundler": "1.12.3" }, "dependencies": { "libp2p": "../../", diff --git a/src/metrics/stats.js b/src/metrics/stats.js index f820c5b429..a445a705f2 100644 --- a/src/metrics/stats.js +++ b/src/metrics/stats.js @@ -82,7 +82,7 @@ class Stats extends EventEmitter { /** * Returns a clone of the internal movingAverages * - * @returns {MovingAverage} + * @returns {Object} */ get movingAverages () { return Object.assign({}, this._movingAverages) From f2f361998df9e32f7f91ec0d0d28564975a5f9dc Mon Sep 17 00:00:00 2001 From: TJKoury Date: Tue, 9 Mar 2021 10:55:38 -0500 Subject: [PATCH 130/447] chore: swap promisify to maintained package (#896) Co-authored-by: Vasco Santos --- package.json | 2 +- src/nat-manager.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 74fdbff2ed..603c056b2c 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "class-is": "^1.1.0", "debug": "^4.3.1", "err-code": "^2.0.0", + "es6-promisify": "^6.1.1", "events": "^3.2.0", "hashlru": "^2.3.0", "interface-datastore": "^3.0.3", @@ -97,7 +98,6 @@ "p-settle": "^4.0.1", "peer-id": "^0.14.2", "private-ip": "^2.0.0", - "promisify-es6": "^1.0.3", "protons": "^2.0.0", "retimer": "^2.0.0", "sanitize-filename": "^1.6.3", diff --git a/src/nat-manager.js b/src/nat-manager.js index 93c0251f81..121139ab7e 100644 --- a/src/nat-manager.js +++ b/src/nat-manager.js @@ -2,7 +2,7 @@ const NatAPI = require('@motrix/nat-api') const debug = require('debug') -const promisify = require('promisify-es6') +const promisify = require('es6-promisify') const Multiaddr = require('multiaddr') const log = Object.assign(debug('libp2p:nat'), { error: debug('libp2p:nat:err') From 8895a092b6f391aa27498cce4fdad2de3df7e408 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 9 Mar 2021 18:52:37 +0100 Subject: [PATCH 131/447] chore: update contributors --- package.json | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 603c056b2c..a0682175d8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.30.9", + "version": "0.30.10", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", @@ -154,46 +154,49 @@ "Friedel Ziegelmayer ", "Maciej Krüger ", "Hugo Dias ", - "dirkmc ", "Volker Mische ", + "dirkmc ", "Richard Littauer ", "a1300 ", - "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", - "Ryan Bell ", - "Samlior ", - "Andrew Nesbitt ", "Elven ", + "Andrew Nesbitt ", + "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", "Giovanni T. Parra ", + "Ryan Bell ", "Thomas Eizinger ", + "Samlior ", "Didrik Nordström ", "Julien Bouquillon ", "Kevin Kwok ", "Kevin Lacker ", - "Diogo Silva ", "Miguel Mota ", "Nuno Nogueira ", + "Philipp Muens ", "RasmusErik Voel Jensen ", "Smite Chow ", "Soeren ", "Sönke Hahn ", + "TJKoury ", "Tiago Alves ", "Daijiro Wachi ", "Cindy Wu ", + "Chris Bratlien ", "Yusef Napora ", "Zane Starr ", - "Chris Bratlien ", "Bernd Strehl ", "ebinks ", + "Ethan Lam ", "isan_rivkin ", "robertkiel ", + "Aleksei ", "Fei Liu ", - "Ethan Lam ", "Felipe Martins ", "Florian-Merle ", "Francis Gulotta ", + "Dmitriy Ryajov ", "Henrique Dias ", "Irakli Gozalishvili ", - "Dmitriy Ryajov ", + "Diogo Silva ", "Joel Gustafson " ] } From 8e1fc78353cba194c86102dafcd68373b3f9a796 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 9 Mar 2021 18:52:37 +0100 Subject: [PATCH 132/447] chore: release version v0.30.10 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9850827d6..3f104e155e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.30.10](https://github.com/libp2p/js-libp2p/compare/v0.30.9...v0.30.10) (2021-03-09) + + +### Bug Fixes + +* conn mgr access to moving averages record object ([#897](https://github.com/libp2p/js-libp2p/issues/897)) ([5f702f3](https://github.com/libp2p/js-libp2p/commit/5f702f3481afd4ad4fbc89f0e9b75a6d56b03520)) + + + ## [0.30.9](https://github.com/libp2p/js-libp2p/compare/v0.30.8...v0.30.9) (2021-02-25) From 9504f1951a3cca55bb7b4e25e4934e4024034ee8 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 22 Mar 2021 17:23:57 +0100 Subject: [PATCH 133/447] fix: connection direction should be only inbound or outbound --- src/upgrader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/upgrader.js b/src/upgrader.js index 14d0a4e82d..7e7ec22a95 100644 --- a/src/upgrader.js +++ b/src/upgrader.js @@ -201,7 +201,7 @@ class Upgrader { * @private * @param {object} options * @param {string} options.cryptoProtocol - The crypto protocol that was negotiated - * @param {string} options.direction - One of ['inbound', 'outbound'] + * @param {'inbound' | 'outbound'} options.direction - One of ['inbound', 'outbound'] * @param {MultiaddrConnection} options.maConn - The transport layer connection * @param {MuxedStream | MultiaddrConnection} options.upgradedConn - A duplex connection returned from multiplexer and/or crypto selection * @param {MuxerFactory} [options.Muxer] - The muxer to be used for muxing From 975e4b0fe0313bfad7344051f70bc713cb70fa31 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 22 Mar 2021 17:28:12 +0100 Subject: [PATCH 134/447] chore: increase bundle size --- .aegir.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.aegir.js b/.aegir.js index 7c482a515d..b4eb9b0f11 100644 --- a/.aegir.js +++ b/.aegir.js @@ -48,7 +48,7 @@ const after = async () => { } module.exports = { - bundlesize: { maxSize: '222kB' }, + bundlesize: { maxSize: '223kB' }, hooks: { pre: before, post: after From f5c1cd1fb07bc73cf9d9da3c2eb4327bed4279a4 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 22 Mar 2021 17:33:37 +0100 Subject: [PATCH 135/447] fix: interface-datastore update --- src/keychain/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/keychain/index.js b/src/keychain/index.js index fdf46cab70..eaff31b5c3 100644 --- a/src/keychain/index.js +++ b/src/keychain/index.js @@ -14,7 +14,7 @@ require('node-forge/lib/sha512') /** * @typedef {import('peer-id')} PeerId - * @typedef {import('interface-datastore/src/types').Datastore} Datastore + * @typedef {import('interface-datastore').Datastore} Datastore */ const keyPrefix = '/pkcs8/' From b9e3bcd91e187f3f7e3ae9f13fe6d16a0553e3ac Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 23 Mar 2021 09:51:32 +0100 Subject: [PATCH 136/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a0682175d8..29f0caf748 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.30.10", + "version": "0.30.11", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From b89445274d9a30beaa447a8ce05c2367109cc8a4 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 23 Mar 2021 09:51:32 +0100 Subject: [PATCH 137/447] chore: release version v0.30.11 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f104e155e..b32711ec3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## [0.30.11](https://github.com/libp2p/js-libp2p/compare/v0.30.10...v0.30.11) (2021-03-23) + + +### Bug Fixes + +* connection direction should be only inbound or outbound ([9504f19](https://github.com/libp2p/js-libp2p/commit/9504f1951a3cca55bb7b4e25e4934e4024034ee8)) +* interface-datastore update ([f5c1cd1](https://github.com/libp2p/js-libp2p/commit/f5c1cd1fb07bc73cf9d9da3c2eb4327bed4279a4)) + + + ## [0.30.10](https://github.com/libp2p/js-libp2p/compare/v0.30.9...v0.30.10) (2021-03-09) From c3e147df6b6f51e40d9152e95189f440737dbeec Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 24 Mar 2021 09:35:17 +0100 Subject: [PATCH 138/447] chore: delegates config md properties for client (#903) --- doc/CONFIGURATION.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index ce09f1e053..89c30b3d07 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -400,13 +400,13 @@ const PeerId = require('peer-id') const peerId = await PeerId.create() const delegatedPeerRouting = new DelegatedPeerRouter(ipfsHttpClient({ - host: 'node0.delegate.ipfs.io' // In production you should setup your own delegates + host: 'node0.delegate.ipfs.io', // In production you should setup your own delegates protocol: 'https', port: 443 })) const delegatedContentRouting = new DelegatedContentRouter(peerId, ipfsHttpClient({ - host: 'node0.delegate.ipfs.io' // In production you should setup your own delegates + host: 'node0.delegate.ipfs.io', // In production you should setup your own delegates protocol: 'https', port: 443 })) From a7128f07ec8d4b729145ecfc6ad1d585ffddea46 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 26 Mar 2021 17:13:07 +0000 Subject: [PATCH 139/447] fix: the API of es6-promisify is not the same as promisify-es6 (#905) --- package.json | 1 + src/nat-manager.js | 28 ++++++++++++--- test/nat-manager/nat-manager.node.js | 52 +++++++++++++++++++++++++--- 3 files changed, 72 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 29f0caf748..806e8b9e18 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ }, "devDependencies": { "@nodeutils/defaults-deep": "^1.1.0", + "@types/es6-promisify": "^6.0.0", "abortable-iterator": "^3.0.0", "aegir": "^29.2.0", "chai-bytes": "^0.1.2", diff --git a/src/nat-manager.js b/src/nat-manager.js index 121139ab7e..887f3d81fb 100644 --- a/src/nat-manager.js +++ b/src/nat-manager.js @@ -2,7 +2,7 @@ const NatAPI = require('@motrix/nat-api') const debug = require('debug') -const promisify = require('es6-promisify') +const { promisify } = require('es6-promisify') const Multiaddr = require('multiaddr') const log = Object.assign(debug('libp2p:nat'), { error: debug('libp2p:nat:err') @@ -132,14 +132,32 @@ class NatManager { } const client = new NatAPI(this._options) - const map = promisify(client.map, { context: client }) - const destroy = promisify(client.destroy, { context: client }) - const externalIp = promisify(client.externalIp, { context: client }) + /** @type {(...any) => any} */ + const map = promisify(client.map.bind(client)) + /** @type {(...any) => any} */ + const destroy = promisify(client.destroy.bind(client)) + /** @type {(...any) => any} */ + const externalIp = promisify(client.externalIp.bind(client)) + + // these are all network operations so add a retry this._client = { - // these are all network operations so add a retry + /** + * @param {...any} args + * @returns {Promise} + */ map: (...args) => retry(() => map(...args), { onFailedAttempt: log.error, unref: true }), + + /** + * @param {...any} args + * @returns {Promise} + */ destroy: (...args) => retry(() => destroy(...args), { onFailedAttempt: log.error, unref: true }), + + /** + * @param {...any} args + * @returns {Promise} + */ externalIp: (...args) => retry(() => externalIp(...args), { onFailedAttempt: log.error, unref: true }) } diff --git a/test/nat-manager/nat-manager.node.js b/test/nat-manager/nat-manager.node.js index 9bf922e505..557f266a1c 100644 --- a/test/nat-manager/nat-manager.node.js +++ b/test/nat-manager/nat-manager.node.js @@ -3,6 +3,7 @@ const { expect } = require('aegir/utils/chai') const sinon = require('sinon') +const { networkInterfaces } = require('os') const AddressManager = require('../../src/address-manager') const TransportManager = require('../../src/transport-manager') const Transport = require('libp2p-tcp') @@ -156,7 +157,7 @@ describe('Nat Manager (TCP)', () => { natManager, addressManager } = await createNatManager([ - '/ip6/::/tcp/5001' + '/ip6/::/tcp/0' ]) let observed = addressManager.getObservedAddrs().map(ma => ma.toString()) @@ -173,7 +174,7 @@ describe('Nat Manager (TCP)', () => { natManager, addressManager } = await createNatManager([ - '/ip6/::1/tcp/5001' + '/ip6/::1/tcp/0' ]) let observed = addressManager.getObservedAddrs().map(ma => ma.toString()) @@ -207,7 +208,7 @@ describe('Nat Manager (TCP)', () => { natManager, addressManager } = await createNatManager([ - '/ip4/127.0.0.1/tcp/5900' + '/ip4/127.0.0.1/tcp/0' ]) let observed = addressManager.getObservedAddrs().map(ma => ma.toString()) @@ -224,7 +225,7 @@ describe('Nat Manager (TCP)', () => { natManager, addressManager } = await createNatManager([ - '/ip4/0.0.0.0/tcp/5900/sctp/49832' + '/ip4/0.0.0.0/tcp/0/sctp/0' ]) let observed = addressManager.getObservedAddrs().map(ma => ma.toString()) @@ -241,4 +242,47 @@ describe('Nat Manager (TCP)', () => { new NatManager({ ttl: 5 }) // eslint-disable-line no-new }).to.throw().with.property('code', ERR_INVALID_PARAMETERS) }) + + it('shuts the nat api down when stopping', async function () { + function findRoutableAddress () { + const interfaces = networkInterfaces() + + for (const name of Object.keys(interfaces)) { + for (const iface of interfaces[name]) { + // Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses + if (iface.family === 'IPv4' && !iface.internal) { + return iface.address + } + } + } + } + + const addr = findRoutableAddress() + + if (!addr) { + // skip test if no non-loopback address is found + this.skip() + } + + const { + natManager + } = await createNatManager([ + `/ip4/${addr}/tcp/0` + ]) + + // use the actual nat manager client not the stub + delete natManager._client + + await natManager._start() + + const client = natManager._client + expect(client).to.be.ok() + + // ensure the client was stopped + const spy = sinon.spy(client, 'destroy') + + await natManager.stop() + + expect(spy.called).to.be.true() + }) }) From c4cae29ef3d761ce809d6dfcd7c4b43af7200a57 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Sat, 27 Mar 2021 12:15:35 +0000 Subject: [PATCH 140/447] chore: do not look up external IP during test (#906) The NAT manager test will throw if the current computer is behind a double NAT as at runtime it won't be possible to map external ports. We don't care about that during the test so configure a fake external IP so the test will still test the shut-down functionality on a computer that is behind a double NAT. --- test/nat-manager/nat-manager.node.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/nat-manager/nat-manager.node.js b/test/nat-manager/nat-manager.node.js index 557f266a1c..55acb0babd 100644 --- a/test/nat-manager/nat-manager.node.js +++ b/test/nat-manager/nat-manager.node.js @@ -268,7 +268,10 @@ describe('Nat Manager (TCP)', () => { natManager } = await createNatManager([ `/ip4/${addr}/tcp/0` - ]) + ], { + // so we don't try to look up the current computer's external address + externalIp: '184.12.31.4' + }) // use the actual nat manager client not the stub delete natManager._client From 3ea95ce642e336b0f54f938907a06b180e26b07e Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Sat, 27 Mar 2021 13:43:35 +0100 Subject: [PATCH 141/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 806e8b9e18..1e6eaf2096 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.30.11", + "version": "0.30.12", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From 8e1743cac40140f22a2bad784f8ef27a5ea94820 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Sat, 27 Mar 2021 13:43:36 +0100 Subject: [PATCH 142/447] chore: release version v0.30.12 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b32711ec3b..31704e47ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.30.12](https://github.com/libp2p/js-libp2p/compare/v0.30.11...v0.30.12) (2021-03-27) + + +### Bug Fixes + +* the API of es6-promisify is not the same as promisify-es6 ([#905](https://github.com/libp2p/js-libp2p/issues/905)) ([a7128f0](https://github.com/libp2p/js-libp2p/commit/a7128f07ec8d4b729145ecfc6ad1d585ffddea46)) + + + ## [0.30.11](https://github.com/libp2p/js-libp2p/compare/v0.30.10...v0.30.11) (2021-03-23) From 8506414ea1f537e09268e830e4a0c8d64ddf4647 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 15 Apr 2021 09:40:02 +0200 Subject: [PATCH 143/447] chore: config types and dependencies update (#904) BREAKING CHANGES: top level types were updated, multiaddr@9.0.0 is used, dialer and keychain internal property names changed and connectionManager minPeers is not supported anymore --- .aegir.js | 24 +- .github/workflows/examples.yml | 102 ++++ .github/workflows/main.yml | 107 +--- .gitignore | 1 + doc/CONFIGURATION.md | 2 +- examples/libp2p-in-the-browser/package.json | 8 +- examples/libp2p-in-the-browser/test.js | 4 +- examples/webrtc-direct/package.json | 6 +- examples/webrtc-direct/test.js | 6 +- package.json | 96 +-- scripts/node-globals.js | 2 + src/address-manager/index.js | 18 +- src/circuit/README.md | 4 +- src/circuit/auto-relay.js | 4 +- src/circuit/circuit/hop.js | 25 +- src/circuit/circuit/stop.js | 13 +- src/circuit/circuit/stream-handler.js | 18 +- src/circuit/circuit/utils.js | 24 +- src/circuit/index.js | 9 +- src/circuit/listener.js | 5 +- src/circuit/protocol/index.d.ts | 173 ++++++ src/circuit/protocol/index.js | 576 ++++++++++++++++-- src/circuit/protocol/index.proto | 42 ++ src/circuit/transport.js | 33 +- src/config.js | 21 +- src/connection-manager/index.js | 11 +- src/connection-manager/latency-monitor.js | 8 +- .../visibility-change-emitter.js | 2 - src/content-routing/index.js | 4 +- src/content-routing/utils.js | 2 +- src/dialer/dial-request.js | 3 +- src/dialer/index.js | 45 +- src/errors.js | 1 + src/get-peer.js | 21 +- src/identify/index.js | 40 +- src/identify/message.d.ts | 95 +++ src/identify/message.js | 347 ++++++++++- src/identify/message.proto | 30 + src/index.js | 117 ++-- src/insecure/plaintext.js | 36 +- src/insecure/proto.d.ts | 128 ++++ src/insecure/proto.js | 393 +++++++++++- src/insecure/proto.proto | 18 + src/keychain/cms.js | 9 + src/keychain/index.js | 52 +- src/keychain/util.js | 8 +- src/metrics/stats.js | 22 +- src/nat-manager.js | 53 +- src/peer-routing.js | 19 +- src/peer-store/address-book.js | 8 +- src/peer-store/book.js | 3 + src/peer-store/index.js | 2 - src/peer-store/metadata-book.js | 3 + src/peer-store/persistent/index.js | 49 +- .../persistent/pb/address-book.d.ts | 198 ++++++ src/peer-store/persistent/pb/address-book.js | 522 ++++++++++++++++ ...dress-book.proto.js => address-book.proto} | 12 +- src/peer-store/persistent/pb/proto-book.d.ts | 59 ++ src/peer-store/persistent/pb/proto-book.js | 157 +++++ src/peer-store/persistent/pb/proto-book.proto | 5 + .../persistent/pb/proto-book.proto.js | 12 - src/peer-store/proto-book.js | 4 + src/ping/index.js | 9 +- src/ping/util.js | 11 +- src/pnet/crypto.js | 8 +- src/pnet/index.js | 2 + src/pnet/key-generator.js | 12 +- src/pubsub-adapter.js | 8 +- src/record/envelope/envelope.d.ts | 77 +++ src/record/envelope/envelope.js | 243 ++++++++ .../{envelope.proto.js => envelope.proto} | 9 +- src/record/envelope/index.js | 17 +- src/record/peer-record/index.js | 22 +- src/record/peer-record/peer-record.d.ts | 133 ++++ src/record/peer-record/peer-record.js | 367 +++++++++++ ...peer-record.proto.js => peer-record.proto} | 14 +- src/registrar.js | 25 +- src/transport-manager.js | 7 +- src/types.ts | 103 ---- src/upgrader.js | 26 +- test/addresses/address-manager.spec.js | 8 +- test/addresses/addresses.node.js | 6 +- test/content-routing/content-routing.node.js | 14 +- test/content-routing/dht/operation.node.js | 6 +- test/core/ping.node.js | 3 + test/dialing/direct.node.js | 16 +- test/dialing/direct.spec.js | 28 +- test/dialing/resolver.spec.js | 24 +- test/fixtures/browser.js | 4 +- test/identify/index.spec.js | 19 +- test/keychain/keychain.spec.js | 18 +- test/nat-manager/nat-manager.node.js | 2 +- test/peer-discovery/index.node.js | 5 +- test/peer-discovery/index.spec.js | 8 +- test/peer-routing/peer-routing.node.js | 16 +- test/peer-store/address-book.spec.js | 8 +- test/peer-store/peer-store.node.js | 2 + test/peer-store/peer-store.spec.js | 10 +- test/peer-store/persisted-peer-store.spec.js | 29 +- test/pubsub/configuration.node.js | 4 +- test/pubsub/implementations.node.js | 6 +- test/pubsub/operation.node.js | 6 +- test/record/peer-record.spec.js | 12 +- test/relay/auto-relay.node.js | 6 +- test/relay/relay.node.js | 6 +- test/transports/transport-manager.node.js | 6 +- test/transports/transport-manager.spec.js | 12 +- test/ts-use/package.json | 24 + test/ts-use/src/main.ts | 192 ++++++ test/ts-use/tsconfig.json | 7 + test/upgrading/upgrader.spec.js | 6 +- test/utils/creators/peer.js | 4 +- test/utils/mockConnection.js | 6 +- tsconfig.json | 9 + 114 files changed, 4609 insertions(+), 837 deletions(-) create mode 100644 .github/workflows/examples.yml create mode 100644 scripts/node-globals.js create mode 100644 src/circuit/protocol/index.d.ts create mode 100644 src/circuit/protocol/index.proto create mode 100644 src/identify/message.d.ts create mode 100644 src/identify/message.proto create mode 100644 src/insecure/proto.d.ts create mode 100644 src/insecure/proto.proto create mode 100644 src/peer-store/persistent/pb/address-book.d.ts create mode 100644 src/peer-store/persistent/pb/address-book.js rename src/peer-store/persistent/pb/{address-book.proto.js => address-book.proto} (81%) create mode 100644 src/peer-store/persistent/pb/proto-book.d.ts create mode 100644 src/peer-store/persistent/pb/proto-book.js create mode 100644 src/peer-store/persistent/pb/proto-book.proto delete mode 100644 src/peer-store/persistent/pb/proto-book.proto.js create mode 100644 src/record/envelope/envelope.d.ts create mode 100644 src/record/envelope/envelope.js rename src/record/envelope/{envelope.proto.js => envelope.proto} (79%) create mode 100644 src/record/peer-record/peer-record.d.ts create mode 100644 src/record/peer-record/peer-record.js rename src/record/peer-record/{peer-record.proto.js => peer-record.proto} (52%) delete mode 100644 src/types.ts create mode 100644 test/ts-use/package.json create mode 100644 test/ts-use/src/main.ts create mode 100644 test/ts-use/tsconfig.json diff --git a/.aegir.js b/.aegir.js index b4eb9b0f11..a787ac4fc7 100644 --- a/.aegir.js +++ b/.aegir.js @@ -1,5 +1,6 @@ 'use strict' +const path = require('path') const Libp2p = require('./src') const { MULTIADDRS_WEBSOCKETS } = require('./test/fixtures/browser') const Peers = require('./test/fixtures/peers') @@ -47,16 +48,23 @@ const after = async () => { await libp2p.stop() } +/** @type {import('aegir').Options["build"]["config"]} */ +const esbuild = { + inject: [path.join(__dirname, './scripts/node-globals.js')] +} + +/** @type {import('aegir').PartialOptions} */ module.exports = { - bundlesize: { maxSize: '223kB' }, - hooks: { - pre: before, - post: after + build: { + bundlesizeMax: '253kB' }, - webpack: { - node: { - // needed by bcrypto - Buffer: true + test: { + before, + after, + browser: { + config: { + buildConfig: esbuild + } } } } diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml new file mode 100644 index 0000000000..b2b0aa37f6 --- /dev/null +++ b/.github/workflows/examples.yml @@ -0,0 +1,102 @@ +name: examples +on: + push: + branches: + - master + pull_request: + branches: + - '**' + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: npm install + - run: npx aegir lint + - run: npx aegir ts -p check + - run: npx aegir build + test-auto-relay-example: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: npm install + - run: cd examples && yarn && npm run test -- auto-relay + test-chat-example: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: npm install + - run: cd examples && yarn && npm run test -- chat + test-connection-encryption-example: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: npm install + - run: cd examples && yarn && npm run test -- connection-encryption + test-discovery-mechanisms-example: + needs: check + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - run: npm install + - run: cd examples && yarn && npm run test -- discovery-mechanisms + test-echo-example: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: npm install + - run: cd examples && yarn && npm run test -- echo + test-libp2p-in-the-browser-example: + needs: check + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - run: npm install + - run: cd examples && yarn && npm run test -- libp2p-in-the-browser + test-peer-and-content-routing-example: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: npm install + - run: cd examples && yarn && npm run test -- peer-and-content-routing + test-pnet-example: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: npm install + - run: cd examples && yarn && npm run test -- pnet + test-protocol-and-stream-muxing-example: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: npm install + - run: cd examples && yarn && npm run test -- protocol-and-stream-muxing + test-pubsub-example: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: npm install + - run: cd examples && yarn && npm run test -- pubsub + test-transports-example: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: npm install + - run: cd examples && yarn && npm run test -- transports + test-webrtc-direct-example: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: npm install + - run: cd examples && yarn && npm run test -- webrtc-direct \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6a3a59b81f..50b74aad04 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,11 +12,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - run: yarn - - run: yarn lint + - run: npm install + - run: npx aegir lint - uses: gozala/typescript-error-reporter-action@v1.0.8 - - run: yarn build - - run: yarn aegir dep-check + - run: npx aegir build + - run: npx aegir dep-check - uses: ipfs/aegir/actions/bundle-size@master name: size with: @@ -34,111 +34,34 @@ jobs: - uses: actions/setup-node@v1 with: node-version: ${{ matrix.node }} - - run: yarn - - run: npx nyc --reporter=lcov aegir test -t node -- --bail + - run: npm install + - run: npx aegir test -t node --cov --bail - uses: codecov/codecov-action@v1 test-chrome: needs: check runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - run: yarn + - run: npm install - run: npx aegir test -t browser -t webworker --bail test-firefox: needs: check runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - run: yarn - - run: npx aegir test -t browser -t webworker --bail -- --browsers FirefoxHeadless - test-interop: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - run: yarn - - run: cd node_modules/interop-libp2p && yarn && LIBP2P_JS=${GITHUB_WORKSPACE}/src/index.js npx aegir test -t node --bail - test-auto-relay-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - run: yarn - - run: cd examples && yarn && npm run test -- auto-relay - test-chat-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - run: yarn - - run: cd examples && yarn && npm run test -- chat - test-connection-encryption-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - run: yarn - - run: cd examples && yarn && npm run test -- connection-encryption - test-discovery-mechanisms-example: - needs: check - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - run: yarn - - run: cd examples && yarn && npm run test -- discovery-mechanisms - test-echo-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - run: yarn - - run: cd examples && yarn && npm run test -- echo - test-libp2p-in-the-browser-example: - needs: check - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - run: yarn - - run: cd examples && yarn && npm run test -- libp2p-in-the-browser - test-peer-and-content-routing-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - run: yarn - - run: cd examples && yarn && npm run test -- peer-and-content-routing - test-pnet-example: + - run: npm install + - run: npx aegir test -t browser -t webworker --bail -- --browser firefox + test-ts: needs: check runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - run: yarn - - run: cd examples && yarn && npm run test -- pnet - test-protocol-and-stream-muxing-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - run: yarn - - run: cd examples && yarn && npm run test -- protocol-and-stream-muxing - test-pubsub-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - run: yarn - - run: cd examples && yarn && npm run test -- pubsub - test-transports-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - run: yarn - - run: cd examples && yarn && npm run test -- transports - test-webrtc-direct-example: + - run: npm install + - run: npm run test:ts + test-interop: needs: check runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - run: yarn - - run: cd examples && yarn && npm run test -- webrtc-direct + - run: npm install + - run: cd node_modules/interop-libp2p && yarn && LIBP2P_JS=${GITHUB_WORKSPACE}/src/index.js npx aegir test -t node --bail diff --git a/.gitignore b/.gitignore index 61b3b5aa3d..69f5439f9c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ docs test/repo-tests* **/bundle.js .cache +.parcel-cache # Logs logs diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 89c30b3d07..f479cb3210 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -599,7 +599,7 @@ const TCP = require('libp2p-tcp') const MPLEX = require('libp2p-mplex') const { NOISE } = require('libp2p-noise') -const { FaultTolerance } = require('libp2p/src/transport-manager')} +const { FaultTolerance } = require('libp2p/src/transport-manager') const node = await Libp2p.create({ modules: { diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json index 5a93afd81a..9b928e8f7b 100644 --- a/examples/libp2p-in-the-browser/package.json +++ b/examples/libp2p-in-the-browser/package.json @@ -2,7 +2,7 @@ "name": "libp2p-in-browser", "version": "1.0.0", "description": "A libp2p node running in the browser", - "main": "index.js", + "main": "dist/index.html", "browserslist": [ "last 2 Chrome versions" ], @@ -20,8 +20,8 @@ "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" + "libp2p-webrtc-star": "^0.22.0", + "libp2p-websockets": "^0.15.0" }, "devDependencies": { "@babel/cli": "^7.13.10", @@ -29,6 +29,6 @@ "babel-plugin-syntax-async-functions": "^6.13.0", "babel-plugin-transform-regenerator": "^6.26.0", "babel-polyfill": "^6.26.0", - "parcel-bundler": "1.12.3" + "parcel": "next" } } diff --git a/examples/libp2p-in-the-browser/test.js b/examples/libp2p-in-the-browser/test.js index c0e67fe7bf..574e5739b2 100644 --- a/examples/libp2p-in-the-browser/test.js +++ b/examples/libp2p-in-the-browser/test.js @@ -17,10 +17,10 @@ async function run() { const out = chunk.toString() if (out.includes('Server running at')) { - url = out.replace('Server running at ', '') + url = out.split('Server running at ')[1] } - if (out.includes('✨ Built in ')) { + if (out.includes('Built in')) { try { const browser = await chromium.launch(); const page = await browser.newPage(); diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index f5491c7ba0..d96c2aed5e 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -3,6 +3,7 @@ "version": "0.0.1", "private": true, "description": "", + "main": "dist/index.html", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "parcel build index.html", @@ -15,14 +16,15 @@ "babel-plugin-syntax-async-functions": "^6.13.0", "babel-plugin-transform-regenerator": "^6.26.0", "babel-polyfill": "^6.26.0", - "parcel-bundler": "1.12.3" + "parcel-bundler": "1.12.3", + "util": "^0.12.3" }, "dependencies": { "libp2p": "../../", "libp2p-bootstrap": "^0.12.1", "libp2p-mplex": "^0.10.1", "libp2p-noise": "^2.0.1", - "libp2p-webrtc-direct": "^0.5.0", + "libp2p-webrtc-direct": "^0.6.0", "peer-id": "^0.14.3" }, "browser": { diff --git a/examples/webrtc-direct/test.js b/examples/webrtc-direct/test.js index 1768e1a082..a6a976a2df 100644 --- a/examples/webrtc-direct/test.js +++ b/examples/webrtc-direct/test.js @@ -50,10 +50,12 @@ async function test () { const out = chunk.toString() if (out.includes('Server running at')) { - dialerUrl = out.replace('Server running at ', '') + dialerUrl = out.split('Server running at ')[1] } - if (out.includes('✨ Built in ')) { + + if (out.includes('Built in ')) { + try { const browser = await chromium.launch(); const page = await browser.newPage(); diff --git a/package.json b/package.json index 1e6eaf2096..668e840c3e 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,28 @@ "scripts": { "lint": "aegir lint", "build": "aegir build", - "test": "npm run test:node && npm run test:browser", + "build:proto": "npm run build:proto:circuit && npm run build:proto:identify && npm run build:proto:plaintext && npm run build:proto:address-book && npm run build:proto:proto-book && npm run build:proto:peer-record && npm run build:proto:envelope", + "build:proto:circuit": "pbjs -t static-module -w commonjs --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/circuit/protocol/index.js ./src/circuit/protocol/index.proto", + "build:proto:identify": "pbjs -t static-module -w commonjs --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/identify/message.js ./src/identify/message.proto", + "build:proto:plaintext": "pbjs -t static-module -w commonjs --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/insecure/proto.js ./src/insecure/proto.proto", + "build:proto:address-book": "pbjs -t static-module -w commonjs --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/peer-store/persistent/pb/address-book.js ./src/peer-store/persistent/pb/address-book.proto", + "build:proto:proto-book": "pbjs -t static-module -w commonjs --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/peer-store/persistent/pb/proto-book.js ./src/peer-store/persistent/pb/proto-book.proto", + "build:proto:peer-record": "pbjs -t static-module -w commonjs --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/record/peer-record/peer-record.js ./src/record/peer-record/peer-record.proto", + "build:proto:envelope": "pbjs -t static-module -w commonjs --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/record/envelope/envelope.js ./src/record/envelope/envelope.proto", + "build:proto-types": "npm run build:proto-types:circuit && npm run build:proto-types:identify && npm run build:proto-types:plaintext && npm run build:proto-types:address-book && npm run build:proto-types:proto-book && npm run build:proto-types:peer-record && npm run build:proto-types:envelope", + "build:proto-types:circuit": "pbts -o src/circuit/protocol/index.d.ts src/circuit/protocol/index.js", + "build:proto-types:identify": "pbts -o src/identify/message.d.ts src/identify/message.js", + "build:proto-types:plaintext": "pbts -o src/insecure/proto.d.ts src/insecure/proto.js", + "build:proto-types:address-book": "pbts -o src/peer-store/persistent/pb/address-book.d.ts src/peer-store/persistent/pb/address-book.js", + "build:proto-types:proto-book": "pbts -o src/peer-store/persistent/pb/proto-book.d.ts src/peer-store/persistent/pb/proto-book.js", + "build:proto-types:peer-record": "pbts -o src/record/peer-record/peer-record.d.ts src/record/peer-record/peer-record.js", + "build:proto-types:envelope": "pbts -o src/record/envelope/envelope.d.ts src/record/envelope/envelope.js", + "test": "aegir test", + "test:ts": "aegir build --no-bundle && npm run test --prefix test/ts-use", "test:node": "aegir test -t node -f \"./test/**/*.{node,spec}.js\"", "test:browser": "aegir test -t browser", "test:examples": "cd examples && npm run test:all", + "prepare": "aegir build --no-bundle", "release": "aegir release -t node -t browser", "release-minor": "aegir release --type minor -t node -t browser", "release-major": "aegir release --type major -t node -t browser", @@ -47,12 +65,18 @@ "homepage": "https://libp2p.io", "license": "MIT", "engines": { - "node": ">=12.0.0", - "npm": ">=6.0.0" + "node": ">=14.0.0" }, "browser": { "@motrix/nat-api": false }, + "eslintConfig": { + "extends": "ipfs", + "ignorePatterns": [ + "!.aegir.js", + "test/ts-use" + ] + }, "dependencies": { "@motrix/nat-api": "^0.3.1", "abort-controller": "^3.0.0", @@ -62,9 +86,9 @@ "cids": "^1.1.5", "class-is": "^1.1.0", "debug": "^4.3.1", - "err-code": "^2.0.0", + "err-code": "^3.0.0", "es6-promisify": "^6.1.1", - "events": "^3.2.0", + "events": "^3.3.0", "hashlru": "^2.3.0", "interface-datastore": "^3.0.3", "ipfs-utils": "^6.0.0", @@ -73,76 +97,78 @@ "it-drain": "^1.0.3", "it-filter": "^1.0.1", "it-first": "^1.0.4", - "it-handshake": "^1.0.2", - "it-length-prefixed": "^3.1.0", + "it-handshake": "^2.0.0", + "it-length-prefixed": "^5.0.2", "it-map": "^1.0.4", "it-merge": "1.0.0", "it-pipe": "^1.1.0", - "it-protocol-buffers": "^0.2.0", "it-take": "1.0.0", "libp2p-crypto": "^0.19.0", - "libp2p-interfaces": "^0.8.1", - "libp2p-utils": "^0.2.2", - "mafmt": "^8.0.0", + "libp2p-interfaces": "^0.10.0", + "libp2p-utils": "^0.3.1", + "mafmt": "^9.0.0", "merge-options": "^3.0.4", "moving-average": "^1.0.0", - "multiaddr": "^8.1.0", - "multicodec": "^2.1.0", - "multihashing-async": "^2.0.1", - "multistream-select": "^1.0.0", + "multiaddr": "^9.0.1", + "multicodec": "^3.0.1", + "multihashing-async": "^2.1.2", + "multistream-select": "^2.0.0", "mutable-proxy": "^1.0.0", "node-forge": "^0.10.0", "p-any": "^3.0.0", "p-fifo": "^1.0.0", - "p-retry": "^4.2.0", - "p-settle": "^4.0.1", + "p-retry": "^4.4.0", + "p-settle": "^4.1.1", "peer-id": "^0.14.2", - "private-ip": "^2.0.0", - "protons": "^2.0.0", - "retimer": "^2.0.0", + "private-ip": "^2.1.0", + "protobufjs": "^6.10.2", + "retimer": "^3.0.0", "sanitize-filename": "^1.6.3", "set-delayed-interval": "^1.0.0", "streaming-iterables": "^5.0.2", "timeout-abort-controller": "^1.1.1", "varint": "^6.0.0", - "xsalsa20": "^1.0.2" + "xsalsa20": "^1.1.0" }, "devDependencies": { "@nodeutils/defaults-deep": "^1.1.0", "@types/es6-promisify": "^6.0.0", + "@types/node-forge": "^0.9.7", + "@types/varint": "^6.0.0", "abortable-iterator": "^3.0.0", - "aegir": "^29.2.0", + "aegir": "^33.0.0", + "buffer": "^6.0.3", "chai-bytes": "^0.1.2", "chai-string": "^1.5.0", - "delay": "^4.4.0", + "delay": "^5.0.0", "interop-libp2p": "^0.3.0", "into-stream": "^6.0.0", - "ipfs-http-client": "^48.2.2", + "ipfs-http-client": "^49.0.4", "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.9.0", - "libp2p-delegated-peer-routing": "^0.8.0", - "libp2p-floodsub": "^0.24.0", + "libp2p-bootstrap": "^0.12.3", + "libp2p-delegated-content-routing": "^0.10.0", + "libp2p-delegated-peer-routing": "^0.9.0", + "libp2p-floodsub": "^0.25.0", "libp2p-gossipsub": "^0.8.0", - "libp2p-kad-dht": "^0.20.5", - "libp2p-mdns": "^0.15.0", + "libp2p-kad-dht": "^0.21.0", + "libp2p-mdns": "^0.16.0", "libp2p-mplex": "^0.10.1", "libp2p-noise": "^2.0.0", - "libp2p-secio": "^0.13.1", "libp2p-tcp": "^0.15.1", - "libp2p-webrtc-star": "^0.20.0", + "libp2p-webrtc-star": "^0.22.0", "libp2p-websockets": "^0.15.0", - "multihashes": "^3.0.1", + "multihashes": "^4.0.2", "nock": "^13.0.3", "p-defer": "^3.0.0", "p-times": "^3.0.0", "p-wait-for": "^3.2.0", "rimraf": "^3.0.2", - "sinon": "^9.2.4", - "uint8arrays": "^2.0.5" + "sinon": "^10.0.0", + "uint8arrays": "^2.1.3", + "util": "^0.12.3" }, "contributors": [ "David Dias ", diff --git a/scripts/node-globals.js b/scripts/node-globals.js new file mode 100644 index 0000000000..cc0a4c9e9c --- /dev/null +++ b/scripts/node-globals.js @@ -0,0 +1,2 @@ +// @ts-nocheck +export const { Buffer } = require('buffer') diff --git a/src/address-manager/index.js b/src/address-manager/index.js index 625432252e..bc4024c4be 100644 --- a/src/address-manager/index.js +++ b/src/address-manager/index.js @@ -1,15 +1,9 @@ 'use strict' -/** @typedef {import('../types').EventEmitterFactory} Events */ -/** @type Events */ const EventEmitter = require('events') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const PeerId = require('peer-id') -/** - * @typedef {import('multiaddr')} Multiaddr - */ - /** * @typedef {Object} AddressManagerOptions * @property {string[]} [listen = []] - list of multiaddrs string representation to listen. @@ -47,7 +41,7 @@ class AddressManager extends EventEmitter { * @returns {Multiaddr[]} */ getListenAddrs () { - return Array.from(this.listen).map((a) => multiaddr(a)) + return Array.from(this.listen).map((a) => new Multiaddr(a)) } /** @@ -56,7 +50,7 @@ class AddressManager extends EventEmitter { * @returns {Multiaddr[]} */ getAnnounceAddrs () { - return Array.from(this.announce).map((a) => multiaddr(a)) + return Array.from(this.announce).map((a) => new Multiaddr(a)) } /** @@ -65,7 +59,7 @@ class AddressManager extends EventEmitter { * @returns {Array} */ getObservedAddrs () { - return Array.from(this.observed).map((a) => multiaddr(a)) + return Array.from(this.observed).map((a) => new Multiaddr(a)) } /** @@ -74,7 +68,7 @@ class AddressManager extends EventEmitter { * @param {string | Multiaddr} addr */ addObservedAddr (addr) { - let ma = multiaddr(addr) + let ma = new Multiaddr(addr) const remotePeer = ma.getPeerId() // strip our peer id if it has been passed @@ -83,7 +77,7 @@ class AddressManager extends EventEmitter { // use same encoding for comparison if (remotePeerId.equals(this.peerId)) { - ma = ma.decapsulate(multiaddr(`/p2p/${this.peerId}`)) + ma = ma.decapsulate(new Multiaddr(`/p2p/${this.peerId}`)) } } diff --git a/src/circuit/README.md b/src/circuit/README.md index e2dd5018b9..680a76c204 100644 --- a/src/circuit/README.md +++ b/src/circuit/README.md @@ -37,7 +37,7 @@ Libp2p circuit configuration can be seen at [Setup with Relay](../../doc/CONFIGU Once you have a circuit relay node running, you can configure other nodes to use it as a relay as follows: ```js -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const Libp2p = require('libp2p') const TCP = require('libp2p-tcp') const MPLEX = require('libp2p-mplex') @@ -47,7 +47,7 @@ const relayAddr = ... const node = await Libp2p.create({ addresses: { - listen: [multiaddr(`${relayAddr}/p2p-circuit`)] + listen: [new Multiaddr(`${relayAddr}/p2p-circuit`)] }, modules: { transport: [TCP], diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js index 122ac979fb..a99bb14d32 100644 --- a/src/circuit/auto-relay.js +++ b/src/circuit/auto-relay.js @@ -7,7 +7,7 @@ const log = Object.assign(debug('libp2p:auto-relay'), { const uint8ArrayFromString = require('uint8arrays/from-string') const uint8ArrayToString = require('uint8arrays/to-string') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const PeerId = require('peer-id') const { relay: multicodec } = require('./multicodec') @@ -157,7 +157,7 @@ class AutoRelay { // Attempt to listen on relay try { - await this._transportManager.listen([multiaddr(listenAddr)]) + await this._transportManager.listen([new Multiaddr(listenAddr)]) // Announce multiaddrs will update on listen success by TransportManager event being triggered } catch (err) { log.error(err) diff --git a/src/circuit/circuit/hop.js b/src/circuit/circuit/hop.js index 8e23e6a894..b2ce72de0a 100644 --- a/src/circuit/circuit/hop.js +++ b/src/circuit/circuit/hop.js @@ -18,17 +18,17 @@ const { stop } = require('./stop') const multicodec = require('./../multicodec') /** - * @typedef {import('../../types').CircuitRequest} CircuitRequest + * @typedef {import('../protocol').ICircuitRelay} ICircuitRelay * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection - * @typedef {import('./stream-handler')} StreamHandlerT + * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream * @typedef {import('../transport')} Transport */ /** * @typedef {Object} HopRequest * @property {Connection} connection - * @property {CircuitRequest} request - * @property {StreamHandlerT} streamHandler + * @property {ICircuitRelay} request + * @property {StreamHandler} streamHandler * @property {Transport} circuit */ @@ -58,6 +58,11 @@ async function handleHop ({ return log.error('invalid hop request via peer %s', connection.remotePeer.toB58String(), err) } + if (!request.dstPeer) { + log('HOP request received but we do not receive a dstPeer') + return + } + // Get the connection to the destination (stop) peer const destinationPeer = new PeerId(request.dstPeer.id) @@ -113,8 +118,8 @@ async function handleHop ({ * * @param {object} options * @param {Connection} options.connection - Connection to the relay - * @param {CircuitRequest} options.request - * @returns {Promise} + * @param {ICircuitRelay} options.request + * @returns {Promise} */ async function hop ({ connection, @@ -128,6 +133,10 @@ async function hop ({ const response = await streamHandler.read() + if (!response) { + throw errCode(new Error('HOP request had no response'), Errors.ERR_HOP_REQUEST_FAILED) + } + if (response.code === CircuitPB.Status.SUCCESS) { log('hop request was successful') return streamHandler.rest() @@ -159,7 +168,7 @@ async function canHop ({ const response = await streamHandler.read() await streamHandler.close() - if (response.code !== CircuitPB.Status.SUCCESS) { + if (!response || response.code !== CircuitPB.Status.SUCCESS) { return false } @@ -171,7 +180,7 @@ async function canHop ({ * * @param {Object} options * @param {Connection} options.connection - * @param {StreamHandlerT} options.streamHandler + * @param {StreamHandler} options.streamHandler * @param {Transport} options.circuit * @private */ diff --git a/src/circuit/circuit/stop.js b/src/circuit/circuit/stop.js index 111b811dc2..6fa92a07c0 100644 --- a/src/circuit/circuit/stop.js +++ b/src/circuit/circuit/stop.js @@ -13,8 +13,7 @@ const { validateAddrs } = require('./utils') /** * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream - * @typedef {import('../../types').CircuitRequest} CircuitRequest - * @typedef {import('./stream-handler')} StreamHandlerT + * @typedef {import('../protocol').ICircuitRelay} ICircuitRelay */ /** @@ -23,8 +22,8 @@ const { validateAddrs } = require('./utils') * @private * @param {Object} options * @param {Connection} options.connection - * @param {CircuitRequest} options.request - The CircuitRelay protobuf request (unencoded) - * @param {StreamHandlerT} options.streamHandler + * @param {ICircuitRelay} options.request - The CircuitRelay protobuf request (unencoded) + * @param {StreamHandler} options.streamHandler * @returns {Promise|void} Resolves a duplex iterable */ module.exports.handleStop = function handleStop ({ @@ -54,7 +53,7 @@ module.exports.handleStop = function handleStop ({ * @private * @param {Object} options * @param {Connection} options.connection - * @param {CircuitRequest} options.request - The CircuitRelay protobuf request (unencoded) + * @param {ICircuitRelay} options.request - The CircuitRelay protobuf request (unencoded) * @returns {Promise} Resolves a duplex iterable */ module.exports.stop = async function stop ({ @@ -68,6 +67,10 @@ module.exports.stop = async function stop ({ streamHandler.write(request) const response = await streamHandler.read() + if (!response) { + return streamHandler.close() + } + if (response.code === CircuitPB.Status.SUCCESS) { log('stop request to %s was successful', connection.remotePeer.toB58String()) return streamHandler.rest() diff --git a/src/circuit/circuit/stream-handler.js b/src/circuit/circuit/stream-handler.js index 5be2c6edf5..bbae7d6825 100644 --- a/src/circuit/circuit/stream-handler.js +++ b/src/circuit/circuit/stream-handler.js @@ -6,16 +6,15 @@ const log = Object.assign(debug('libp2p:circuit:stream-handler'), { }) const lp = require('it-length-prefixed') +// @ts-ignore it-handshake does not export types const handshake = require('it-handshake') -const { CircuitRelay: CircuitPB } = require('../protocol') +const { CircuitRelay } = require('../protocol') /** * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream + * @typedef {import('../protocol').ICircuitRelay} ICircuitRelay */ -/** - * @template T - */ class StreamHandler { /** * Create a stream handler for connection @@ -29,6 +28,7 @@ class StreamHandler { this.stream = stream this.shake = handshake(this.stream) + // @ts-ignore options are not optional this.decoder = lp.decode.fromReader(this.shake.reader, { maxDataLength: maxLength }) } @@ -36,12 +36,11 @@ class StreamHandler { * Read and decode message * * @async - * @returns {Promise} */ async read () { const msg = await this.decoder.next() if (msg.value) { - const value = CircuitPB.decode(msg.value.slice()) + const value = CircuitRelay.decode(msg.value.slice()) log('read message type', value.type) return value } @@ -54,13 +53,13 @@ class StreamHandler { /** * Encode and write array of buffers * - * @param {CircuitPB} msg - An unencoded CircuitRelay protobuf message + * @param {ICircuitRelay} msg - An unencoded CircuitRelay protobuf message * @returns {void} */ write (msg) { log('write message type %s', msg.type) // @ts-ignore lp.encode expects type type 'Buffer | BufferList', not 'Uint8Array' - this.shake.write(lp.encode.single(CircuitPB.encode(msg))) + this.shake.write(lp.encode.single(CircuitRelay.encode(msg).finish())) } /** @@ -73,6 +72,9 @@ class StreamHandler { return this.shake.stream } + /** + * @param {ICircuitRelay} msg - An unencoded CircuitRelay protobuf message + */ end (msg) { this.write(msg) this.close() diff --git a/src/circuit/circuit/utils.js b/src/circuit/circuit/utils.js index 65c5afe47d..a69dcb50ba 100644 --- a/src/circuit/circuit/utils.js +++ b/src/circuit/circuit/utils.js @@ -1,18 +1,18 @@ 'use strict' -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const { CircuitRelay } = require('../protocol') /** * @typedef {import('./stream-handler')} StreamHandler - * @typedef {import('../../types').CircuitStatus} CircuitStatus + * @typedef {import('../protocol').ICircuitRelay} ICircuitRelay */ /** * Write a response * * @param {StreamHandler} streamHandler - * @param {CircuitStatus} status + * @param {import('../protocol').CircuitRelay.Status} status */ function writeResponse (streamHandler, status) { streamHandler.write({ @@ -24,14 +24,16 @@ function writeResponse (streamHandler, status) { /** * Validate incomming HOP/STOP message * - * @param {*} msg - A CircuitRelay unencoded protobuf message + * @param {ICircuitRelay} msg - A CircuitRelay unencoded protobuf message * @param {StreamHandler} streamHandler */ function validateAddrs (msg, streamHandler) { try { - msg.dstPeer.addrs.forEach((addr) => { - return multiaddr(addr) - }) + if (msg.dstPeer && msg.dstPeer.addrs) { + msg.dstPeer.addrs.forEach((addr) => { + return new Multiaddr(addr) + }) + } } catch (err) { writeResponse(streamHandler, msg.type === CircuitRelay.Type.HOP ? CircuitRelay.Status.HOP_DST_MULTIADDR_INVALID @@ -40,9 +42,11 @@ function validateAddrs (msg, streamHandler) { } try { - msg.srcPeer.addrs.forEach((addr) => { - return multiaddr(addr) - }) + if (msg.srcPeer && msg.srcPeer.addrs) { + msg.srcPeer.addrs.forEach((addr) => { + return new Multiaddr(addr) + }) + } } catch (err) { writeResponse(streamHandler, msg.type === CircuitRelay.Type.HOP ? CircuitRelay.Status.HOP_SRC_MULTIADDR_INVALID diff --git a/src/circuit/index.js b/src/circuit/index.js index 447d829ac5..da8e879e52 100644 --- a/src/circuit/index.js +++ b/src/circuit/index.js @@ -8,13 +8,12 @@ const log = Object.assign(debug('libp2p:relay'), { const { setDelayedInterval, clearDelayedInterval +// @ts-ignore set-delayed-interval does not export types } = require('set-delayed-interval') const AutoRelay = require('./auto-relay') const { namespaceToCid } = require('./utils') const { - ADVERTISE_BOOT_DELAY, - ADVERTISE_TTL, RELAY_RENDEZVOUS_NS } = require('./constants') @@ -45,12 +44,6 @@ class Relay { constructor (libp2p) { this._libp2p = libp2p this._options = { - advertise: { - bootDelay: ADVERTISE_BOOT_DELAY, - enabled: true, - ttl: ADVERTISE_TTL, - ...libp2p._config.relay.advertise - }, ...libp2p._config.relay } diff --git a/src/circuit/listener.js b/src/circuit/listener.js index d19cca5e46..a3f11dc7d4 100644 --- a/src/circuit/listener.js +++ b/src/circuit/listener.js @@ -1,10 +1,9 @@ 'use strict' const { EventEmitter } = require('events') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') /** - * @typedef {import('multiaddr')} Multiaddr * @typedef {import('libp2p-interfaces/src/transport/types').Listener} Listener */ @@ -24,7 +23,7 @@ module.exports = (libp2p) => { async function listen (addr) { const addrString = String(addr).split('/p2p-circuit').find(a => a !== '') - const relayConn = await libp2p.dial(multiaddr(addrString)) + const relayConn = await libp2p.dial(new Multiaddr(addrString)) const relayedAddr = relayConn.remoteAddr.encapsulate('/p2p-circuit') listeningAddrs.set(relayConn.remotePeer.toB58String(), relayedAddr) diff --git a/src/circuit/protocol/index.d.ts b/src/circuit/protocol/index.d.ts new file mode 100644 index 0000000000..68e4880b95 --- /dev/null +++ b/src/circuit/protocol/index.d.ts @@ -0,0 +1,173 @@ +import * as $protobuf from "protobufjs"; +/** Properties of a CircuitRelay. */ +export interface ICircuitRelay { + + /** CircuitRelay type */ + type?: (CircuitRelay.Type|null); + + /** CircuitRelay srcPeer */ + srcPeer?: (CircuitRelay.IPeer|null); + + /** CircuitRelay dstPeer */ + dstPeer?: (CircuitRelay.IPeer|null); + + /** CircuitRelay code */ + code?: (CircuitRelay.Status|null); +} + +/** Represents a CircuitRelay. */ +export class CircuitRelay implements ICircuitRelay { + + /** + * Constructs a new CircuitRelay. + * @param [p] Properties to set + */ + constructor(p?: ICircuitRelay); + + /** CircuitRelay type. */ + public type: CircuitRelay.Type; + + /** CircuitRelay srcPeer. */ + public srcPeer?: (CircuitRelay.IPeer|null); + + /** CircuitRelay dstPeer. */ + public dstPeer?: (CircuitRelay.IPeer|null); + + /** CircuitRelay code. */ + public code: CircuitRelay.Status; + + /** + * Encodes the specified CircuitRelay message. Does not implicitly {@link CircuitRelay.verify|verify} messages. + * @param m CircuitRelay message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: ICircuitRelay, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a CircuitRelay message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns CircuitRelay + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): CircuitRelay; + + /** + * Creates a CircuitRelay message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns CircuitRelay + */ + public static fromObject(d: { [k: string]: any }): CircuitRelay; + + /** + * Creates a plain object from a CircuitRelay message. Also converts values to other types if specified. + * @param m CircuitRelay + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: CircuitRelay, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this CircuitRelay to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; +} + +export namespace CircuitRelay { + + /** Status enum. */ + enum Status { + SUCCESS = 100, + HOP_SRC_ADDR_TOO_LONG = 220, + HOP_DST_ADDR_TOO_LONG = 221, + HOP_SRC_MULTIADDR_INVALID = 250, + HOP_DST_MULTIADDR_INVALID = 251, + HOP_NO_CONN_TO_DST = 260, + HOP_CANT_DIAL_DST = 261, + HOP_CANT_OPEN_DST_STREAM = 262, + HOP_CANT_SPEAK_RELAY = 270, + HOP_CANT_RELAY_TO_SELF = 280, + STOP_SRC_ADDR_TOO_LONG = 320, + STOP_DST_ADDR_TOO_LONG = 321, + STOP_SRC_MULTIADDR_INVALID = 350, + STOP_DST_MULTIADDR_INVALID = 351, + STOP_RELAY_REFUSED = 390, + MALFORMED_MESSAGE = 400 + } + + /** Type enum. */ + enum Type { + HOP = 1, + STOP = 2, + STATUS = 3, + CAN_HOP = 4 + } + + /** Properties of a Peer. */ + interface IPeer { + + /** Peer id */ + id: Uint8Array; + + /** Peer addrs */ + addrs?: (Uint8Array[]|null); + } + + /** Represents a Peer. */ + class Peer implements IPeer { + + /** + * Constructs a new Peer. + * @param [p] Properties to set + */ + constructor(p?: CircuitRelay.IPeer); + + /** Peer id. */ + public id: Uint8Array; + + /** Peer addrs. */ + public addrs: Uint8Array[]; + + /** + * Encodes the specified Peer message. Does not implicitly {@link CircuitRelay.Peer.verify|verify} messages. + * @param m Peer message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: CircuitRelay.IPeer, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a Peer message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns Peer + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): CircuitRelay.Peer; + + /** + * Creates a Peer message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns Peer + */ + public static fromObject(d: { [k: string]: any }): CircuitRelay.Peer; + + /** + * Creates a plain object from a Peer message. Also converts values to other types if specified. + * @param m Peer + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: CircuitRelay.Peer, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Peer to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + } +} diff --git a/src/circuit/protocol/index.js b/src/circuit/protocol/index.js index a9d3e31a6f..be12eb9b37 100644 --- a/src/circuit/protocol/index.js +++ b/src/circuit/protocol/index.js @@ -1,46 +1,530 @@ -'use strict' -const protobuf = require('protons') - -/** @type {{CircuitRelay: import('../../types').CircuitMessageProto}} */ -module.exports = protobuf(` -message CircuitRelay { - - enum Status { - SUCCESS = 100; - HOP_SRC_ADDR_TOO_LONG = 220; - HOP_DST_ADDR_TOO_LONG = 221; - HOP_SRC_MULTIADDR_INVALID = 250; - HOP_DST_MULTIADDR_INVALID = 251; - HOP_NO_CONN_TO_DST = 260; - HOP_CANT_DIAL_DST = 261; - HOP_CANT_OPEN_DST_STREAM = 262; - HOP_CANT_SPEAK_RELAY = 270; - HOP_CANT_RELAY_TO_SELF = 280; - STOP_SRC_ADDR_TOO_LONG = 320; - STOP_DST_ADDR_TOO_LONG = 321; - STOP_SRC_MULTIADDR_INVALID = 350; - STOP_DST_MULTIADDR_INVALID = 351; - STOP_RELAY_REFUSED = 390; - MALFORMED_MESSAGE = 400; - } - - enum Type { // RPC identifier, either HOP, STOP or STATUS - HOP = 1; - STOP = 2; - STATUS = 3; - CAN_HOP = 4; - } - - message Peer { - required bytes id = 1; // peer id - repeated bytes addrs = 2; // peer's known addresses - } - - optional Type type = 1; // Type of the message - - optional Peer srcPeer = 2; // srcPeer and dstPeer are used when Type is HOP or STATUS - optional Peer dstPeer = 3; - - optional Status code = 4; // Status code, used when Type is STATUS -} -`) +/*eslint-disable*/ +"use strict"; + +var $protobuf = require("protobufjs/minimal"); + +// Common aliases +var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; + +// Exported root namespace +var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); + +$root.CircuitRelay = (function() { + + /** + * Properties of a CircuitRelay. + * @exports ICircuitRelay + * @interface ICircuitRelay + * @property {CircuitRelay.Type|null} [type] CircuitRelay type + * @property {CircuitRelay.IPeer|null} [srcPeer] CircuitRelay srcPeer + * @property {CircuitRelay.IPeer|null} [dstPeer] CircuitRelay dstPeer + * @property {CircuitRelay.Status|null} [code] CircuitRelay code + */ + + /** + * Constructs a new CircuitRelay. + * @exports CircuitRelay + * @classdesc Represents a CircuitRelay. + * @implements ICircuitRelay + * @constructor + * @param {ICircuitRelay=} [p] Properties to set + */ + function CircuitRelay(p) { + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + /** + * CircuitRelay type. + * @member {CircuitRelay.Type} type + * @memberof CircuitRelay + * @instance + */ + CircuitRelay.prototype.type = 1; + + /** + * CircuitRelay srcPeer. + * @member {CircuitRelay.IPeer|null|undefined} srcPeer + * @memberof CircuitRelay + * @instance + */ + CircuitRelay.prototype.srcPeer = null; + + /** + * CircuitRelay dstPeer. + * @member {CircuitRelay.IPeer|null|undefined} dstPeer + * @memberof CircuitRelay + * @instance + */ + CircuitRelay.prototype.dstPeer = null; + + /** + * CircuitRelay code. + * @member {CircuitRelay.Status} code + * @memberof CircuitRelay + * @instance + */ + CircuitRelay.prototype.code = 100; + + /** + * Encodes the specified CircuitRelay message. Does not implicitly {@link CircuitRelay.verify|verify} messages. + * @function encode + * @memberof CircuitRelay + * @static + * @param {ICircuitRelay} m CircuitRelay message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + CircuitRelay.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.type != null && Object.hasOwnProperty.call(m, "type")) + w.uint32(8).int32(m.type); + if (m.srcPeer != null && Object.hasOwnProperty.call(m, "srcPeer")) + $root.CircuitRelay.Peer.encode(m.srcPeer, w.uint32(18).fork()).ldelim(); + if (m.dstPeer != null && Object.hasOwnProperty.call(m, "dstPeer")) + $root.CircuitRelay.Peer.encode(m.dstPeer, w.uint32(26).fork()).ldelim(); + if (m.code != null && Object.hasOwnProperty.call(m, "code")) + w.uint32(32).int32(m.code); + return w; + }; + + /** + * Decodes a CircuitRelay message from the specified reader or buffer. + * @function decode + * @memberof CircuitRelay + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {CircuitRelay} CircuitRelay + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + CircuitRelay.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.CircuitRelay(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + m.type = r.int32(); + break; + case 2: + m.srcPeer = $root.CircuitRelay.Peer.decode(r, r.uint32()); + break; + case 3: + m.dstPeer = $root.CircuitRelay.Peer.decode(r, r.uint32()); + break; + case 4: + m.code = r.int32(); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates a CircuitRelay message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof CircuitRelay + * @static + * @param {Object.} d Plain object + * @returns {CircuitRelay} CircuitRelay + */ + CircuitRelay.fromObject = function fromObject(d) { + if (d instanceof $root.CircuitRelay) + return d; + var m = new $root.CircuitRelay(); + switch (d.type) { + case "HOP": + case 1: + m.type = 1; + break; + case "STOP": + case 2: + m.type = 2; + break; + case "STATUS": + case 3: + m.type = 3; + break; + case "CAN_HOP": + case 4: + m.type = 4; + break; + } + if (d.srcPeer != null) { + if (typeof d.srcPeer !== "object") + throw TypeError(".CircuitRelay.srcPeer: object expected"); + m.srcPeer = $root.CircuitRelay.Peer.fromObject(d.srcPeer); + } + if (d.dstPeer != null) { + if (typeof d.dstPeer !== "object") + throw TypeError(".CircuitRelay.dstPeer: object expected"); + m.dstPeer = $root.CircuitRelay.Peer.fromObject(d.dstPeer); + } + switch (d.code) { + case "SUCCESS": + case 100: + m.code = 100; + break; + case "HOP_SRC_ADDR_TOO_LONG": + case 220: + m.code = 220; + break; + case "HOP_DST_ADDR_TOO_LONG": + case 221: + m.code = 221; + break; + case "HOP_SRC_MULTIADDR_INVALID": + case 250: + m.code = 250; + break; + case "HOP_DST_MULTIADDR_INVALID": + case 251: + m.code = 251; + break; + case "HOP_NO_CONN_TO_DST": + case 260: + m.code = 260; + break; + case "HOP_CANT_DIAL_DST": + case 261: + m.code = 261; + break; + case "HOP_CANT_OPEN_DST_STREAM": + case 262: + m.code = 262; + break; + case "HOP_CANT_SPEAK_RELAY": + case 270: + m.code = 270; + break; + case "HOP_CANT_RELAY_TO_SELF": + case 280: + m.code = 280; + break; + case "STOP_SRC_ADDR_TOO_LONG": + case 320: + m.code = 320; + break; + case "STOP_DST_ADDR_TOO_LONG": + case 321: + m.code = 321; + break; + case "STOP_SRC_MULTIADDR_INVALID": + case 350: + m.code = 350; + break; + case "STOP_DST_MULTIADDR_INVALID": + case 351: + m.code = 351; + break; + case "STOP_RELAY_REFUSED": + case 390: + m.code = 390; + break; + case "MALFORMED_MESSAGE": + case 400: + m.code = 400; + break; + } + return m; + }; + + /** + * Creates a plain object from a CircuitRelay message. Also converts values to other types if specified. + * @function toObject + * @memberof CircuitRelay + * @static + * @param {CircuitRelay} m CircuitRelay + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + CircuitRelay.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.defaults) { + d.type = o.enums === String ? "HOP" : 1; + d.srcPeer = null; + d.dstPeer = null; + d.code = o.enums === String ? "SUCCESS" : 100; + } + if (m.type != null && m.hasOwnProperty("type")) { + d.type = o.enums === String ? $root.CircuitRelay.Type[m.type] : m.type; + } + if (m.srcPeer != null && m.hasOwnProperty("srcPeer")) { + d.srcPeer = $root.CircuitRelay.Peer.toObject(m.srcPeer, o); + } + if (m.dstPeer != null && m.hasOwnProperty("dstPeer")) { + d.dstPeer = $root.CircuitRelay.Peer.toObject(m.dstPeer, o); + } + if (m.code != null && m.hasOwnProperty("code")) { + d.code = o.enums === String ? $root.CircuitRelay.Status[m.code] : m.code; + } + return d; + }; + + /** + * Converts this CircuitRelay to JSON. + * @function toJSON + * @memberof CircuitRelay + * @instance + * @returns {Object.} JSON object + */ + CircuitRelay.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Status enum. + * @name CircuitRelay.Status + * @enum {number} + * @property {number} SUCCESS=100 SUCCESS value + * @property {number} HOP_SRC_ADDR_TOO_LONG=220 HOP_SRC_ADDR_TOO_LONG value + * @property {number} HOP_DST_ADDR_TOO_LONG=221 HOP_DST_ADDR_TOO_LONG value + * @property {number} HOP_SRC_MULTIADDR_INVALID=250 HOP_SRC_MULTIADDR_INVALID value + * @property {number} HOP_DST_MULTIADDR_INVALID=251 HOP_DST_MULTIADDR_INVALID value + * @property {number} HOP_NO_CONN_TO_DST=260 HOP_NO_CONN_TO_DST value + * @property {number} HOP_CANT_DIAL_DST=261 HOP_CANT_DIAL_DST value + * @property {number} HOP_CANT_OPEN_DST_STREAM=262 HOP_CANT_OPEN_DST_STREAM value + * @property {number} HOP_CANT_SPEAK_RELAY=270 HOP_CANT_SPEAK_RELAY value + * @property {number} HOP_CANT_RELAY_TO_SELF=280 HOP_CANT_RELAY_TO_SELF value + * @property {number} STOP_SRC_ADDR_TOO_LONG=320 STOP_SRC_ADDR_TOO_LONG value + * @property {number} STOP_DST_ADDR_TOO_LONG=321 STOP_DST_ADDR_TOO_LONG value + * @property {number} STOP_SRC_MULTIADDR_INVALID=350 STOP_SRC_MULTIADDR_INVALID value + * @property {number} STOP_DST_MULTIADDR_INVALID=351 STOP_DST_MULTIADDR_INVALID value + * @property {number} STOP_RELAY_REFUSED=390 STOP_RELAY_REFUSED value + * @property {number} MALFORMED_MESSAGE=400 MALFORMED_MESSAGE value + */ + CircuitRelay.Status = (function() { + var valuesById = {}, values = Object.create(valuesById); + values[valuesById[100] = "SUCCESS"] = 100; + values[valuesById[220] = "HOP_SRC_ADDR_TOO_LONG"] = 220; + values[valuesById[221] = "HOP_DST_ADDR_TOO_LONG"] = 221; + values[valuesById[250] = "HOP_SRC_MULTIADDR_INVALID"] = 250; + values[valuesById[251] = "HOP_DST_MULTIADDR_INVALID"] = 251; + values[valuesById[260] = "HOP_NO_CONN_TO_DST"] = 260; + values[valuesById[261] = "HOP_CANT_DIAL_DST"] = 261; + values[valuesById[262] = "HOP_CANT_OPEN_DST_STREAM"] = 262; + values[valuesById[270] = "HOP_CANT_SPEAK_RELAY"] = 270; + values[valuesById[280] = "HOP_CANT_RELAY_TO_SELF"] = 280; + values[valuesById[320] = "STOP_SRC_ADDR_TOO_LONG"] = 320; + values[valuesById[321] = "STOP_DST_ADDR_TOO_LONG"] = 321; + values[valuesById[350] = "STOP_SRC_MULTIADDR_INVALID"] = 350; + values[valuesById[351] = "STOP_DST_MULTIADDR_INVALID"] = 351; + values[valuesById[390] = "STOP_RELAY_REFUSED"] = 390; + values[valuesById[400] = "MALFORMED_MESSAGE"] = 400; + return values; + })(); + + /** + * Type enum. + * @name CircuitRelay.Type + * @enum {number} + * @property {number} HOP=1 HOP value + * @property {number} STOP=2 STOP value + * @property {number} STATUS=3 STATUS value + * @property {number} CAN_HOP=4 CAN_HOP value + */ + CircuitRelay.Type = (function() { + var valuesById = {}, values = Object.create(valuesById); + values[valuesById[1] = "HOP"] = 1; + values[valuesById[2] = "STOP"] = 2; + values[valuesById[3] = "STATUS"] = 3; + values[valuesById[4] = "CAN_HOP"] = 4; + return values; + })(); + + CircuitRelay.Peer = (function() { + + /** + * Properties of a Peer. + * @memberof CircuitRelay + * @interface IPeer + * @property {Uint8Array} id Peer id + * @property {Array.|null} [addrs] Peer addrs + */ + + /** + * Constructs a new Peer. + * @memberof CircuitRelay + * @classdesc Represents a Peer. + * @implements IPeer + * @constructor + * @param {CircuitRelay.IPeer=} [p] Properties to set + */ + function Peer(p) { + this.addrs = []; + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + /** + * Peer id. + * @member {Uint8Array} id + * @memberof CircuitRelay.Peer + * @instance + */ + Peer.prototype.id = $util.newBuffer([]); + + /** + * Peer addrs. + * @member {Array.} addrs + * @memberof CircuitRelay.Peer + * @instance + */ + Peer.prototype.addrs = $util.emptyArray; + + /** + * Encodes the specified Peer message. Does not implicitly {@link CircuitRelay.Peer.verify|verify} messages. + * @function encode + * @memberof CircuitRelay.Peer + * @static + * @param {CircuitRelay.IPeer} m Peer message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Peer.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + w.uint32(10).bytes(m.id); + if (m.addrs != null && m.addrs.length) { + for (var i = 0; i < m.addrs.length; ++i) + w.uint32(18).bytes(m.addrs[i]); + } + return w; + }; + + /** + * Decodes a Peer message from the specified reader or buffer. + * @function decode + * @memberof CircuitRelay.Peer + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {CircuitRelay.Peer} Peer + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Peer.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.CircuitRelay.Peer(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + m.id = r.bytes(); + break; + case 2: + if (!(m.addrs && m.addrs.length)) + m.addrs = []; + m.addrs.push(r.bytes()); + break; + default: + r.skipType(t & 7); + break; + } + } + if (!m.hasOwnProperty("id")) + throw $util.ProtocolError("missing required 'id'", { instance: m }); + return m; + }; + + /** + * Creates a Peer message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof CircuitRelay.Peer + * @static + * @param {Object.} d Plain object + * @returns {CircuitRelay.Peer} Peer + */ + Peer.fromObject = function fromObject(d) { + if (d instanceof $root.CircuitRelay.Peer) + return d; + var m = new $root.CircuitRelay.Peer(); + if (d.id != null) { + if (typeof d.id === "string") + $util.base64.decode(d.id, m.id = $util.newBuffer($util.base64.length(d.id)), 0); + else if (d.id.length) + m.id = d.id; + } + if (d.addrs) { + if (!Array.isArray(d.addrs)) + throw TypeError(".CircuitRelay.Peer.addrs: array expected"); + m.addrs = []; + for (var i = 0; i < d.addrs.length; ++i) { + if (typeof d.addrs[i] === "string") + $util.base64.decode(d.addrs[i], m.addrs[i] = $util.newBuffer($util.base64.length(d.addrs[i])), 0); + else if (d.addrs[i].length) + m.addrs[i] = d.addrs[i]; + } + } + return m; + }; + + /** + * Creates a plain object from a Peer message. Also converts values to other types if specified. + * @function toObject + * @memberof CircuitRelay.Peer + * @static + * @param {CircuitRelay.Peer} m Peer + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + Peer.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.arrays || o.defaults) { + d.addrs = []; + } + if (o.defaults) { + if (o.bytes === String) + d.id = ""; + else { + d.id = []; + if (o.bytes !== Array) + d.id = $util.newBuffer(d.id); + } + } + if (m.id != null && m.hasOwnProperty("id")) { + d.id = o.bytes === String ? $util.base64.encode(m.id, 0, m.id.length) : o.bytes === Array ? Array.prototype.slice.call(m.id) : m.id; + } + if (m.addrs && m.addrs.length) { + d.addrs = []; + for (var j = 0; j < m.addrs.length; ++j) { + d.addrs[j] = o.bytes === String ? $util.base64.encode(m.addrs[j], 0, m.addrs[j].length) : o.bytes === Array ? Array.prototype.slice.call(m.addrs[j]) : m.addrs[j]; + } + } + return d; + }; + + /** + * Converts this Peer to JSON. + * @function toJSON + * @memberof CircuitRelay.Peer + * @instance + * @returns {Object.} JSON object + */ + Peer.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return Peer; + })(); + + return CircuitRelay; +})(); + +module.exports = $root; diff --git a/src/circuit/protocol/index.proto b/src/circuit/protocol/index.proto new file mode 100644 index 0000000000..259ad2835a --- /dev/null +++ b/src/circuit/protocol/index.proto @@ -0,0 +1,42 @@ +syntax = "proto2"; + +message CircuitRelay { + + enum Status { + SUCCESS = 100; + HOP_SRC_ADDR_TOO_LONG = 220; + HOP_DST_ADDR_TOO_LONG = 221; + HOP_SRC_MULTIADDR_INVALID = 250; + HOP_DST_MULTIADDR_INVALID = 251; + HOP_NO_CONN_TO_DST = 260; + HOP_CANT_DIAL_DST = 261; + HOP_CANT_OPEN_DST_STREAM = 262; + HOP_CANT_SPEAK_RELAY = 270; + HOP_CANT_RELAY_TO_SELF = 280; + STOP_SRC_ADDR_TOO_LONG = 320; + STOP_DST_ADDR_TOO_LONG = 321; + STOP_SRC_MULTIADDR_INVALID = 350; + STOP_DST_MULTIADDR_INVALID = 351; + STOP_RELAY_REFUSED = 390; + MALFORMED_MESSAGE = 400; + } + + enum Type { // RPC identifier, either HOP, STOP or STATUS + HOP = 1; + STOP = 2; + STATUS = 3; + CAN_HOP = 4; + } + + message Peer { + required bytes id = 1; // peer id + repeated bytes addrs = 2; // peer's known addresses + } + + optional Type type = 1; // Type of the message + + optional Peer srcPeer = 2; // srcPeer and dstPeer are used when Type is HOP or STATUS + optional Peer dstPeer = 3; + + optional Status code = 4; // Status code, used when Type is STATUS +} diff --git a/src/circuit/transport.js b/src/circuit/transport.js index fc2ddad4f0..b220620398 100644 --- a/src/circuit/transport.js +++ b/src/circuit/transport.js @@ -5,10 +5,12 @@ const log = Object.assign(debug('libp2p:circuit'), { error: debug('libp2p:circuit:err') }) +const errCode = require('err-code') const mafmt = require('mafmt') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const PeerId = require('peer-id') const { CircuitRelay: CircuitPB } = require('./protocol') +const { codes } = require('../errors') const toConnection = require('libp2p-utils/src/stream-to-ma-conn') @@ -21,10 +23,8 @@ const StreamHandler = require('./circuit/stream-handler') const transportSymbol = Symbol.for('@libp2p/js-libp2p-circuit/circuit') /** - * @typedef {import('multiaddr')} Multiaddr * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream - * @typedef {import('../types').CircuitRequest} CircuitRequest */ class Circuit { @@ -54,7 +54,7 @@ class Circuit { * @param {MuxedStream} props.stream */ async _onProtocol ({ connection, stream }) { - /** @type {import('./circuit/stream-handler')} */ + /** @type {import('./circuit/stream-handler')} */ const streamHandler = new StreamHandler({ stream }) const request = await streamHandler.read() @@ -96,8 +96,10 @@ class Circuit { } if (virtualConnection) { - const remoteAddr = multiaddr(request.dstPeer.addrs[0]) - const localAddr = multiaddr(request.srcPeer.addrs[0]) + // @ts-ignore dst peer will not be undefined + const remoteAddr = new Multiaddr(request.dstPeer.addrs[0]) + // @ts-ignore src peer will not be undefined + const localAddr = new Multiaddr(request.srcPeer.addrs[0]) const maConn = toConnection({ stream: virtualConnection, remoteAddr, @@ -123,10 +125,19 @@ class Circuit { 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()) + const relayAddr = new Multiaddr(addrs[0]) + const destinationAddr = new Multiaddr(addrs[addrs.length - 1]) + const relayId = relayAddr.getPeerId() + const destinationId = destinationAddr.getPeerId() + + if (!relayId || !destinationId) { + const errMsg = 'Circuit relay dial failed as addresses did not have peer id' + log.error(errMsg) + throw errCode(new Error(errMsg), codes.ERR_RELAYED_DIAL) + } + + const relayPeer = PeerId.createFromCID(relayId) + const destinationPeer = PeerId.createFromCID(destinationId) let disconnectOnFailure = false let relayConnection = this._connectionManager.get(relayPeer) @@ -146,7 +157,7 @@ class Circuit { }, dstPeer: { id: destinationPeer.toBytes(), - addrs: [multiaddr(destinationAddr).bytes] + addrs: [new Multiaddr(destinationAddr).bytes] } } }) diff --git a/src/config.js b/src/config.js index 6eefa42556..bbcc6dab47 100644 --- a/src/config.js +++ b/src/config.js @@ -1,6 +1,7 @@ 'use strict' const mergeOptions = require('merge-options') +// @ts-ignore no types in multiaddr path const { dnsaddrResolver } = require('multiaddr/src/resolvers') const Constants = require('./constants') @@ -10,11 +11,18 @@ const RelayConstants = require('./circuit/constants') const { publicAddressesFirst } = require('libp2p-utils/src/address-sort') const { FaultTolerance } = require('./transport-manager') +/** + * @typedef {import('multiaddr').Multiaddr} Multiaddr + * @typedef {import('.').Libp2pOptions} Libp2pOptions + * @typedef {import('.').constructorOptions} constructorOptions + */ + const DefaultConfig = { addresses: { listen: [], announce: [], - noAnnounce: [] + noAnnounce: [], + announceFilter: (/** @type {Multiaddr[]} */ multiaddrs) => multiaddrs }, connectionManager: { minConnections: 25 @@ -95,10 +103,15 @@ const DefaultConfig = { } } +/** + * @param {Libp2pOptions} opts + * @returns {DefaultConfig & Libp2pOptions & constructorOptions} + */ module.exports.validate = (opts) => { - opts = mergeOptions(DefaultConfig, opts) + /** @type {DefaultConfig & Libp2pOptions & constructorOptions} */ + const resultingOptions = mergeOptions(DefaultConfig, opts) - if (opts.modules.transport.length < 1) throw new Error("'options.modules.transport' must contain at least 1 transport") + if (resultingOptions.modules.transport.length < 1) throw new Error("'options.modules.transport' must contain at least 1 transport") - return opts + return resultingOptions } diff --git a/src/connection-manager/index.js b/src/connection-manager/index.js index 1bdef27af9..985d26f145 100644 --- a/src/connection-manager/index.js +++ b/src/connection-manager/index.js @@ -8,10 +8,9 @@ const log = Object.assign(debug('libp2p:connection-manager'), { const errcode = require('err-code') const mergeOptions = require('merge-options') const LatencyMonitor = require('./latency-monitor') +// @ts-ignore retimer does not have types const retimer = require('retimer') -/** @typedef {import('../types').EventEmitterFactory} Events */ -/** @type Events */ const EventEmitter = require('events') const PeerId = require('peer-id') @@ -188,8 +187,10 @@ class ConnectionManager extends EventEmitter { _checkMetrics () { if (this._libp2p.metrics) { const movingAverages = this._libp2p.metrics.global.movingAverages + // @ts-ignore moving averages object types const received = movingAverages.dataReceived[this._options.movingAverageInterval].movingAverage() this._checkMaxLimit('maxReceivedData', received) + // @ts-ignore moving averages object types const sent = movingAverages.dataSent[this._options.movingAverageInterval].movingAverage() this._checkMaxLimit('maxSentData', sent) const total = received + sent @@ -362,7 +363,7 @@ class ConnectionManager extends EventEmitter { */ _maybeDisconnectOne () { if (this._options.minConnections < this.connections.size) { - const peerValues = Array.from(this._peerValues).sort(byPeerValue) + const peerValues = Array.from(new Map([...this._peerValues.entries()].sort((a, b) => a[1] - b[1]))) log('%s: sorted peer values: %j', this._peerId, peerValues) const disconnectPeer = peerValues[0] if (disconnectPeer) { @@ -381,7 +382,3 @@ class ConnectionManager extends EventEmitter { } module.exports = ConnectionManager - -function byPeerValue (peerValueEntryA, peerValueEntryB) { - return peerValueEntryA[1] - peerValueEntryB[1] -} diff --git a/src/connection-manager/latency-monitor.js b/src/connection-manager/latency-monitor.js index d5b44e9ab4..5253c2ae71 100644 --- a/src/connection-manager/latency-monitor.js +++ b/src/connection-manager/latency-monitor.js @@ -5,8 +5,6 @@ * This code is based on `latency-monitor` (https://github.com/mlucool/latency-monitor) by `mlucool` (https://github.com/mlucool), available under Apache License 2.0 (https://github.com/mlucool/latency-monitor/blob/master/LICENSE) */ -/** @typedef {import('../types').EventEmitterFactory} Events */ -/** @type Events */ const EventEmitter = require('events') const VisibilityChangeEmitter = require('./visibility-change-emitter') const debug = require('debug')('latency-monitor:LatencyMonitor') @@ -59,7 +57,8 @@ class LatencyMonitor extends EventEmitter { that._latecyCheckMultiply = 2 * (that.latencyRandomPercentage / 100.0) * that.latencyCheckIntervalMs that._latecyCheckSubtract = that._latecyCheckMultiply / 2 - that.dataEmitIntervalMs = (dataEmitIntervalMs === null || dataEmitIntervalMs === 0) ? undefined + that.dataEmitIntervalMs = (dataEmitIntervalMs === null || dataEmitIntervalMs === 0) + ? undefined : dataEmitIntervalMs || 5 * 1000 // 5s debug('latencyCheckIntervalMs: %s dataEmitIntervalMs: %s', that.latencyCheckIntervalMs, that.dataEmitIntervalMs) @@ -174,7 +173,8 @@ class LatencyMonitor extends EventEmitter { events: this._latencyData.events, minMs: this._latencyData.minMs, maxMs: this._latencyData.maxMs, - avgMs: this._latencyData.events ? this._latencyData.totalMs / this._latencyData.events + avgMs: this._latencyData.events + ? this._latencyData.totalMs / this._latencyData.events : Number.POSITIVE_INFINITY, lengthMs: this.getDeltaMS(this._latencyData.startTime) } diff --git a/src/connection-manager/visibility-change-emitter.js b/src/connection-manager/visibility-change-emitter.js index bf7da71b82..9efb6ffb33 100644 --- a/src/connection-manager/visibility-change-emitter.js +++ b/src/connection-manager/visibility-change-emitter.js @@ -6,8 +6,6 @@ */ 'use strict' -/** @typedef {import('../types').EventEmitterFactory} Events */ -/** @type Events */ const EventEmitter = require('events') const debug = require('debug')('latency-monitor:VisibilityChangeEmitter') diff --git a/src/content-routing/index.js b/src/content-routing/index.js index f409daa3c4..44f1d9c580 100644 --- a/src/content-routing/index.js +++ b/src/content-routing/index.js @@ -14,8 +14,9 @@ const { pipe } = require('it-pipe') /** * @typedef {import('peer-id')} PeerId - * @typedef {import('multiaddr')} Multiaddr + * @typedef {import('multiaddr').Multiaddr} Multiaddr * @typedef {import('cids')} CID + * @typedef {import('libp2p-interfaces/src/content-routing/types')} ContentRoutingModule */ /** @@ -31,6 +32,7 @@ class ContentRouting { */ constructor (libp2p) { this.libp2p = libp2p + /** @type {ContentRoutingModule[]} */ this.routers = libp2p._modules.contentRouting || [] this.dht = libp2p._dht diff --git a/src/content-routing/utils.js b/src/content-routing/utils.js index 55ba3a7516..c43ec9a98e 100644 --- a/src/content-routing/utils.js +++ b/src/content-routing/utils.js @@ -7,7 +7,7 @@ const take = require('it-take') /** * @typedef {import('peer-id')} PeerId - * @typedef {import('multiaddr')} Multiaddr + * @typedef {import('multiaddr').Multiaddr} Multiaddr */ /** diff --git a/src/dialer/dial-request.js b/src/dialer/dial-request.js index 8027bacc76..3f6fc18ae1 100644 --- a/src/dialer/dial-request.js +++ b/src/dialer/dial-request.js @@ -3,13 +3,14 @@ const errCode = require('err-code') const AbortController = require('abort-controller').default const { anySignal } = require('any-signal') +// @ts-ignore p-fifo does not export types const FIFO = require('p-fifo') const pAny = require('p-any') /** * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection * @typedef {import('./')} Dialer - * @typedef {import('multiaddr')} Multiaddr + * @typedef {import('multiaddr').Multiaddr} Multiaddr */ /** diff --git a/src/dialer/index.js b/src/dialer/index.js index 17b96e82b9..1225e1cc28 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -5,7 +5,8 @@ const log = Object.assign(debug('libp2p:dialer'), { error: debug('libp2p:dialer:err') }) const errCode = require('err-code') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') +// @ts-ignore timeout-abourt-controles does not export types const TimeoutController = require('timeout-abort-controller') const { anySignal } = require('any-signal') @@ -22,7 +23,6 @@ const { /** * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection - * @typedef {import('multiaddr')} Multiaddr * @typedef {import('peer-id')} PeerId * @typedef {import('../peer-store')} PeerStore * @typedef {import('../peer-store/address-book').Address} Address @@ -38,9 +38,9 @@ const { * * @typedef {Object} DialerOptions * @property {(addresses: Address[]) => Address[]} [options.addressSorter = publicAddressesFirst] - Sort the known addresses of a peer before trying to dial. - * @property {number} [concurrency = MAX_PARALLEL_DIALS] - Number of max concurrent dials. - * @property {number} [perPeerLimit = MAX_PER_PEER_DIALS] - Number of max concurrent dials per peer. - * @property {number} [timeout = DIAL_TIMEOUT] - How long a dial attempt is allowed to take. + * @property {number} [maxParallelDials = MAX_PARALLEL_DIALS] - Number of max concurrent dials. + * @property {number} [maxDialsPerPeer = MAX_PER_PEER_DIALS] - Number of max concurrent dials per peer. + * @property {number} [dialTimeout = DIAL_TIMEOUT] - How long a dial attempt is allowed to take. * @property {Record} [resolvers = {}] - multiaddr resolvers to use when dialing * * @typedef DialTarget @@ -50,7 +50,7 @@ const { * @typedef PendingDial * @property {DialRequest} dialRequest * @property {TimeoutController} controller - * @property {Promise} promise + * @property {Promise} promise * @property {function():void} destroy */ @@ -63,22 +63,22 @@ class Dialer { transportManager, peerStore, addressSorter = publicAddressesFirst, - concurrency = MAX_PARALLEL_DIALS, - timeout = DIAL_TIMEOUT, - perPeerLimit = MAX_PER_PEER_DIALS, + maxParallelDials = MAX_PARALLEL_DIALS, + dialTimeout = DIAL_TIMEOUT, + maxDialsPerPeer = MAX_PER_PEER_DIALS, resolvers = {} }) { this.transportManager = transportManager this.peerStore = peerStore this.addressSorter = addressSorter - this.concurrency = concurrency - this.timeout = timeout - this.perPeerLimit = perPeerLimit - this.tokens = [...new Array(concurrency)].map((_, index) => index) + this.maxParallelDials = maxParallelDials + this.timeout = dialTimeout + this.maxDialsPerPeer = maxDialsPerPeer + this.tokens = [...new Array(maxParallelDials)].map((_, index) => index) this._pendingDials = new Map() for (const [key, value] of Object.entries(resolvers)) { - multiaddr.resolvers.set(key, value) + Multiaddr.resolvers.set(key, value) } } @@ -150,11 +150,12 @@ class Dialer { // 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)) { + if (Multiaddr.isMultiaddr(peer)) { knownAddrs = knownAddrs.filter((addr) => !peer.equals(addr)) knownAddrs.unshift(peer) } + /** @type {Multiaddr[]} */ const addrs = [] for (const a of knownAddrs) { const resolvedAddrs = await this._resolve(a) @@ -177,6 +178,10 @@ class Dialer { * @returns {PendingDial} */ _createPendingDial (dialTarget, options = {}) { + /** + * @param {Multiaddr} addr + * @param {{ signal: { aborted: any; }; }} options + */ const dialAction = (addr, options) => { if (options.signal.aborted) throw errCode(new Error('already aborted'), codes.ERR_ALREADY_ABORTED) return this.transportManager.dial(addr, options) @@ -207,13 +212,19 @@ class Dialer { return pendingDial } + /** + * @param {number} num + */ getTokens (num) { - const total = Math.min(num, this.perPeerLimit, this.tokens.length) + const total = Math.min(num, this.maxDialsPerPeer, this.tokens.length) const tokens = this.tokens.splice(0, total) log('%d tokens request, returning %d, %d remaining', num, total, this.tokens.length) return tokens } + /** + * @param {number} token + */ releaseToken (token) { // Guard against duplicate releases if (this.tokens.indexOf(token) > -1) return @@ -259,7 +270,7 @@ class Dialer { */ async _resolveRecord (ma) { try { - ma = multiaddr(ma.toString()) // Use current multiaddr module + ma = new Multiaddr(ma.toString()) // Use current multiaddr module const multiaddrs = await ma.resolve() return multiaddrs } catch (_) { diff --git a/src/errors.js b/src/errors.js index cbff9aa82e..14752b2706 100644 --- a/src/errors.js +++ b/src/errors.js @@ -16,6 +16,7 @@ exports.codes = { ERR_NODE_NOT_STARTED: 'ERR_NODE_NOT_STARTED', ERR_ALREADY_ABORTED: 'ERR_ALREADY_ABORTED', ERR_NO_VALID_ADDRESSES: 'ERR_NO_VALID_ADDRESSES', + ERR_RELAYED_DIAL: 'ERR_RELAYED_DIAL', ERR_DIALED_SELF: 'ERR_DIALED_SELF', ERR_DISCOVERED_SELF: 'ERR_DISCOVERED_SELF', ERR_DUPLICATE_TRANSPORT: 'ERR_DUPLICATE_TRANSPORT', diff --git a/src/get-peer.js b/src/get-peer.js index 807c333384..a0de9ef8b6 100644 --- a/src/get-peer.js +++ b/src/get-peer.js @@ -1,15 +1,11 @@ 'use strict' const PeerId = require('peer-id') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const errCode = require('err-code') const { codes } = require('./errors') -/** - * @typedef {import('multiaddr')} Multiaddr - */ - /** * Converts the given `peer` to a `Peer` object. * If a multiaddr is received, the addressBook is updated. @@ -19,14 +15,23 @@ const { codes } = require('./errors') */ function getPeer (peer) { if (typeof peer === 'string') { - peer = multiaddr(peer) + peer = new Multiaddr(peer) } let addr - if (multiaddr.isMultiaddr(peer)) { + if (Multiaddr.isMultiaddr(peer)) { addr = peer + const idStr = peer.getPeerId() + + if (!idStr) { + throw errCode( + new Error(`${peer} does not have a valid peer type`), + codes.ERR_INVALID_MULTIADDR + ) + } + try { - peer = PeerId.createFromB58String(peer.getPeerId()) + peer = PeerId.createFromB58String(idStr) } catch (err) { throw errCode( new Error(`${peer} is not a valid peer type`), diff --git a/src/identify/index.js b/src/identify/index.js index 127891e845..498ea0e1f3 100644 --- a/src/identify/index.js +++ b/src/identify/index.js @@ -5,14 +5,14 @@ const log = Object.assign(debug('libp2p:identify'), { error: debug('libp2p:identify:err') }) const errCode = require('err-code') -const pb = require('it-protocol-buffers') const lp = require('it-length-prefixed') const { pipe } = require('it-pipe') const { collect, take, consume } = require('streaming-iterables') const uint8ArrayFromString = require('uint8arrays/from-string') const PeerId = require('peer-id') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') +// @ts-ignore it-buffer does not have types const { toBuffer } = require('it-buffer') const Message = require('./message') @@ -23,7 +23,6 @@ const PeerRecord = require('../record/peer-record') const { MULTICODEC_IDENTIFY, MULTICODEC_IDENTIFY_PUSH, - AGENT_VERSION, PROTOCOL_VERSION } = require('./consts') @@ -34,6 +33,11 @@ const { codes } = require('../errors') * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream */ +/** + * @typedef {Object} HostProperties + * @property {string} agentVersion + */ + class IdentifyService { /** * @class @@ -51,7 +55,6 @@ class IdentifyService { // Store self host metadata this._host = { - agentVersion: AGENT_VERSION, protocolVersion: PROTOCOL_VERSION, ...libp2p._options.host } @@ -94,12 +97,12 @@ class IdentifyService { const { stream } = await connection.newStream(MULTICODEC_IDENTIFY_PUSH) await pipe( - [{ + [Message.Identify.encode({ listenAddrs, signedPeerRecord, protocols - }], - pb.encode(Message), + }).finish()], + lp.encode(), stream, consume ) @@ -160,12 +163,12 @@ class IdentifyService { let message try { - message = Message.decode(data) + message = Message.Identify.decode(data) } catch (err) { throw errCode(err, codes.ERR_INVALID_MESSAGE) } - let { + const { publicKey, listenAddrs, protocols, @@ -180,7 +183,7 @@ class IdentifyService { } // Get the observedAddr if there is one - observedAddr = IdentifyService.getCleanMultiaddr(observedAddr) + const cleanObservedAddr = IdentifyService.getCleanMultiaddr(observedAddr) try { const envelope = await Envelope.openAndCertify(signedPeerRecord, PeerRecord.DOMAIN) @@ -194,7 +197,7 @@ class IdentifyService { // LEGACY: Update peers data in PeerStore try { - this.peerStore.addressBook.set(id, listenAddrs.map((addr) => multiaddr(addr))) + this.peerStore.addressBook.set(id, listenAddrs.map((addr) => new Multiaddr(addr))) } catch (err) { log.error('received invalid addrs', err) } @@ -203,7 +206,7 @@ class IdentifyService { this.peerStore.metadataBook.set(id, 'AgentVersion', uint8ArrayFromString(message.agentVersion)) // TODO: Add and score our observed addr - log('received observed address of %s', observedAddr) + log('received observed address of %s', cleanObservedAddr) // this.addressManager.addObservedAddr(observedAddr) } @@ -246,7 +249,7 @@ class IdentifyService { const signedPeerRecord = await this.peerStore.addressBook.getRawEnvelope(this.peerId) const protocols = this.peerStore.protoBook.get(this.peerId) || [] - const message = Message.encode({ + const message = Message.Identify.encode({ protocolVersion: this._host.protocolVersion, agentVersion: this._host.agentVersion, publicKey, @@ -254,7 +257,7 @@ class IdentifyService { signedPeerRecord, observedAddr: connection.remoteAddr.bytes, protocols - }) + }).finish() try { await pipe( @@ -288,7 +291,7 @@ class IdentifyService { toBuffer, collect ) - message = Message.decode(data) + message = Message.Identify.decode(data) } catch (err) { return log.error('received invalid message', err) } @@ -307,7 +310,8 @@ class IdentifyService { // LEGACY: Update peers data in PeerStore try { - this.peerStore.addressBook.set(id, message.listenAddrs.map((addr) => multiaddr(addr))) + this.peerStore.addressBook.set(id, + message.listenAddrs.map((addr) => new Multiaddr(addr))) } catch (err) { log.error('received invalid addrs', err) } @@ -320,12 +324,12 @@ class IdentifyService { * Takes the `addr` and converts it to a Multiaddr if possible * * @param {Uint8Array | string} addr - * @returns {multiaddr|null} + * @returns {Multiaddr|null} */ static getCleanMultiaddr (addr) { if (addr && addr.length > 0) { try { - return multiaddr(addr) + return new Multiaddr(addr) } catch (_) { return null } diff --git a/src/identify/message.d.ts b/src/identify/message.d.ts new file mode 100644 index 0000000000..ba49c586aa --- /dev/null +++ b/src/identify/message.d.ts @@ -0,0 +1,95 @@ +import * as $protobuf from "protobufjs"; +/** Properties of an Identify. */ +export interface IIdentify { + + /** Identify protocolVersion */ + protocolVersion?: (string|null); + + /** Identify agentVersion */ + agentVersion?: (string|null); + + /** Identify publicKey */ + publicKey?: (Uint8Array|null); + + /** Identify listenAddrs */ + listenAddrs?: (Uint8Array[]|null); + + /** Identify observedAddr */ + observedAddr?: (Uint8Array|null); + + /** Identify protocols */ + protocols?: (string[]|null); + + /** Identify signedPeerRecord */ + signedPeerRecord?: (Uint8Array|null); +} + +/** Represents an Identify. */ +export class Identify implements IIdentify { + + /** + * Constructs a new Identify. + * @param [p] Properties to set + */ + constructor(p?: IIdentify); + + /** Identify protocolVersion. */ + public protocolVersion: string; + + /** Identify agentVersion. */ + public agentVersion: string; + + /** Identify publicKey. */ + public publicKey: Uint8Array; + + /** Identify listenAddrs. */ + public listenAddrs: Uint8Array[]; + + /** Identify observedAddr. */ + public observedAddr: Uint8Array; + + /** Identify protocols. */ + public protocols: string[]; + + /** Identify signedPeerRecord. */ + public signedPeerRecord: Uint8Array; + + /** + * Encodes the specified Identify message. Does not implicitly {@link Identify.verify|verify} messages. + * @param m Identify message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: IIdentify, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes an Identify message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns Identify + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Identify; + + /** + * Creates an Identify message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns Identify + */ + public static fromObject(d: { [k: string]: any }): Identify; + + /** + * Creates a plain object from an Identify message. Also converts values to other types if specified. + * @param m Identify + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: Identify, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Identify to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; +} diff --git a/src/identify/message.js b/src/identify/message.js index 25b003f5c0..9f4d6e4ec2 100644 --- a/src/identify/message.js +++ b/src/identify/message.js @@ -1,35 +1,328 @@ -'use strict' +/*eslint-disable*/ +"use strict"; -const protons = require('protons') -const schema = ` -message Identify { - // protocolVersion determines compatibility between peers - optional string protocolVersion = 5; // e.g. ipfs/1.0.0 +var $protobuf = require("protobufjs/minimal"); - // agentVersion is like a UserAgent string in browsers, or client version in bittorrent - // includes the client name and client. - optional string agentVersion = 6; // e.g. go-ipfs/0.1.0 +// Common aliases +var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; - // publicKey is this node's public key (which also gives its node.ID) - // - may not need to be sent, as secure channel implies it has been sent. - // - then again, if we change / disable secure channel, may still want it. - optional bytes publicKey = 1; +// Exported root namespace +var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); - // listenAddrs are the multiaddrs the sender node listens for open connections on - repeated bytes listenAddrs = 2; +$root.Identify = (function() { - // oservedAddr is the multiaddr of the remote endpoint that the sender node perceives - // this is useful information to convey to the other side, as it helps the remote endpoint - // determine whether its connection to the local peer goes through NAT. - optional bytes observedAddr = 4; + /** + * Properties of an Identify. + * @exports IIdentify + * @interface IIdentify + * @property {string|null} [protocolVersion] Identify protocolVersion + * @property {string|null} [agentVersion] Identify agentVersion + * @property {Uint8Array|null} [publicKey] Identify publicKey + * @property {Array.|null} [listenAddrs] Identify listenAddrs + * @property {Uint8Array|null} [observedAddr] Identify observedAddr + * @property {Array.|null} [protocols] Identify protocols + * @property {Uint8Array|null} [signedPeerRecord] Identify signedPeerRecord + */ - repeated string protocols = 3; + /** + * Constructs a new Identify. + * @exports Identify + * @classdesc Represents an Identify. + * @implements IIdentify + * @constructor + * @param {IIdentify=} [p] Properties to set + */ + function Identify(p) { + this.listenAddrs = []; + this.protocols = []; + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } - // signedPeerRecord contains a serialized SignedEnvelope containing a PeerRecord, - // signed by the sending node. It contains the same addresses as the listenAddrs field, but - // in a form that lets us share authenticated addrs with other peers. - optional bytes signedPeerRecord = 8; -} -` + /** + * Identify protocolVersion. + * @member {string} protocolVersion + * @memberof Identify + * @instance + */ + Identify.prototype.protocolVersion = ""; -module.exports = protons(schema).Identify + /** + * Identify agentVersion. + * @member {string} agentVersion + * @memberof Identify + * @instance + */ + Identify.prototype.agentVersion = ""; + + /** + * Identify publicKey. + * @member {Uint8Array} publicKey + * @memberof Identify + * @instance + */ + Identify.prototype.publicKey = $util.newBuffer([]); + + /** + * Identify listenAddrs. + * @member {Array.} listenAddrs + * @memberof Identify + * @instance + */ + Identify.prototype.listenAddrs = $util.emptyArray; + + /** + * Identify observedAddr. + * @member {Uint8Array} observedAddr + * @memberof Identify + * @instance + */ + Identify.prototype.observedAddr = $util.newBuffer([]); + + /** + * Identify protocols. + * @member {Array.} protocols + * @memberof Identify + * @instance + */ + Identify.prototype.protocols = $util.emptyArray; + + /** + * Identify signedPeerRecord. + * @member {Uint8Array} signedPeerRecord + * @memberof Identify + * @instance + */ + Identify.prototype.signedPeerRecord = $util.newBuffer([]); + + /** + * Encodes the specified Identify message. Does not implicitly {@link Identify.verify|verify} messages. + * @function encode + * @memberof Identify + * @static + * @param {IIdentify} m Identify message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Identify.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.publicKey != null && Object.hasOwnProperty.call(m, "publicKey")) + w.uint32(10).bytes(m.publicKey); + if (m.listenAddrs != null && m.listenAddrs.length) { + for (var i = 0; i < m.listenAddrs.length; ++i) + w.uint32(18).bytes(m.listenAddrs[i]); + } + if (m.protocols != null && m.protocols.length) { + for (var i = 0; i < m.protocols.length; ++i) + w.uint32(26).string(m.protocols[i]); + } + if (m.observedAddr != null && Object.hasOwnProperty.call(m, "observedAddr")) + w.uint32(34).bytes(m.observedAddr); + if (m.protocolVersion != null && Object.hasOwnProperty.call(m, "protocolVersion")) + w.uint32(42).string(m.protocolVersion); + if (m.agentVersion != null && Object.hasOwnProperty.call(m, "agentVersion")) + w.uint32(50).string(m.agentVersion); + if (m.signedPeerRecord != null && Object.hasOwnProperty.call(m, "signedPeerRecord")) + w.uint32(66).bytes(m.signedPeerRecord); + return w; + }; + + /** + * Decodes an Identify message from the specified reader or buffer. + * @function decode + * @memberof Identify + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {Identify} Identify + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Identify.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.Identify(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 5: + m.protocolVersion = r.string(); + break; + case 6: + m.agentVersion = r.string(); + break; + case 1: + m.publicKey = r.bytes(); + break; + case 2: + if (!(m.listenAddrs && m.listenAddrs.length)) + m.listenAddrs = []; + m.listenAddrs.push(r.bytes()); + break; + case 4: + m.observedAddr = r.bytes(); + break; + case 3: + if (!(m.protocols && m.protocols.length)) + m.protocols = []; + m.protocols.push(r.string()); + break; + case 8: + m.signedPeerRecord = r.bytes(); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates an Identify message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof Identify + * @static + * @param {Object.} d Plain object + * @returns {Identify} Identify + */ + Identify.fromObject = function fromObject(d) { + if (d instanceof $root.Identify) + return d; + var m = new $root.Identify(); + if (d.protocolVersion != null) { + m.protocolVersion = String(d.protocolVersion); + } + if (d.agentVersion != null) { + m.agentVersion = String(d.agentVersion); + } + if (d.publicKey != null) { + if (typeof d.publicKey === "string") + $util.base64.decode(d.publicKey, m.publicKey = $util.newBuffer($util.base64.length(d.publicKey)), 0); + else if (d.publicKey.length) + m.publicKey = d.publicKey; + } + if (d.listenAddrs) { + if (!Array.isArray(d.listenAddrs)) + throw TypeError(".Identify.listenAddrs: array expected"); + m.listenAddrs = []; + for (var i = 0; i < d.listenAddrs.length; ++i) { + if (typeof d.listenAddrs[i] === "string") + $util.base64.decode(d.listenAddrs[i], m.listenAddrs[i] = $util.newBuffer($util.base64.length(d.listenAddrs[i])), 0); + else if (d.listenAddrs[i].length) + m.listenAddrs[i] = d.listenAddrs[i]; + } + } + if (d.observedAddr != null) { + if (typeof d.observedAddr === "string") + $util.base64.decode(d.observedAddr, m.observedAddr = $util.newBuffer($util.base64.length(d.observedAddr)), 0); + else if (d.observedAddr.length) + m.observedAddr = d.observedAddr; + } + if (d.protocols) { + if (!Array.isArray(d.protocols)) + throw TypeError(".Identify.protocols: array expected"); + m.protocols = []; + for (var i = 0; i < d.protocols.length; ++i) { + m.protocols[i] = String(d.protocols[i]); + } + } + if (d.signedPeerRecord != null) { + if (typeof d.signedPeerRecord === "string") + $util.base64.decode(d.signedPeerRecord, m.signedPeerRecord = $util.newBuffer($util.base64.length(d.signedPeerRecord)), 0); + else if (d.signedPeerRecord.length) + m.signedPeerRecord = d.signedPeerRecord; + } + return m; + }; + + /** + * Creates a plain object from an Identify message. Also converts values to other types if specified. + * @function toObject + * @memberof Identify + * @static + * @param {Identify} m Identify + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + Identify.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.arrays || o.defaults) { + d.listenAddrs = []; + d.protocols = []; + } + if (o.defaults) { + if (o.bytes === String) + d.publicKey = ""; + else { + d.publicKey = []; + if (o.bytes !== Array) + d.publicKey = $util.newBuffer(d.publicKey); + } + if (o.bytes === String) + d.observedAddr = ""; + else { + d.observedAddr = []; + if (o.bytes !== Array) + d.observedAddr = $util.newBuffer(d.observedAddr); + } + d.protocolVersion = ""; + d.agentVersion = ""; + if (o.bytes === String) + d.signedPeerRecord = ""; + else { + d.signedPeerRecord = []; + if (o.bytes !== Array) + d.signedPeerRecord = $util.newBuffer(d.signedPeerRecord); + } + } + if (m.publicKey != null && m.hasOwnProperty("publicKey")) { + d.publicKey = o.bytes === String ? $util.base64.encode(m.publicKey, 0, m.publicKey.length) : o.bytes === Array ? Array.prototype.slice.call(m.publicKey) : m.publicKey; + } + if (m.listenAddrs && m.listenAddrs.length) { + d.listenAddrs = []; + for (var j = 0; j < m.listenAddrs.length; ++j) { + d.listenAddrs[j] = o.bytes === String ? $util.base64.encode(m.listenAddrs[j], 0, m.listenAddrs[j].length) : o.bytes === Array ? Array.prototype.slice.call(m.listenAddrs[j]) : m.listenAddrs[j]; + } + } + if (m.protocols && m.protocols.length) { + d.protocols = []; + for (var j = 0; j < m.protocols.length; ++j) { + d.protocols[j] = m.protocols[j]; + } + } + if (m.observedAddr != null && m.hasOwnProperty("observedAddr")) { + d.observedAddr = o.bytes === String ? $util.base64.encode(m.observedAddr, 0, m.observedAddr.length) : o.bytes === Array ? Array.prototype.slice.call(m.observedAddr) : m.observedAddr; + } + if (m.protocolVersion != null && m.hasOwnProperty("protocolVersion")) { + d.protocolVersion = m.protocolVersion; + } + if (m.agentVersion != null && m.hasOwnProperty("agentVersion")) { + d.agentVersion = m.agentVersion; + } + if (m.signedPeerRecord != null && m.hasOwnProperty("signedPeerRecord")) { + d.signedPeerRecord = o.bytes === String ? $util.base64.encode(m.signedPeerRecord, 0, m.signedPeerRecord.length) : o.bytes === Array ? Array.prototype.slice.call(m.signedPeerRecord) : m.signedPeerRecord; + } + return d; + }; + + /** + * Converts this Identify to JSON. + * @function toJSON + * @memberof Identify + * @instance + * @returns {Object.} JSON object + */ + Identify.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return Identify; +})(); + +module.exports = $root; diff --git a/src/identify/message.proto b/src/identify/message.proto new file mode 100644 index 0000000000..63f25561cc --- /dev/null +++ b/src/identify/message.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; + +message Identify { + // protocolVersion determines compatibility between peers + optional string protocolVersion = 5; // e.g. ipfs/1.0.0 + + // agentVersion is like a UserAgent string in browsers, or client version in bittorrent + // includes the client name and client. + optional string agentVersion = 6; // e.g. go-ipfs/0.1.0 + + // publicKey is this node's public key (which also gives its node.ID) + // - may not need to be sent, as secure channel implies it has been sent. + // - then again, if we change / disable secure channel, may still want it. + optional bytes publicKey = 1; + + // listenAddrs are the multiaddrs the sender node listens for open connections on + repeated bytes listenAddrs = 2; + + // oservedAddr is the multiaddr of the remote endpoint that the sender node perceives + // this is useful information to convey to the other side, as it helps the remote endpoint + // determine whether its connection to the local peer goes through NAT. + optional bytes observedAddr = 4; + + repeated string protocols = 3; + + // signedPeerRecord contains a serialized SignedEnvelope containing a PeerRecord, + // signed by the sending node. It contains the same addresses as the listenAddrs field, but + // in a form that lets us share authenticated addrs with other peers. + optional bytes signedPeerRecord = 8; +} diff --git a/src/index.js b/src/index.js index 034aaa5006..02fa4f4ed5 100644 --- a/src/index.js +++ b/src/index.js @@ -4,13 +4,11 @@ const debug = require('debug') const log = Object.assign(debug('libp2p'), { error: debug('libp2p:err') }) -/** @typedef {import('./types').EventEmitterFactory} Events */ -/** @type Events */ const EventEmitter = require('events') const errCode = require('err-code') const PeerId = require('peer-id') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const PeerRouting = require('./peer-routing') const ContentRouting = require('./content-routing') @@ -38,29 +36,60 @@ const NatManager = require('./nat-manager') const { updateSelfPeerRecord } = require('./record/utils') /** - * @typedef {import('multiaddr')} Multiaddr * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream - * @typedef {import('libp2p-interfaces/src/transport/types').TransportFactory} TransportFactory + * @typedef {import('libp2p-interfaces/src/transport/types').TransportFactory} TransportFactory * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxerFactory} MuxerFactory + * @typedef {import('libp2p-interfaces/src/content-routing/types')} ContentRoutingModule + * @typedef {import('libp2p-interfaces/src/peer-discovery/types')} PeerDiscoveryModule + * @typedef {import('libp2p-interfaces/src/peer-routing/types')} PeerRoutingModule * @typedef {import('libp2p-interfaces/src/crypto/types').Crypto} Crypto * @typedef {import('libp2p-interfaces/src/pubsub')} Pubsub + * @typedef {import('libp2p-interfaces/src/pubsub').PubsubOptions} PubsubOptions + * @typedef {import('interface-datastore').Datastore} Datastore + * @typedef {import('./pnet')} Protector */ /** + * @typedef {Object} HandlerProps + * @property {Connection} connection + * @property {MuxedStream} stream + * @property {string} protocol + * + * @typedef {Object} RandomWalkOptions + * @property {boolean} [enabled = false] + * @property {number} [queriesPerPeriod = 1] + * @property {number} [interval = 300e3] + * @property {number} [timeout = 10e3] + * + * @typedef {Object} DhtOptions + * @property {boolean} [enabled = false] + * @property {number} [kBucketSize = 20] + * @property {RandomWalkOptions} [randomWalk] + * + * @typedef {Object} KeychainOptions + * @property {Datastore} [datastore] + * * @typedef {Object} PeerStoreOptions * @property {boolean} persistence * - * @typedef {Object} RelayOptions + * @typedef {Object} PubsubLocalOptions + * @property {boolean} enabled + * + * @typedef {Object} MetricsOptions * @property {boolean} enabled - * @property {import('./circuit').RelayAdvertiseOptions} advertise - * @property {import('./circuit').HopOptions} hop - * @property {import('./circuit').AutoRelayOptions} autoRelay + * + * @typedef {Object} RelayOptions + * @property {boolean} [enabled = true] + * @property {import('./circuit').RelayAdvertiseOptions} [advertise] + * @property {import('./circuit').HopOptions} [hop] + * @property {import('./circuit').AutoRelayOptions} [autoRelay] * * @typedef {Object} Libp2pConfig - * @property {Object} [dht] dht module options - * @property {Object} [peerDiscovery] - * @property {Pubsub} [pubsub] pubsub module options + * @property {DhtOptions} [dht] dht module options + * @property {import('./nat-manager').NatManagerOptions} [nat] + * @property {Record} [peerDiscovery] + * @property {PubsubLocalOptions & PubsubOptions} [pubsub] pubsub module options * @property {RelayOptions} [relay] * @property {Record} [transport] transport options indexed by transport key * @@ -68,16 +97,25 @@ const { updateSelfPeerRecord } = require('./record/utils') * @property {TransportFactory[]} transport * @property {MuxerFactory[]} streamMuxer * @property {Crypto[]} connEncryption + * @property {PeerDiscoveryModule[]} [peerDiscovery] + * @property {PeerRoutingModule[]} [peerRouting] + * @property {ContentRoutingModule[]} [contentRouting] + * @property {Object} [dht] + * @property {Pubsub} [pubsub] + * @property {Protector} [connProtector] * * @typedef {Object} Libp2pOptions * @property {Libp2pModules} modules libp2p modules to use * @property {import('./address-manager').AddressManagerOptions} [addresses] * @property {import('./connection-manager').ConnectionManagerOptions} [connectionManager] + * @property {Datastore} [datastore] * @property {import('./dialer').DialerOptions} [dialer] - * @property {import('./metrics').MetricsOptions} [metrics] - * @property {Object} [keychain] - * @property {import('./transport-manager').TransportManagerOptions} [transportManager] + * @property {import('./identify/index').HostProperties} [host] libp2p host + * @property {KeychainOptions & import('./keychain/index').KeychainOptions} [keychain] + * @property {MetricsOptions & import('./metrics').MetricsOptions} [metrics] + * @property {import('./peer-routing').PeerRoutingOptions} [peerRouting] * @property {PeerStoreOptions & import('./peer-store/persistent').PersistentPeerStoreOptions} [peerStore] + * @property {import('./transport-manager').TransportManagerOptions} [transportManager] * @property {Libp2pConfig} [config] * * @typedef {Object} constructorOptions @@ -152,9 +190,6 @@ class Libp2p extends EventEmitter { this._discovery = new Map() // Discovery service instances/references // Create the Connection Manager - 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 @@ -175,7 +210,6 @@ class Libp2p extends EventEmitter { const keychainOpts = Keychain.generateOptions() this.keychain = new Keychain(this._options.keychain.datastore, { - passPhrase: this._options.keychain.pass, ...keychainOpts, ...this._options.keychain }) @@ -203,6 +237,7 @@ class Libp2p extends EventEmitter { peerId: this.peerId, addressManager: this.addressManager, transportManager: this.transportManager, + // @ts-ignore Nat typedef is not understood as Object ...this._options.config.nat }) @@ -227,11 +262,7 @@ class Libp2p extends EventEmitter { this.dialer = new Dialer({ transportManager: this.transportManager, peerStore: this.peerStore, - concurrency: this._options.dialer.maxParallelDials, - perPeerLimit: this._options.dialer.maxDialsPerPeer, - timeout: this._options.dialer.dialTimeout, - resolvers: this._options.dialer.resolvers, - addressSorter: this._options.dialer.addressSorter + ...this._options.dialer }) this._modules.transport.forEach((Transport) => { @@ -268,6 +299,7 @@ class Libp2p extends EventEmitter { // dht provided components (peerRouting, contentRouting, dht) if (this._modules.dht) { const DHT = this._modules.dht + // @ts-ignore Object is not constructable this._dht = new DHT({ libp2p: this, dialer: this.dialer, @@ -426,7 +458,7 @@ class Libp2p extends EventEmitter { * @returns {Promise} */ dial (peer, options) { - return this.dialProtocol(peer, [], options) + return this._dial(peer, options) } /** @@ -439,9 +471,26 @@ class Libp2p extends EventEmitter { * @param {string[]|string} protocols * @param {object} [options] * @param {AbortSignal} [options.signal] - * @returns {Promise} + * @returns {Promise} */ async dialProtocol (peer, protocols, options) { + const connection = await this._dial(peer, options) + + // If a protocol was provided, create a new stream + if (protocols && protocols.length) { + return connection.newStream(protocols) + } + + return connection + } + + /** + * @async + * @param {PeerId|Multiaddr|string} peer - The peer to dial + * @param {object} [options] + * @returns {Promise} + */ + async _dial (peer, options) { const { id, multiaddrs } = getPeer(peer) if (id.equals(this.peerId)) { @@ -456,11 +505,6 @@ class Libp2p extends EventEmitter { this.peerStore.addressBook.add(id, multiaddrs) } - // If a protocol was provided, create a new stream - if (protocols && protocols.length) { - return connection.newStream(protocols) - } - return connection } @@ -484,13 +528,13 @@ class Libp2p extends EventEmitter { addrs = addrs.concat(this.addressManager.getObservedAddrs().map(ma => ma.toString())) - const announceFilter = this._options.addresses.announceFilter || ((multiaddrs) => multiaddrs) + const announceFilter = this._options.addresses.announceFilter // dedupe multiaddrs const addrSet = new Set(addrs) // Create advertising list - return announceFilter(Array.from(addrSet).map(str => multiaddr(str))) + return announceFilter(Array.from(addrSet).map(str => new Multiaddr(str))) } /** @@ -536,7 +580,7 @@ class Libp2p extends EventEmitter { * Registers the `handler` for each protocol * * @param {string[]|string} protocols - * @param {({ connection: Connection, stream: MuxedStream, protocol: string }) => void} handler + * @param {(props: HandlerProps) => void} handler */ handle (protocols, handler) { protocols = Array.isArray(protocols) ? protocols : [protocols] @@ -669,6 +713,9 @@ class Libp2p extends EventEmitter { * @private */ async _setupPeerDiscovery () { + /** + * @param {PeerDiscoveryModule} DiscoveryService + */ const setupService = (DiscoveryService) => { let config = { enabled: true // on by default @@ -677,6 +724,7 @@ class Libp2p extends EventEmitter { if (DiscoveryService.tag && this._config.peerDiscovery && this._config.peerDiscovery[DiscoveryService.tag]) { + // @ts-ignore PeerDiscovery not understood as an Object for spread config = { ...config, ...this._config.peerDiscovery[DiscoveryService.tag] } } @@ -685,6 +733,7 @@ class Libp2p extends EventEmitter { let discoveryService if (typeof DiscoveryService === 'function') { + // @ts-ignore DiscoveryService has no constructor type inferred discoveryService = new DiscoveryService(Object.assign({}, config, { peerId: this.peerId, libp2p: this diff --git a/src/insecure/plaintext.js b/src/insecure/plaintext.js index 07efe9f758..2ea0458315 100644 --- a/src/insecure/plaintext.js +++ b/src/insecure/plaintext.js @@ -4,6 +4,7 @@ const debug = require('debug') const log = Object.assign(debug('libp2p:plaintext'), { error: debug('libp2p:plaintext:err') }) +// @ts-ignore it-handshake do not export types const handshake = require('it-handshake') const lp = require('it-length-prefixed') const PeerId = require('peer-id') @@ -16,8 +17,12 @@ const protocol = '/plaintext/2.0.0' * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection */ +/** + * @param {import('./proto').IExchange} exchange + */ function lpEncodeExchange (exchange) { - const pb = Exchange.encode(exchange) + const pb = Exchange.encode(exchange).finish() + // @ts-ignore TODO: Uint8Array not assignable to Buffer return lp.encode.single(pb) } @@ -68,12 +73,23 @@ async function encrypt (localId, conn, remoteId) { } } -module.exports = { - protocol, - secureInbound: (localId, conn, remoteId) => { - return encrypt(localId, conn, remoteId) - }, - secureOutbound: (localId, conn, remoteId) => { - return encrypt(localId, conn, remoteId) - } -} +module.exports = + { + protocol, + /** + * @param {PeerId} localId + * @param {Connection} conn + * @param {PeerId | undefined} remoteId + */ + secureInbound: (localId, conn, remoteId) => { + return encrypt(localId, conn, remoteId) + }, + /** + * @param {PeerId} localId + * @param {Connection} conn + * @param {PeerId | undefined} remoteId + */ + secureOutbound: (localId, conn, remoteId) => { + return encrypt(localId, conn, remoteId) + } + } diff --git a/src/insecure/proto.d.ts b/src/insecure/proto.d.ts new file mode 100644 index 0000000000..a4fbac0610 --- /dev/null +++ b/src/insecure/proto.d.ts @@ -0,0 +1,128 @@ +import * as $protobuf from "protobufjs"; +/** Properties of an Exchange. */ +export interface IExchange { + + /** Exchange id */ + id?: (Uint8Array|null); + + /** Exchange pubkey */ + pubkey?: (IPublicKey|null); +} + +/** Represents an Exchange. */ +export class Exchange implements IExchange { + + /** + * Constructs a new Exchange. + * @param [p] Properties to set + */ + constructor(p?: IExchange); + + /** Exchange id. */ + public id: Uint8Array; + + /** Exchange pubkey. */ + public pubkey?: (IPublicKey|null); + + /** + * Encodes the specified Exchange message. Does not implicitly {@link Exchange.verify|verify} messages. + * @param m Exchange message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: IExchange, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes an Exchange message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns Exchange + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Exchange; + + /** + * Creates an Exchange message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns Exchange + */ + public static fromObject(d: { [k: string]: any }): Exchange; + + /** + * Creates a plain object from an Exchange message. Also converts values to other types if specified. + * @param m Exchange + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: Exchange, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Exchange to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; +} + +/** KeyType enum. */ +export enum KeyType { + RSA = 0, + Ed25519 = 1, + Secp256k1 = 2, + ECDSA = 3 +} + +/** Represents a PublicKey. */ +export class PublicKey implements IPublicKey { + + /** + * Constructs a new PublicKey. + * @param [p] Properties to set + */ + constructor(p?: IPublicKey); + + /** PublicKey Type. */ + public Type: KeyType; + + /** PublicKey Data. */ + public Data: Uint8Array; + + /** + * Encodes the specified PublicKey message. Does not implicitly {@link PublicKey.verify|verify} messages. + * @param m PublicKey message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: IPublicKey, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a PublicKey message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns PublicKey + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): PublicKey; + + /** + * Creates a PublicKey message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns PublicKey + */ + public static fromObject(d: { [k: string]: any }): PublicKey; + + /** + * Creates a plain object from a PublicKey message. Also converts values to other types if specified. + * @param m PublicKey + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: PublicKey, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this PublicKey to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; +} diff --git a/src/insecure/proto.js b/src/insecure/proto.js index 2c7d7e89a6..7c0161f60d 100644 --- a/src/insecure/proto.js +++ b/src/insecure/proto.js @@ -1,22 +1,371 @@ -'use strict' - -const protobuf = require('protons') - -module.exports = protobuf(` -message Exchange { - optional bytes id = 1; - optional PublicKey pubkey = 2; -} - -enum KeyType { - RSA = 0; - Ed25519 = 1; - Secp256k1 = 2; - ECDSA = 3; -} - -message PublicKey { - required KeyType Type = 1; - required bytes Data = 2; -} -`) +/*eslint-disable*/ +"use strict"; + +var $protobuf = require("protobufjs/minimal"); + +// Common aliases +var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; + +// Exported root namespace +var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); + +$root.Exchange = (function() { + + /** + * Properties of an Exchange. + * @exports IExchange + * @interface IExchange + * @property {Uint8Array|null} [id] Exchange id + * @property {IPublicKey|null} [pubkey] Exchange pubkey + */ + + /** + * Constructs a new Exchange. + * @exports Exchange + * @classdesc Represents an Exchange. + * @implements IExchange + * @constructor + * @param {IExchange=} [p] Properties to set + */ + function Exchange(p) { + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + /** + * Exchange id. + * @member {Uint8Array} id + * @memberof Exchange + * @instance + */ + Exchange.prototype.id = $util.newBuffer([]); + + /** + * Exchange pubkey. + * @member {IPublicKey|null|undefined} pubkey + * @memberof Exchange + * @instance + */ + Exchange.prototype.pubkey = null; + + /** + * Encodes the specified Exchange message. Does not implicitly {@link Exchange.verify|verify} messages. + * @function encode + * @memberof Exchange + * @static + * @param {IExchange} m Exchange message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Exchange.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.id != null && Object.hasOwnProperty.call(m, "id")) + w.uint32(10).bytes(m.id); + if (m.pubkey != null && Object.hasOwnProperty.call(m, "pubkey")) + $root.PublicKey.encode(m.pubkey, w.uint32(18).fork()).ldelim(); + return w; + }; + + /** + * Decodes an Exchange message from the specified reader or buffer. + * @function decode + * @memberof Exchange + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {Exchange} Exchange + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Exchange.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.Exchange(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + m.id = r.bytes(); + break; + case 2: + m.pubkey = $root.PublicKey.decode(r, r.uint32()); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates an Exchange message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof Exchange + * @static + * @param {Object.} d Plain object + * @returns {Exchange} Exchange + */ + Exchange.fromObject = function fromObject(d) { + if (d instanceof $root.Exchange) + return d; + var m = new $root.Exchange(); + if (d.id != null) { + if (typeof d.id === "string") + $util.base64.decode(d.id, m.id = $util.newBuffer($util.base64.length(d.id)), 0); + else if (d.id.length) + m.id = d.id; + } + if (d.pubkey != null) { + if (typeof d.pubkey !== "object") + throw TypeError(".Exchange.pubkey: object expected"); + m.pubkey = $root.PublicKey.fromObject(d.pubkey); + } + return m; + }; + + /** + * Creates a plain object from an Exchange message. Also converts values to other types if specified. + * @function toObject + * @memberof Exchange + * @static + * @param {Exchange} m Exchange + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + Exchange.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.defaults) { + if (o.bytes === String) + d.id = ""; + else { + d.id = []; + if (o.bytes !== Array) + d.id = $util.newBuffer(d.id); + } + d.pubkey = null; + } + if (m.id != null && m.hasOwnProperty("id")) { + d.id = o.bytes === String ? $util.base64.encode(m.id, 0, m.id.length) : o.bytes === Array ? Array.prototype.slice.call(m.id) : m.id; + } + if (m.pubkey != null && m.hasOwnProperty("pubkey")) { + d.pubkey = $root.PublicKey.toObject(m.pubkey, o); + } + return d; + }; + + /** + * Converts this Exchange to JSON. + * @function toJSON + * @memberof Exchange + * @instance + * @returns {Object.} JSON object + */ + Exchange.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return Exchange; +})(); + +/** + * KeyType enum. + * @exports KeyType + * @enum {number} + * @property {number} RSA=0 RSA value + * @property {number} Ed25519=1 Ed25519 value + * @property {number} Secp256k1=2 Secp256k1 value + * @property {number} ECDSA=3 ECDSA value + */ +$root.KeyType = (function() { + var valuesById = {}, values = Object.create(valuesById); + values[valuesById[0] = "RSA"] = 0; + values[valuesById[1] = "Ed25519"] = 1; + values[valuesById[2] = "Secp256k1"] = 2; + values[valuesById[3] = "ECDSA"] = 3; + return values; +})(); + +$root.PublicKey = (function() { + + /** + * Properties of a PublicKey. + * @exports IPublicKey + * @interface IPublicKey + * @property {KeyType|null} [Type] PublicKey Type + * @property {Uint8Array|null} [Data] PublicKey Data + */ + + /** + * Constructs a new PublicKey. + * @exports PublicKey + * @classdesc Represents a PublicKey. + * @implements IPublicKey + * @constructor + * @param {IPublicKey=} [p] Properties to set + */ + function PublicKey(p) { + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + /** + * PublicKey Type. + * @member {KeyType} Type + * @memberof PublicKey + * @instance + */ + PublicKey.prototype.Type = 0; + + /** + * PublicKey Data. + * @member {Uint8Array} Data + * @memberof PublicKey + * @instance + */ + PublicKey.prototype.Data = $util.newBuffer([]); + + /** + * Encodes the specified PublicKey message. Does not implicitly {@link PublicKey.verify|verify} messages. + * @function encode + * @memberof PublicKey + * @static + * @param {IPublicKey} m PublicKey message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + PublicKey.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.Type != null && Object.hasOwnProperty.call(m, "Type")) + w.uint32(8).int32(m.Type); + if (m.Data != null && Object.hasOwnProperty.call(m, "Data")) + w.uint32(18).bytes(m.Data); + return w; + }; + + /** + * Decodes a PublicKey message from the specified reader or buffer. + * @function decode + * @memberof PublicKey + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {PublicKey} PublicKey + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + PublicKey.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.PublicKey(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + m.Type = r.int32(); + break; + case 2: + m.Data = r.bytes(); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates a PublicKey message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof PublicKey + * @static + * @param {Object.} d Plain object + * @returns {PublicKey} PublicKey + */ + PublicKey.fromObject = function fromObject(d) { + if (d instanceof $root.PublicKey) + return d; + var m = new $root.PublicKey(); + switch (d.Type) { + case "RSA": + case 0: + m.Type = 0; + break; + case "Ed25519": + case 1: + m.Type = 1; + break; + case "Secp256k1": + case 2: + m.Type = 2; + break; + case "ECDSA": + case 3: + m.Type = 3; + break; + } + if (d.Data != null) { + if (typeof d.Data === "string") + $util.base64.decode(d.Data, m.Data = $util.newBuffer($util.base64.length(d.Data)), 0); + else if (d.Data.length) + m.Data = d.Data; + } + return m; + }; + + /** + * Creates a plain object from a PublicKey message. Also converts values to other types if specified. + * @function toObject + * @memberof PublicKey + * @static + * @param {PublicKey} m PublicKey + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + PublicKey.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.defaults) { + d.Type = o.enums === String ? "RSA" : 0; + if (o.bytes === String) + d.Data = ""; + else { + d.Data = []; + if (o.bytes !== Array) + d.Data = $util.newBuffer(d.Data); + } + } + if (m.Type != null && m.hasOwnProperty("Type")) { + d.Type = o.enums === String ? $root.KeyType[m.Type] : m.Type; + } + if (m.Data != null && m.hasOwnProperty("Data")) { + d.Data = o.bytes === String ? $util.base64.encode(m.Data, 0, m.Data.length) : o.bytes === Array ? Array.prototype.slice.call(m.Data) : m.Data; + } + return d; + }; + + /** + * Converts this PublicKey to JSON. + * @function toJSON + * @memberof PublicKey + * @instance + * @returns {Object.} JSON object + */ + PublicKey.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return PublicKey; +})(); + +module.exports = $root; diff --git a/src/insecure/proto.proto b/src/insecure/proto.proto new file mode 100644 index 0000000000..9d174a898c --- /dev/null +++ b/src/insecure/proto.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +message Exchange { + optional bytes id = 1; + optional PublicKey pubkey = 2; +} + +enum KeyType { + RSA = 0; + Ed25519 = 1; + Secp256k1 = 2; + ECDSA = 3; +} + +message PublicKey { + KeyType Type = 1; + bytes Data = 2; +} \ No newline at end of file diff --git a/src/keychain/cms.js b/src/keychain/cms.js index bcd5c36506..92a1932a09 100644 --- a/src/keychain/cms.js +++ b/src/keychain/cms.js @@ -1,7 +1,10 @@ 'use strict' +// @ts-ignore node-forge types not exported require('node-forge/lib/pkcs7') +// @ts-ignore node-forge types not exported require('node-forge/lib/pbe') +// @ts-ignore node-forge types not exported const forge = require('node-forge/lib/forge') const { certificateForKey, findAsync } = require('./util') const errcode = require('err-code') @@ -85,6 +88,7 @@ class CMS { try { const buf = forge.util.createBuffer(uint8ArrayToString(cmsData, 'ascii')) const obj = forge.asn1.fromDer(buf) + // @ts-ignore not defined cms = forge.pkcs7.messageFromAsn1(obj) } catch (err) { throw errcode(new Error('Invalid CMS: ' + err.message), 'ERR_INVALID_CMS') @@ -93,11 +97,15 @@ class CMS { // Find a recipient whose key we hold. We only deal with recipient certs // issued by ipfs (O=ipfs). const recipients = cms.recipients + // @ts-ignore cms types not defined .filter(r => r.issuer.find(a => a.shortName === 'O' && a.value === 'ipfs')) + // @ts-ignore cms types not defined .filter(r => r.issuer.find(a => a.shortName === 'CN')) + // @ts-ignore cms types not defined .map(r => { return { recipient: r, + // @ts-ignore cms types not defined keyId: r.issuer.find(a => a.shortName === 'CN').value } }) @@ -113,6 +121,7 @@ class CMS { }) if (!r) { + // @ts-ignore cms types not defined const missingKeys = recipients.map(r => r.keyId) throw errcode(new Error('Decryption needs one of the key(s): ' + missingKeys.join(', ')), 'ERR_MISSING_KEYS', { missingKeys diff --git a/src/keychain/index.js b/src/keychain/index.js index eaff31b5c3..0cf13d0515 100644 --- a/src/keychain/index.js +++ b/src/keychain/index.js @@ -10,6 +10,7 @@ const errcode = require('err-code') const uint8ArrayToString = require('uint8arrays/to-string') const uint8ArrayFromString = require('uint8arrays/from-string') +// @ts-ignore node-forge sha512 types not exported require('node-forge/lib/sha512') /** @@ -17,6 +18,26 @@ require('node-forge/lib/sha512') * @typedef {import('interface-datastore').Datastore} Datastore */ +/** + * @typedef {Object} DekOptions + * @property {string} hash + * @property {string} salt + * @property {number} iterationCount + * @property {number} keyLength + * + * @typedef {Object} KeychainOptions + * @property {string} pass + * @property {DekOptions} [dek] + */ + +/** + * Information about a key. + * + * @typedef {Object} KeyInfo + * @property {string} id - The universally unique key id. + * @property {string} name - The local key name. + */ + const keyPrefix = '/pkcs8/' const infoPrefix = '/info/' const privates = new WeakMap() @@ -38,6 +59,9 @@ const defaultOptions = { } } +/** + * @param {string} name + */ function validateKeyName (name) { if (!name) return false if (typeof name !== 'string') return false @@ -85,14 +109,6 @@ function DsInfoName (name) { return new Key(infoPrefix + name) } -/** - * Information about a key. - * - * @typedef {Object} KeyInfo - * @property {string} id - The universally unique key id. - * @property {string} name - The local key name. - */ - /** * Manages the lifecycle of a key. Keys are encrypted at rest using PKCS #8. * @@ -106,7 +122,7 @@ class Keychain { * Creates a new instance of a key chain. * * @param {Datastore} store - where the key are. - * @param {object} options + * @param {KeychainOptions} options * @class */ constructor (store, options) { @@ -118,8 +134,8 @@ class Keychain { this.opts = mergeOptions(defaultOptions, options) // Enforce NIST SP 800-132 - if (this.opts.passPhrase && this.opts.passPhrase.length < 20) { - throw new Error('passPhrase must be least 20 characters') + if (this.opts.pass && this.opts.pass.length < 20) { + throw new Error('pass must be least 20 characters') } if (this.opts.dek.keyLength < NIST.minKeyLength) { throw new Error(`dek.keyLength must be least ${NIST.minKeyLength} bytes`) @@ -131,12 +147,14 @@ class Keychain { throw new Error(`dek.iterationCount must be least ${NIST.minIterationCount}`) } - 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) : '' + const dek = this.opts.pass + ? crypto.pbkdf2( + this.opts.pass, + this.opts.dek.salt, + this.opts.dek.iterationCount, + this.opts.dek.keyLength, + this.opts.dek.hash) + : '' privates.set(this, { dek }) } diff --git a/src/keychain/util.js b/src/keychain/util.js index 6a332c9ceb..a84c3f1081 100644 --- a/src/keychain/util.js +++ b/src/keychain/util.js @@ -4,7 +4,6 @@ require('node-forge/lib/x509') const forge = require('node-forge/lib/forge') const pki = forge.pki -exports = module.exports /** * Gets a self-signed X.509 certificate for the key. @@ -17,7 +16,7 @@ exports = module.exports * @param {RsaPrivateKey} privateKey - The naked key * @returns {Uint8Array} */ -exports.certificateForKey = (key, privateKey) => { +const certificateForKey = (key, privateKey) => { const publicKey = pki.setRsaPublicKey(privateKey.n, privateKey.e) const cert = pki.createCertificate() cert.publicKey = publicKey @@ -87,4 +86,7 @@ async function findAsync (array, asyncCompare) { return array[index] } -module.exports.findAsync = findAsync +module.exports = { + certificateForKey, + findAsync +} diff --git a/src/metrics/stats.js b/src/metrics/stats.js index a445a705f2..0153624154 100644 --- a/src/metrics/stats.js +++ b/src/metrics/stats.js @@ -1,8 +1,6 @@ // @ts-nocheck 'use strict' -/** @typedef {import('../types').EventEmitterFactory} Events */ -/** @type Events */ const EventEmitter = require('events') const Big = require('bignumber.js') const MovingAverage = require('moving-average') @@ -32,13 +30,13 @@ class Stats extends EventEmitter { const intervals = this._options.movingAverageIntervals - for (var i = 0; i < initialCounters.length; i++) { - var key = initialCounters[i] + for (let i = 0; i < initialCounters.length; i++) { + const key = initialCounters[i] this._stats[key] = Big(0) this._movingAverages[key] = {} - for (var k = 0; k < intervals.length; k++) { - var interval = intervals[k] - var ma = this._movingAverages[key][interval] = MovingAverage(interval) + for (let k = 0; k < intervals.length; k++) { + const interval = intervals[k] + const ma = this._movingAverages[key][interval] = MovingAverage(interval) ma.push(this._frequencyLastTime, 0) } } @@ -72,8 +70,6 @@ class Stats extends EventEmitter { /** * Returns a clone of the current stats. - * - * @returns {Object} */ get snapshot () { return Object.assign({}, this._stats) @@ -81,8 +77,6 @@ class Stats extends EventEmitter { /** * Returns a clone of the internal movingAverages - * - * @returns {Object} */ get movingAverages () { return Object.assign({}, this._movingAverages) @@ -219,9 +213,9 @@ class Stats extends EventEmitter { const intervals = this._options.movingAverageIntervals - for (var i = 0; i < intervals.length; i++) { - var movingAverageInterval = intervals[i] - var movingAverage = movingAverages[movingAverageInterval] + for (let i = 0; i < intervals.length; i++) { + const movingAverageInterval = intervals[i] + let movingAverage = movingAverages[movingAverageInterval] if (!movingAverage) { movingAverage = movingAverages[movingAverageInterval] = MovingAverage(movingAverageInterval) } diff --git a/src/nat-manager.js b/src/nat-manager.js index 887f3d81fb..478db81104 100644 --- a/src/nat-manager.js +++ b/src/nat-manager.js @@ -1,14 +1,16 @@ 'use strict' +// @ts-ignore nat-api does not export types const NatAPI = require('@motrix/nat-api') const debug = require('debug') const { promisify } = require('es6-promisify') -const Multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const log = Object.assign(debug('libp2p:nat'), { error: debug('libp2p:nat:err') }) const { isBrowser } = require('ipfs-utils/src/env') const retry = require('p-retry') +// @ts-ignore private-api does not export types const isPrivateIp = require('private-ip') const pkg = require('../package.json') const errcode = require('err-code') @@ -17,33 +19,39 @@ const { } = require('./errors') const isLoopback = require('libp2p-utils/src/multiaddr/is-loopback') +const DEFAULT_TTL = 7200 + /** * @typedef {import('peer-id')} PeerId * @typedef {import('./transport-manager')} TransportManager * @typedef {import('./address-manager')} AddressManager */ +/** + * @typedef {Object} NatManagerProperties + * @property {PeerId} peerId - The peer ID of the current node + * @property {TransportManager} transportManager - A transport manager + * @property {AddressManager} addressManager - An address manager + * + * @typedef {Object} NatManagerOptions + * @property {boolean} enabled - Whether to enable the NAT manager + * @property {string} [externalIp] - Pass a value to use instead of auto-detection + * @property {string} [description] - A string value to use for the port mapping description on the gateway + * @property {number} [ttl = DEFAULT_TTL] - How long UPnP port mappings should last for in seconds (minimum 1200) + * @property {boolean} [keepAlive] - Whether to automatically refresh UPnP port mappings when their TTL is reached + * @property {string} [gateway] - Pass a value to use instead of auto-detection + * @property {object} [pmp] - PMP options + * @property {boolean} [pmp.enabled] - Whether to enable PMP as well as UPnP + */ + function highPort (min = 1024, max = 65535) { return Math.floor(Math.random() * (max - min + 1) + min) } -const DEFAULT_TTL = 7200 - class NatManager { /** * @class - * @param {object} options - * @param {PeerId} options.peerId - The peer ID of the current node - * @param {TransportManager} options.transportManager - A transport manager - * @param {AddressManager} options.addressManager - An address manager - * @param {boolean} options.enabled - Whether to enable the NAT manager - * @param {string} [options.externalIp] - Pass a value to use instead of auto-detection - * @param {string} [options.description] - A string value to use for the port mapping description on the gateway - * @param {number} [options.ttl] - How long UPnP port mappings should last for in seconds (minimum 1200) - * @param {boolean} [options.keepAlive] - Whether to automatically refresh UPnP port mappings when their TTL is reached - * @param {string} [options.gateway] - Pass a value to use instead of auto-detection - * @param {object} [options.pmp] - PMP options - * @param {boolean} [options.pmp.enabled] - Whether to enable PMP as well as UPnP + * @param {NatManagerProperties & NatManagerOptions} options */ constructor ({ peerId, addressManager, transportManager, ...options }) { this._peerId = peerId @@ -89,15 +97,18 @@ class NatManager { if (!addr.isThinWaistAddress() || transport !== 'tcp') { // only bare tcp addresses + // eslint-disable-next-line no-continue continue } if (isLoopback(addr)) { + // eslint-disable-next-line no-continue continue } - if (family !== 'ipv4') { + if (family !== 4) { // ignore ipv6 + // eslint-disable-next-line no-continue continue } @@ -119,9 +130,9 @@ class NatManager { }) this._addressManager.addObservedAddr(Multiaddr.fromNodeAddress({ - family: 'IPv4', + family: 4, address: publicIp, - port: `${publicPort}` + port: publicPort }, transport)) } } @@ -133,11 +144,11 @@ class NatManager { const client = new NatAPI(this._options) - /** @type {(...any) => any} */ + /** @type {(...any: any) => any} */ const map = promisify(client.map.bind(client)) - /** @type {(...any) => any} */ + /** @type {(...any: any) => any} */ const destroy = promisify(client.destroy.bind(client)) - /** @type {(...any) => any} */ + /** @type {(...any: any) => any} */ const externalIp = promisify(client.externalIp.bind(client)) // these are all network operations so add a retry diff --git a/src/peer-routing.js b/src/peer-routing.js index 32c9cc20e8..e280936182 100644 --- a/src/peer-routing.js +++ b/src/peer-routing.js @@ -19,12 +19,25 @@ const filter = require('it-filter') const { setDelayedInterval, clearDelayedInterval +// @ts-ignore module with no types } = require('set-delayed-interval') -const PeerId = require('peer-id') /** - * @typedef {import('multiaddr')} Multiaddr + * @typedef {import('peer-id')} PeerId + * @typedef {import('multiaddr').Multiaddr} Multiaddr + * @typedef {import('libp2p-interfaces/src/peer-routing/types')} PeerRoutingModule */ + +/** + * @typedef {Object} RefreshManagerOptions + * @property {boolean} [enabled = true] - Whether to enable the Refresh manager + * @property {number} [bootDelay = 6e5] - Boot delay to start the Refresh Manager (in ms) + * @property {number} [interval = 10e3] - Interval between each Refresh Manager run (in ms) + * + * @typedef {Object} PeerRoutingOptions + * @property {RefreshManagerOptions} [refreshManager] + */ + class PeerRouting { /** * @class @@ -33,6 +46,7 @@ class PeerRouting { constructor (libp2p) { this._peerId = libp2p.peerId this._peerStore = libp2p.peerStore + /** @type {PeerRoutingModule[]} */ this._routers = libp2p._modules.peerRouting || [] // If we have the dht, add it to the available peer routers @@ -95,6 +109,7 @@ class PeerRouting { ...this._routers.map(router => [router.findPeer(id, options)]) ), (source) => filter(source, Boolean), + // @ts-ignore findPeer resolves a Promise (source) => storeAddresses(source, this._peerStore), (source) => first(source) ) diff --git a/src/peer-store/address-book.js b/src/peer-store/address-book.js index 74b6049a5c..595781c068 100644 --- a/src/peer-store/address-book.js +++ b/src/peer-store/address-book.js @@ -6,7 +6,7 @@ const log = Object.assign(debug('libp2p:peer-store:address-book'), { }) const errcode = require('err-code') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const PeerId = require('peer-id') const Book = require('./book') @@ -18,7 +18,6 @@ const { const Envelope = require('../record/envelope') /** - * @typedef {import('multiaddr')} Multiaddr * @typedef {import('./')} PeerStore */ @@ -60,7 +59,7 @@ class AddressBook extends Book { if (!data.addresses) { return [] } - return data.addresses.map((address) => address.multiaddr) + return data.addresses.map((/** @type {Address} */ address) => address.multiaddr) } }) @@ -295,9 +294,10 @@ class AddressBook extends Book { } // create Address for each address + /** @type {Address[]} */ const addresses = [] multiaddrs.forEach((addr) => { - if (!multiaddr.isMultiaddr(addr)) { + if (!Multiaddr.isMultiaddr(addr)) { log.error(`multiaddr ${addr} must be an instance of multiaddr`) throw errcode(new Error(`multiaddr ${addr} must be an instance of multiaddr`), ERR_INVALID_PARAMETERS) } diff --git a/src/peer-store/book.js b/src/peer-store/book.js index 48855c157b..9b6d561b1e 100644 --- a/src/peer-store/book.js +++ b/src/peer-store/book.js @@ -7,6 +7,9 @@ const { codes: { ERR_INVALID_PARAMETERS } } = require('../errors') +/** + * @param {any} data + */ const passthrough = data => data /** diff --git a/src/peer-store/index.js b/src/peer-store/index.js index 0741229057..0f2f989f79 100644 --- a/src/peer-store/index.js +++ b/src/peer-store/index.js @@ -2,8 +2,6 @@ const errcode = require('err-code') -/** @typedef {import('../types').EventEmitterFactory} Events */ -/** @type Events */ const EventEmitter = require('events') const PeerId = require('peer-id') diff --git a/src/peer-store/metadata-book.js b/src/peer-store/metadata-book.js index d497bb2f04..50b227da91 100644 --- a/src/peer-store/metadata-book.js +++ b/src/peer-store/metadata-book.js @@ -81,6 +81,9 @@ class MetadataBook extends Book { * Set data into the datastructure * * @override + * @param {PeerId} peerId + * @param {string} key + * @param {Uint8Array} value */ _setValue (peerId, key, value, { emit = true } = {}) { const id = peerId.toB58String() diff --git a/src/peer-store/persistent/index.js b/src/peer-store/persistent/index.js index bbab49657e..8d7d50ea9a 100644 --- a/src/peer-store/persistent/index.js +++ b/src/peer-store/persistent/index.js @@ -5,7 +5,7 @@ const log = Object.assign(debug('libp2p:persistent-peer-store'), { error: debug('libp2p:persistent-peer-store:err') }) const { Key } = require('interface-datastore') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const PeerId = require('peer-id') const PeerStore = require('..') @@ -18,13 +18,18 @@ const { NAMESPACE_PROTOCOL } = require('./consts') -const Addresses = require('./pb/address-book.proto') -const Protocols = require('./pb/proto-book.proto') +const { Addresses } = require('./pb/address-book') +const { Protocols } = require('./pb/proto-book') + +/** + * @typedef {import('interface-datastore').Batch} Batch + * @typedef {import('../address-book.js').Address} Address + */ /** * @typedef {Object} PersistentPeerStoreProperties * @property {PeerId} peerId - * @property {any} datastore + * @property {import('interface-datastore').Datastore} datastore * * @typedef {Object} PersistentPeerStoreOptions * @property {number} [threshold = 5] - Number of dirty peers allowed before commit data. @@ -214,7 +219,7 @@ class PersistentPeerStore extends PeerStore { * * @private * @param {PeerId} peerId - * @param {Object} batch + * @param {Batch} batch */ _batchAddressBook (peerId, batch) { const b32key = peerId.toString() @@ -234,11 +239,13 @@ class PersistentPeerStore extends PeerStore { multiaddr: address.multiaddr.bytes, isCertified: address.isCertified })), - certified_record: entry.record ? { - seq: entry.record.seqNumber, - raw: entry.record.raw - } : undefined - }) + certifiedRecord: entry.record + ? { + seq: entry.record.seqNumber, + raw: entry.record.raw + } + : undefined + }).finish() batch.put(key, encodedData) } catch (err) { @@ -251,7 +258,7 @@ class PersistentPeerStore extends PeerStore { * * @private * @param {PeerId} peerId - * @param {Object} batch + * @param {Batch} batch */ _batchKeyBook (peerId, batch) { const b32key = peerId.toString() @@ -277,14 +284,14 @@ class PersistentPeerStore extends PeerStore { * * @private * @param {PeerId} peerId - * @param {Object} batch + * @param {Batch} batch */ _batchMetadataBook (peerId, batch) { const b32key = peerId.toString() const dirtyMetada = this._dirtyMetadata.get(peerId.toB58String()) || [] try { - dirtyMetada.forEach((dirtyKey) => { + dirtyMetada.forEach((/** @type {string} */ dirtyKey) => { const key = new Key(`${NAMESPACE_METADATA}${b32key}/${dirtyKey}`) const dirtyValue = this.metadataBook.getValue(peerId, dirtyKey) @@ -304,7 +311,7 @@ class PersistentPeerStore extends PeerStore { * * @private * @param {PeerId} peerId - * @param {Object} batch + * @param {Batch} batch */ _batchProtoBook (peerId, batch) { const b32key = peerId.toString() @@ -319,7 +326,7 @@ class PersistentPeerStore extends PeerStore { return } - const encodedData = Protocols.encode({ protocols }) + const encodedData = Protocols.encode({ protocols }).finish() batch.put(key, encodedData) } catch (err) { @@ -351,13 +358,15 @@ class PersistentPeerStore extends PeerStore { peerId, { addresses: decoded.addrs.map((address) => ({ - multiaddr: multiaddr(address.multiaddr), + multiaddr: new Multiaddr(address.multiaddr), isCertified: Boolean(address.isCertified) })), - record: decoded.certified_record ? { - raw: decoded.certified_record.raw, - seqNumber: decoded.certified_record.seq - } : undefined + record: decoded.certifiedRecord + ? { + raw: decoded.certifiedRecord.raw, + seqNumber: decoded.certifiedRecord.seq + } + : undefined }, { emit: false }) break diff --git a/src/peer-store/persistent/pb/address-book.d.ts b/src/peer-store/persistent/pb/address-book.d.ts new file mode 100644 index 0000000000..0080a6390c --- /dev/null +++ b/src/peer-store/persistent/pb/address-book.d.ts @@ -0,0 +1,198 @@ +import * as $protobuf from "protobufjs"; +/** Properties of an Addresses. */ +export interface IAddresses { + + /** Addresses addrs */ + addrs?: (Addresses.IAddress[]|null); + + /** Addresses certifiedRecord */ + certifiedRecord?: (Addresses.ICertifiedRecord|null); +} + +/** Represents an Addresses. */ +export class Addresses implements IAddresses { + + /** + * Constructs a new Addresses. + * @param [p] Properties to set + */ + constructor(p?: IAddresses); + + /** Addresses addrs. */ + public addrs: Addresses.IAddress[]; + + /** Addresses certifiedRecord. */ + public certifiedRecord?: (Addresses.ICertifiedRecord|null); + + /** + * Encodes the specified Addresses message. Does not implicitly {@link Addresses.verify|verify} messages. + * @param m Addresses message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: IAddresses, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes an Addresses message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns Addresses + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Addresses; + + /** + * Creates an Addresses message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns Addresses + */ + public static fromObject(d: { [k: string]: any }): Addresses; + + /** + * Creates a plain object from an Addresses message. Also converts values to other types if specified. + * @param m Addresses + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: Addresses, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Addresses to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; +} + +export namespace Addresses { + + /** Properties of an Address. */ + interface IAddress { + + /** Address multiaddr */ + multiaddr?: (Uint8Array|null); + + /** Address isCertified */ + isCertified?: (boolean|null); + } + + /** Represents an Address. */ + class Address implements IAddress { + + /** + * Constructs a new Address. + * @param [p] Properties to set + */ + constructor(p?: Addresses.IAddress); + + /** Address multiaddr. */ + public multiaddr: Uint8Array; + + /** Address isCertified. */ + public isCertified: boolean; + + /** + * Encodes the specified Address message. Does not implicitly {@link Addresses.Address.verify|verify} messages. + * @param m Address message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: Addresses.IAddress, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes an Address message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns Address + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Addresses.Address; + + /** + * Creates an Address message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns Address + */ + public static fromObject(d: { [k: string]: any }): Addresses.Address; + + /** + * Creates a plain object from an Address message. Also converts values to other types if specified. + * @param m Address + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: Addresses.Address, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Address to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + } + + /** Properties of a CertifiedRecord. */ + interface ICertifiedRecord { + + /** CertifiedRecord seq */ + seq?: (number|null); + + /** CertifiedRecord raw */ + raw?: (Uint8Array|null); + } + + /** Represents a CertifiedRecord. */ + class CertifiedRecord implements ICertifiedRecord { + + /** + * Constructs a new CertifiedRecord. + * @param [p] Properties to set + */ + constructor(p?: Addresses.ICertifiedRecord); + + /** CertifiedRecord seq. */ + public seq: number; + + /** CertifiedRecord raw. */ + public raw: Uint8Array; + + /** + * Encodes the specified CertifiedRecord message. Does not implicitly {@link Addresses.CertifiedRecord.verify|verify} messages. + * @param m CertifiedRecord message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: Addresses.ICertifiedRecord, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a CertifiedRecord message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns CertifiedRecord + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Addresses.CertifiedRecord; + + /** + * Creates a CertifiedRecord message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns CertifiedRecord + */ + public static fromObject(d: { [k: string]: any }): Addresses.CertifiedRecord; + + /** + * Creates a plain object from a CertifiedRecord message. Also converts values to other types if specified. + * @param m CertifiedRecord + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: Addresses.CertifiedRecord, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this CertifiedRecord to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + } +} diff --git a/src/peer-store/persistent/pb/address-book.js b/src/peer-store/persistent/pb/address-book.js new file mode 100644 index 0000000000..489a36ca4d --- /dev/null +++ b/src/peer-store/persistent/pb/address-book.js @@ -0,0 +1,522 @@ +/*eslint-disable*/ +"use strict"; + +var $protobuf = require("protobufjs/minimal"); + +// Common aliases +var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; + +// Exported root namespace +var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); + +$root.Addresses = (function() { + + /** + * Properties of an Addresses. + * @exports IAddresses + * @interface IAddresses + * @property {Array.|null} [addrs] Addresses addrs + * @property {Addresses.ICertifiedRecord|null} [certifiedRecord] Addresses certifiedRecord + */ + + /** + * Constructs a new Addresses. + * @exports Addresses + * @classdesc Represents an Addresses. + * @implements IAddresses + * @constructor + * @param {IAddresses=} [p] Properties to set + */ + function Addresses(p) { + this.addrs = []; + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + /** + * Addresses addrs. + * @member {Array.} addrs + * @memberof Addresses + * @instance + */ + Addresses.prototype.addrs = $util.emptyArray; + + /** + * Addresses certifiedRecord. + * @member {Addresses.ICertifiedRecord|null|undefined} certifiedRecord + * @memberof Addresses + * @instance + */ + Addresses.prototype.certifiedRecord = null; + + /** + * Encodes the specified Addresses message. Does not implicitly {@link Addresses.verify|verify} messages. + * @function encode + * @memberof Addresses + * @static + * @param {IAddresses} m Addresses message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Addresses.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.addrs != null && m.addrs.length) { + for (var i = 0; i < m.addrs.length; ++i) + $root.Addresses.Address.encode(m.addrs[i], w.uint32(10).fork()).ldelim(); + } + if (m.certifiedRecord != null && Object.hasOwnProperty.call(m, "certifiedRecord")) + $root.Addresses.CertifiedRecord.encode(m.certifiedRecord, w.uint32(18).fork()).ldelim(); + return w; + }; + + /** + * Decodes an Addresses message from the specified reader or buffer. + * @function decode + * @memberof Addresses + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {Addresses} Addresses + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Addresses.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.Addresses(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + if (!(m.addrs && m.addrs.length)) + m.addrs = []; + m.addrs.push($root.Addresses.Address.decode(r, r.uint32())); + break; + case 2: + m.certifiedRecord = $root.Addresses.CertifiedRecord.decode(r, r.uint32()); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates an Addresses message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof Addresses + * @static + * @param {Object.} d Plain object + * @returns {Addresses} Addresses + */ + Addresses.fromObject = function fromObject(d) { + if (d instanceof $root.Addresses) + return d; + var m = new $root.Addresses(); + if (d.addrs) { + if (!Array.isArray(d.addrs)) + throw TypeError(".Addresses.addrs: array expected"); + m.addrs = []; + for (var i = 0; i < d.addrs.length; ++i) { + if (typeof d.addrs[i] !== "object") + throw TypeError(".Addresses.addrs: object expected"); + m.addrs[i] = $root.Addresses.Address.fromObject(d.addrs[i]); + } + } + if (d.certifiedRecord != null) { + if (typeof d.certifiedRecord !== "object") + throw TypeError(".Addresses.certifiedRecord: object expected"); + m.certifiedRecord = $root.Addresses.CertifiedRecord.fromObject(d.certifiedRecord); + } + return m; + }; + + /** + * Creates a plain object from an Addresses message. Also converts values to other types if specified. + * @function toObject + * @memberof Addresses + * @static + * @param {Addresses} m Addresses + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + Addresses.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.arrays || o.defaults) { + d.addrs = []; + } + if (o.defaults) { + d.certifiedRecord = null; + } + if (m.addrs && m.addrs.length) { + d.addrs = []; + for (var j = 0; j < m.addrs.length; ++j) { + d.addrs[j] = $root.Addresses.Address.toObject(m.addrs[j], o); + } + } + if (m.certifiedRecord != null && m.hasOwnProperty("certifiedRecord")) { + d.certifiedRecord = $root.Addresses.CertifiedRecord.toObject(m.certifiedRecord, o); + } + return d; + }; + + /** + * Converts this Addresses to JSON. + * @function toJSON + * @memberof Addresses + * @instance + * @returns {Object.} JSON object + */ + Addresses.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + Addresses.Address = (function() { + + /** + * Properties of an Address. + * @memberof Addresses + * @interface IAddress + * @property {Uint8Array|null} [multiaddr] Address multiaddr + * @property {boolean|null} [isCertified] Address isCertified + */ + + /** + * Constructs a new Address. + * @memberof Addresses + * @classdesc Represents an Address. + * @implements IAddress + * @constructor + * @param {Addresses.IAddress=} [p] Properties to set + */ + function Address(p) { + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + /** + * Address multiaddr. + * @member {Uint8Array} multiaddr + * @memberof Addresses.Address + * @instance + */ + Address.prototype.multiaddr = $util.newBuffer([]); + + /** + * Address isCertified. + * @member {boolean} isCertified + * @memberof Addresses.Address + * @instance + */ + Address.prototype.isCertified = false; + + /** + * Encodes the specified Address message. Does not implicitly {@link Addresses.Address.verify|verify} messages. + * @function encode + * @memberof Addresses.Address + * @static + * @param {Addresses.IAddress} m Address message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Address.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.multiaddr != null && Object.hasOwnProperty.call(m, "multiaddr")) + w.uint32(10).bytes(m.multiaddr); + if (m.isCertified != null && Object.hasOwnProperty.call(m, "isCertified")) + w.uint32(16).bool(m.isCertified); + return w; + }; + + /** + * Decodes an Address message from the specified reader or buffer. + * @function decode + * @memberof Addresses.Address + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {Addresses.Address} Address + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Address.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.Addresses.Address(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + m.multiaddr = r.bytes(); + break; + case 2: + m.isCertified = r.bool(); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates an Address message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof Addresses.Address + * @static + * @param {Object.} d Plain object + * @returns {Addresses.Address} Address + */ + Address.fromObject = function fromObject(d) { + if (d instanceof $root.Addresses.Address) + return d; + var m = new $root.Addresses.Address(); + if (d.multiaddr != null) { + if (typeof d.multiaddr === "string") + $util.base64.decode(d.multiaddr, m.multiaddr = $util.newBuffer($util.base64.length(d.multiaddr)), 0); + else if (d.multiaddr.length) + m.multiaddr = d.multiaddr; + } + if (d.isCertified != null) { + m.isCertified = Boolean(d.isCertified); + } + return m; + }; + + /** + * Creates a plain object from an Address message. Also converts values to other types if specified. + * @function toObject + * @memberof Addresses.Address + * @static + * @param {Addresses.Address} m Address + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + Address.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.defaults) { + if (o.bytes === String) + d.multiaddr = ""; + else { + d.multiaddr = []; + if (o.bytes !== Array) + d.multiaddr = $util.newBuffer(d.multiaddr); + } + d.isCertified = false; + } + if (m.multiaddr != null && m.hasOwnProperty("multiaddr")) { + d.multiaddr = o.bytes === String ? $util.base64.encode(m.multiaddr, 0, m.multiaddr.length) : o.bytes === Array ? Array.prototype.slice.call(m.multiaddr) : m.multiaddr; + } + if (m.isCertified != null && m.hasOwnProperty("isCertified")) { + d.isCertified = m.isCertified; + } + return d; + }; + + /** + * Converts this Address to JSON. + * @function toJSON + * @memberof Addresses.Address + * @instance + * @returns {Object.} JSON object + */ + Address.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return Address; + })(); + + Addresses.CertifiedRecord = (function() { + + /** + * Properties of a CertifiedRecord. + * @memberof Addresses + * @interface ICertifiedRecord + * @property {number|null} [seq] CertifiedRecord seq + * @property {Uint8Array|null} [raw] CertifiedRecord raw + */ + + /** + * Constructs a new CertifiedRecord. + * @memberof Addresses + * @classdesc Represents a CertifiedRecord. + * @implements ICertifiedRecord + * @constructor + * @param {Addresses.ICertifiedRecord=} [p] Properties to set + */ + function CertifiedRecord(p) { + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + /** + * CertifiedRecord seq. + * @member {number} seq + * @memberof Addresses.CertifiedRecord + * @instance + */ + CertifiedRecord.prototype.seq = $util.Long ? $util.Long.fromBits(0,0,true) : 0; + + /** + * CertifiedRecord raw. + * @member {Uint8Array} raw + * @memberof Addresses.CertifiedRecord + * @instance + */ + CertifiedRecord.prototype.raw = $util.newBuffer([]); + + /** + * Encodes the specified CertifiedRecord message. Does not implicitly {@link Addresses.CertifiedRecord.verify|verify} messages. + * @function encode + * @memberof Addresses.CertifiedRecord + * @static + * @param {Addresses.ICertifiedRecord} m CertifiedRecord message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + CertifiedRecord.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.seq != null && Object.hasOwnProperty.call(m, "seq")) + w.uint32(8).uint64(m.seq); + if (m.raw != null && Object.hasOwnProperty.call(m, "raw")) + w.uint32(18).bytes(m.raw); + return w; + }; + + /** + * Decodes a CertifiedRecord message from the specified reader or buffer. + * @function decode + * @memberof Addresses.CertifiedRecord + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {Addresses.CertifiedRecord} CertifiedRecord + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + CertifiedRecord.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.Addresses.CertifiedRecord(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + m.seq = r.uint64(); + break; + case 2: + m.raw = r.bytes(); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates a CertifiedRecord message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof Addresses.CertifiedRecord + * @static + * @param {Object.} d Plain object + * @returns {Addresses.CertifiedRecord} CertifiedRecord + */ + CertifiedRecord.fromObject = function fromObject(d) { + if (d instanceof $root.Addresses.CertifiedRecord) + return d; + var m = new $root.Addresses.CertifiedRecord(); + if (d.seq != null) { + if ($util.Long) + (m.seq = $util.Long.fromValue(d.seq)).unsigned = true; + else if (typeof d.seq === "string") + m.seq = parseInt(d.seq, 10); + else if (typeof d.seq === "number") + m.seq = d.seq; + else if (typeof d.seq === "object") + m.seq = new $util.LongBits(d.seq.low >>> 0, d.seq.high >>> 0).toNumber(true); + } + if (d.raw != null) { + if (typeof d.raw === "string") + $util.base64.decode(d.raw, m.raw = $util.newBuffer($util.base64.length(d.raw)), 0); + else if (d.raw.length) + m.raw = d.raw; + } + return m; + }; + + /** + * Creates a plain object from a CertifiedRecord message. Also converts values to other types if specified. + * @function toObject + * @memberof Addresses.CertifiedRecord + * @static + * @param {Addresses.CertifiedRecord} m CertifiedRecord + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + CertifiedRecord.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.defaults) { + if ($util.Long) { + var n = new $util.Long(0, 0, true); + d.seq = o.longs === String ? n.toString() : o.longs === Number ? n.toNumber() : n; + } else + d.seq = o.longs === String ? "0" : 0; + if (o.bytes === String) + d.raw = ""; + else { + d.raw = []; + if (o.bytes !== Array) + d.raw = $util.newBuffer(d.raw); + } + } + if (m.seq != null && m.hasOwnProperty("seq")) { + if (typeof m.seq === "number") + d.seq = o.longs === String ? String(m.seq) : m.seq; + else + d.seq = o.longs === String ? $util.Long.prototype.toString.call(m.seq) : o.longs === Number ? new $util.LongBits(m.seq.low >>> 0, m.seq.high >>> 0).toNumber(true) : m.seq; + } + if (m.raw != null && m.hasOwnProperty("raw")) { + d.raw = o.bytes === String ? $util.base64.encode(m.raw, 0, m.raw.length) : o.bytes === Array ? Array.prototype.slice.call(m.raw) : m.raw; + } + return d; + }; + + /** + * Converts this CertifiedRecord to JSON. + * @function toJSON + * @memberof Addresses.CertifiedRecord + * @instance + * @returns {Object.} JSON object + */ + CertifiedRecord.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return CertifiedRecord; + })(); + + return Addresses; +})(); + +module.exports = $root; diff --git a/src/peer-store/persistent/pb/address-book.proto.js b/src/peer-store/persistent/pb/address-book.proto similarity index 81% rename from src/peer-store/persistent/pb/address-book.proto.js rename to src/peer-store/persistent/pb/address-book.proto index eff744e2e1..d154bf0de7 100644 --- a/src/peer-store/persistent/pb/address-book.proto.js +++ b/src/peer-store/persistent/pb/address-book.proto @@ -1,12 +1,9 @@ -'use strict' +syntax = "proto3"; -const protons = require('protons') - -const message = ` message Addresses { // Address represents a single multiaddr. message Address { - required bytes multiaddr = 1; + bytes multiaddr = 1; // Flag to indicate if the address comes from a certified source. optional bool isCertified = 2; @@ -27,7 +24,4 @@ message Addresses { // The most recently received signed PeerRecord. CertifiedRecord certified_record = 2; -} -` - -module.exports = protons(message).Addresses +} \ No newline at end of file diff --git a/src/peer-store/persistent/pb/proto-book.d.ts b/src/peer-store/persistent/pb/proto-book.d.ts new file mode 100644 index 0000000000..f3590f878c --- /dev/null +++ b/src/peer-store/persistent/pb/proto-book.d.ts @@ -0,0 +1,59 @@ +import * as $protobuf from "protobufjs"; +/** Properties of a Protocols. */ +export interface IProtocols { + + /** Protocols protocols */ + protocols?: (string[]|null); +} + +/** Represents a Protocols. */ +export class Protocols implements IProtocols { + + /** + * Constructs a new Protocols. + * @param [p] Properties to set + */ + constructor(p?: IProtocols); + + /** Protocols protocols. */ + public protocols: string[]; + + /** + * Encodes the specified Protocols message. Does not implicitly {@link Protocols.verify|verify} messages. + * @param m Protocols message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: IProtocols, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a Protocols message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns Protocols + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Protocols; + + /** + * Creates a Protocols message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns Protocols + */ + public static fromObject(d: { [k: string]: any }): Protocols; + + /** + * Creates a plain object from a Protocols message. Also converts values to other types if specified. + * @param m Protocols + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: Protocols, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Protocols to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; +} diff --git a/src/peer-store/persistent/pb/proto-book.js b/src/peer-store/persistent/pb/proto-book.js new file mode 100644 index 0000000000..fea33f9933 --- /dev/null +++ b/src/peer-store/persistent/pb/proto-book.js @@ -0,0 +1,157 @@ +/*eslint-disable*/ +"use strict"; + +var $protobuf = require("protobufjs/minimal"); + +// Common aliases +var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; + +// Exported root namespace +var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); + +$root.Protocols = (function() { + + /** + * Properties of a Protocols. + * @exports IProtocols + * @interface IProtocols + * @property {Array.|null} [protocols] Protocols protocols + */ + + /** + * Constructs a new Protocols. + * @exports Protocols + * @classdesc Represents a Protocols. + * @implements IProtocols + * @constructor + * @param {IProtocols=} [p] Properties to set + */ + function Protocols(p) { + this.protocols = []; + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + /** + * Protocols protocols. + * @member {Array.} protocols + * @memberof Protocols + * @instance + */ + Protocols.prototype.protocols = $util.emptyArray; + + /** + * Encodes the specified Protocols message. Does not implicitly {@link Protocols.verify|verify} messages. + * @function encode + * @memberof Protocols + * @static + * @param {IProtocols} m Protocols message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Protocols.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.protocols != null && m.protocols.length) { + for (var i = 0; i < m.protocols.length; ++i) + w.uint32(10).string(m.protocols[i]); + } + return w; + }; + + /** + * Decodes a Protocols message from the specified reader or buffer. + * @function decode + * @memberof Protocols + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {Protocols} Protocols + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Protocols.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.Protocols(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + if (!(m.protocols && m.protocols.length)) + m.protocols = []; + m.protocols.push(r.string()); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates a Protocols message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof Protocols + * @static + * @param {Object.} d Plain object + * @returns {Protocols} Protocols + */ + Protocols.fromObject = function fromObject(d) { + if (d instanceof $root.Protocols) + return d; + var m = new $root.Protocols(); + if (d.protocols) { + if (!Array.isArray(d.protocols)) + throw TypeError(".Protocols.protocols: array expected"); + m.protocols = []; + for (var i = 0; i < d.protocols.length; ++i) { + m.protocols[i] = String(d.protocols[i]); + } + } + return m; + }; + + /** + * Creates a plain object from a Protocols message. Also converts values to other types if specified. + * @function toObject + * @memberof Protocols + * @static + * @param {Protocols} m Protocols + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + Protocols.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.arrays || o.defaults) { + d.protocols = []; + } + if (m.protocols && m.protocols.length) { + d.protocols = []; + for (var j = 0; j < m.protocols.length; ++j) { + d.protocols[j] = m.protocols[j]; + } + } + return d; + }; + + /** + * Converts this Protocols to JSON. + * @function toJSON + * @memberof Protocols + * @instance + * @returns {Object.} JSON object + */ + Protocols.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return Protocols; +})(); + +module.exports = $root; diff --git a/src/peer-store/persistent/pb/proto-book.proto b/src/peer-store/persistent/pb/proto-book.proto new file mode 100644 index 0000000000..a452f0caf9 --- /dev/null +++ b/src/peer-store/persistent/pb/proto-book.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message Protocols { + repeated string protocols = 1; +} \ No newline at end of file diff --git a/src/peer-store/persistent/pb/proto-book.proto.js b/src/peer-store/persistent/pb/proto-book.proto.js deleted file mode 100644 index 74b7e223ed..0000000000 --- a/src/peer-store/persistent/pb/proto-book.proto.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict' - -const protons = require('protons') - -/* eslint-disable no-tabs */ -const message = ` -message Protocols { - repeated string protocols = 1; -} -` - -module.exports = protons(message).Protocols diff --git a/src/peer-store/proto-book.js b/src/peer-store/proto-book.js index 5c17b1371a..3ce6d306e0 100644 --- a/src/peer-store/proto-book.js +++ b/src/peer-store/proto-book.js @@ -74,6 +74,10 @@ class ProtoBook extends Book { const recSet = this.data.get(id) const newSet = new Set(protocols) + /** + * @param {Set} a + * @param {Set} b + */ const isSetEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value)) // Already knows the peer and the recorded protocols are the same? diff --git a/src/ping/index.js b/src/ping/index.js index eb8d7b96e9..6ad988526f 100644 --- a/src/ping/index.js +++ b/src/ping/index.js @@ -8,6 +8,7 @@ const errCode = require('err-code') const crypto = require('libp2p-crypto') const { pipe } = require('it-pipe') +// @ts-ignore it-buffer has no types exported const { toBuffer } = require('it-buffer') const { collect, take } = require('streaming-iterables') const equals = require('uint8arrays/equals') @@ -16,8 +17,9 @@ const { PROTOCOL, PING_LENGTH } = require('./constants') /** * @typedef {import('../')} Libp2p - * @typedef {import('multiaddr')} Multiaddr + * @typedef {import('multiaddr').Multiaddr} Multiaddr * @typedef {import('peer-id')} PeerId + * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream */ /** @@ -31,7 +33,8 @@ async function ping (node, peer) { // @ts-ignore multiaddr might not have toB58String log('dialing %s to %s', PROTOCOL, peer.toB58String ? peer.toB58String() : peer) - const { stream } = await node.dialProtocol(peer, PROTOCOL) + const connection = await node.dial(peer) + const { stream } = await connection.newStream(PROTOCOL) const start = Date.now() const data = crypto.randomBytes(PING_LENGTH) @@ -39,7 +42,7 @@ async function ping (node, peer) { const [result] = await pipe( [data], stream, - stream => take(1, stream), + (/** @type {MuxedStream} */ stream) => take(1, stream), toBuffer, collect ) diff --git a/src/ping/util.js b/src/ping/util.js index d46b3c619b..e942420a5d 100644 --- a/src/ping/util.js +++ b/src/ping/util.js @@ -3,11 +3,16 @@ const crypto = require('libp2p-crypto') const constants = require('./constants') -exports = module.exports - -exports.rnd = (length) => { +/** + * @param {number} length + */ +function rnd (length) { if (!length) { length = constants.PING_LENGTH } return crypto.randomBytes(length) } + +module.exports = { + rnd +} diff --git a/src/pnet/crypto.js b/src/pnet/crypto.js index 9cf6f76581..da1f829710 100644 --- a/src/pnet/crypto.js +++ b/src/pnet/crypto.js @@ -7,6 +7,7 @@ const log = Object.assign(debug('libp2p:pnet'), { }) const Errors = require('./errors') +// @ts-ignore xsalsa20 has no types exported const xsalsa20 = require('xsalsa20') const KEY_LENGTH = require('./key-generator').KEY_LENGTH const uint8ArrayFromString = require('uint8arrays/from-string') @@ -21,7 +22,8 @@ const uint8ArrayToString = require('uint8arrays/to-string') */ module.exports.createBoxStream = (nonce, psk) => { const xor = xsalsa20(nonce, psk) - return (source) => (async function * () { + + return (/** @type {AsyncIterable} */ source) => (async function * () { for await (const chunk of source) { yield Uint8Array.from(xor.update(chunk.slice())) } @@ -36,7 +38,7 @@ module.exports.createBoxStream = (nonce, psk) => { * @returns {*} a through iterable */ module.exports.createUnboxStream = (nonce, psk) => { - return (source) => (async function * () { + return (/** @type {AsyncIterable} */ source) => (async function * () { const xor = xsalsa20(nonce, psk) log.trace('Decryption enabled') @@ -51,7 +53,7 @@ module.exports.createUnboxStream = (nonce, psk) => { * * @param {Uint8Array} pskBuffer * @throws {INVALID_PSK} - * @returns {Object} The PSK metadata (tag, codecName, psk) + * @returns {{ tag?: string, codecName?: string, psk: Uint8Array }} The PSK metadata (tag, codecName, psk) */ module.exports.decodeV1PSK = (pskBuffer) => { try { diff --git a/src/pnet/index.js b/src/pnet/index.js index 194a5005ec..c54e1f9c4d 100644 --- a/src/pnet/index.js +++ b/src/pnet/index.js @@ -6,6 +6,7 @@ const log = Object.assign(debug('libp2p:pnet'), { }) const { pipe } = require('it-pipe') const errcode = require('err-code') +// @ts-ignore it-pair has no types exported const duplexPair = require('it-pair/duplex') const crypto = require('libp2p-crypto') const Errors = require('./errors') @@ -17,6 +18,7 @@ const { createUnboxStream, decodeV1PSK } = require('./crypto') +// @ts-ignore it-handshake has no types exported const handshake = require('it-handshake') const { NONCE_LENGTH } = require('./key-generator') diff --git a/src/pnet/key-generator.js b/src/pnet/key-generator.js index 8a7a1ef5a2..e973f7787c 100644 --- a/src/pnet/key-generator.js +++ b/src/pnet/key-generator.js @@ -22,8 +22,12 @@ module.exports = generate module.exports.NONCE_LENGTH = 24 module.exports.KEY_LENGTH = KEY_LENGTH -// @ts-ignore This condition will always return 'false' since the types 'Module | undefined' -if (require.main === module) { - // @ts-ignore - generate(process.stdout) +try { + // @ts-ignore This condition will always return 'false' since the types 'Module | undefined' + if (require.main === module) { + // @ts-ignore + generate(process.stdout) + } +} catch (error) { + } diff --git a/src/pubsub-adapter.js b/src/pubsub-adapter.js index 7d7af8df2f..e965ab5c4b 100644 --- a/src/pubsub-adapter.js +++ b/src/pubsub-adapter.js @@ -1,12 +1,18 @@ 'use strict' +// Pubsub adapter to keep API with handlers while not removed. /** * @typedef {import('libp2p-interfaces/src/pubsub').InMessage} InMessage * @typedef {import('libp2p-interfaces/src/pubsub')} PubsubRouter */ -// Pubsub adapter to keep API with handlers while not removed. +/** + * @param {import("libp2p-interfaces/src/pubsub")} PubsubRouter + * @param {import('.')} libp2p + * @param {{ enabled: boolean; } & import(".").PubsubLocalOptions & import("libp2p-interfaces/src/pubsub").PubsubOptions} options + */ function pubsubAdapter (PubsubRouter, libp2p, options) { + // @ts-ignore Pubsub constructor type not defined const pubsub = new PubsubRouter(libp2p, options) pubsub._subscribeAdapter = pubsub.subscribe pubsub._unsubscribeAdapter = pubsub.unsubscribe diff --git a/src/record/envelope/envelope.d.ts b/src/record/envelope/envelope.d.ts new file mode 100644 index 0000000000..440590c14b --- /dev/null +++ b/src/record/envelope/envelope.d.ts @@ -0,0 +1,77 @@ +import * as $protobuf from "protobufjs"; +/** Properties of an Envelope. */ +export interface IEnvelope { + + /** Envelope publicKey */ + publicKey?: (Uint8Array|null); + + /** Envelope payloadType */ + payloadType?: (Uint8Array|null); + + /** Envelope payload */ + payload?: (Uint8Array|null); + + /** Envelope signature */ + signature?: (Uint8Array|null); +} + +/** Represents an Envelope. */ +export class Envelope implements IEnvelope { + + /** + * Constructs a new Envelope. + * @param [p] Properties to set + */ + constructor(p?: IEnvelope); + + /** Envelope publicKey. */ + public publicKey: Uint8Array; + + /** Envelope payloadType. */ + public payloadType: Uint8Array; + + /** Envelope payload. */ + public payload: Uint8Array; + + /** Envelope signature. */ + public signature: Uint8Array; + + /** + * Encodes the specified Envelope message. Does not implicitly {@link Envelope.verify|verify} messages. + * @param m Envelope message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: IEnvelope, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes an Envelope message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns Envelope + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Envelope; + + /** + * Creates an Envelope message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns Envelope + */ + public static fromObject(d: { [k: string]: any }): Envelope; + + /** + * Creates a plain object from an Envelope message. Also converts values to other types if specified. + * @param m Envelope + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: Envelope, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Envelope to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; +} diff --git a/src/record/envelope/envelope.js b/src/record/envelope/envelope.js new file mode 100644 index 0000000000..ff102b2470 --- /dev/null +++ b/src/record/envelope/envelope.js @@ -0,0 +1,243 @@ +/*eslint-disable*/ +"use strict"; + +var $protobuf = require("protobufjs/minimal"); + +// Common aliases +var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; + +// Exported root namespace +var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); + +$root.Envelope = (function() { + + /** + * Properties of an Envelope. + * @exports IEnvelope + * @interface IEnvelope + * @property {Uint8Array|null} [publicKey] Envelope publicKey + * @property {Uint8Array|null} [payloadType] Envelope payloadType + * @property {Uint8Array|null} [payload] Envelope payload + * @property {Uint8Array|null} [signature] Envelope signature + */ + + /** + * Constructs a new Envelope. + * @exports Envelope + * @classdesc Represents an Envelope. + * @implements IEnvelope + * @constructor + * @param {IEnvelope=} [p] Properties to set + */ + function Envelope(p) { + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + /** + * Envelope publicKey. + * @member {Uint8Array} publicKey + * @memberof Envelope + * @instance + */ + Envelope.prototype.publicKey = $util.newBuffer([]); + + /** + * Envelope payloadType. + * @member {Uint8Array} payloadType + * @memberof Envelope + * @instance + */ + Envelope.prototype.payloadType = $util.newBuffer([]); + + /** + * Envelope payload. + * @member {Uint8Array} payload + * @memberof Envelope + * @instance + */ + Envelope.prototype.payload = $util.newBuffer([]); + + /** + * Envelope signature. + * @member {Uint8Array} signature + * @memberof Envelope + * @instance + */ + Envelope.prototype.signature = $util.newBuffer([]); + + /** + * Encodes the specified Envelope message. Does not implicitly {@link Envelope.verify|verify} messages. + * @function encode + * @memberof Envelope + * @static + * @param {IEnvelope} m Envelope message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Envelope.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.publicKey != null && Object.hasOwnProperty.call(m, "publicKey")) + w.uint32(10).bytes(m.publicKey); + if (m.payloadType != null && Object.hasOwnProperty.call(m, "payloadType")) + w.uint32(18).bytes(m.payloadType); + if (m.payload != null && Object.hasOwnProperty.call(m, "payload")) + w.uint32(26).bytes(m.payload); + if (m.signature != null && Object.hasOwnProperty.call(m, "signature")) + w.uint32(42).bytes(m.signature); + return w; + }; + + /** + * Decodes an Envelope message from the specified reader or buffer. + * @function decode + * @memberof Envelope + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {Envelope} Envelope + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Envelope.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.Envelope(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + m.publicKey = r.bytes(); + break; + case 2: + m.payloadType = r.bytes(); + break; + case 3: + m.payload = r.bytes(); + break; + case 5: + m.signature = r.bytes(); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates an Envelope message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof Envelope + * @static + * @param {Object.} d Plain object + * @returns {Envelope} Envelope + */ + Envelope.fromObject = function fromObject(d) { + if (d instanceof $root.Envelope) + return d; + var m = new $root.Envelope(); + if (d.publicKey != null) { + if (typeof d.publicKey === "string") + $util.base64.decode(d.publicKey, m.publicKey = $util.newBuffer($util.base64.length(d.publicKey)), 0); + else if (d.publicKey.length) + m.publicKey = d.publicKey; + } + if (d.payloadType != null) { + if (typeof d.payloadType === "string") + $util.base64.decode(d.payloadType, m.payloadType = $util.newBuffer($util.base64.length(d.payloadType)), 0); + else if (d.payloadType.length) + m.payloadType = d.payloadType; + } + if (d.payload != null) { + if (typeof d.payload === "string") + $util.base64.decode(d.payload, m.payload = $util.newBuffer($util.base64.length(d.payload)), 0); + else if (d.payload.length) + m.payload = d.payload; + } + if (d.signature != null) { + if (typeof d.signature === "string") + $util.base64.decode(d.signature, m.signature = $util.newBuffer($util.base64.length(d.signature)), 0); + else if (d.signature.length) + m.signature = d.signature; + } + return m; + }; + + /** + * Creates a plain object from an Envelope message. Also converts values to other types if specified. + * @function toObject + * @memberof Envelope + * @static + * @param {Envelope} m Envelope + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + Envelope.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.defaults) { + if (o.bytes === String) + d.publicKey = ""; + else { + d.publicKey = []; + if (o.bytes !== Array) + d.publicKey = $util.newBuffer(d.publicKey); + } + if (o.bytes === String) + d.payloadType = ""; + else { + d.payloadType = []; + if (o.bytes !== Array) + d.payloadType = $util.newBuffer(d.payloadType); + } + if (o.bytes === String) + d.payload = ""; + else { + d.payload = []; + if (o.bytes !== Array) + d.payload = $util.newBuffer(d.payload); + } + if (o.bytes === String) + d.signature = ""; + else { + d.signature = []; + if (o.bytes !== Array) + d.signature = $util.newBuffer(d.signature); + } + } + if (m.publicKey != null && m.hasOwnProperty("publicKey")) { + d.publicKey = o.bytes === String ? $util.base64.encode(m.publicKey, 0, m.publicKey.length) : o.bytes === Array ? Array.prototype.slice.call(m.publicKey) : m.publicKey; + } + if (m.payloadType != null && m.hasOwnProperty("payloadType")) { + d.payloadType = o.bytes === String ? $util.base64.encode(m.payloadType, 0, m.payloadType.length) : o.bytes === Array ? Array.prototype.slice.call(m.payloadType) : m.payloadType; + } + if (m.payload != null && m.hasOwnProperty("payload")) { + d.payload = o.bytes === String ? $util.base64.encode(m.payload, 0, m.payload.length) : o.bytes === Array ? Array.prototype.slice.call(m.payload) : m.payload; + } + if (m.signature != null && m.hasOwnProperty("signature")) { + d.signature = o.bytes === String ? $util.base64.encode(m.signature, 0, m.signature.length) : o.bytes === Array ? Array.prototype.slice.call(m.signature) : m.signature; + } + return d; + }; + + /** + * Converts this Envelope to JSON. + * @function toJSON + * @memberof Envelope + * @instance + * @returns {Object.} JSON object + */ + Envelope.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return Envelope; +})(); + +module.exports = $root; diff --git a/src/record/envelope/envelope.proto.js b/src/record/envelope/envelope.proto similarity index 79% rename from src/record/envelope/envelope.proto.js rename to src/record/envelope/envelope.proto index c8907debda..5b80cf504c 100644 --- a/src/record/envelope/envelope.proto.js +++ b/src/record/envelope/envelope.proto @@ -1,9 +1,5 @@ -'use strict' +syntax = "proto3"; -const protons = require('protons') - -/** @type {{Envelope: import('../../types').MessageProto}} */ -module.exports = protons(` message Envelope { // public_key is the public key of the keypair the enclosed payload was // signed with. @@ -20,5 +16,4 @@ message Envelope { // the enclosed public key, over the payload, prefixing a domain string for // additional security. bytes signature = 5; -} -`) +} \ No newline at end of file diff --git a/src/record/envelope/index.js b/src/record/envelope/index.js index 46f9c3ccf6..02be96e06f 100644 --- a/src/record/envelope/index.js +++ b/src/record/envelope/index.js @@ -3,13 +3,14 @@ const errCode = require('err-code') const uint8arraysConcat = require('uint8arrays/concat') const uint8arraysFromString = require('uint8arrays/from-string') +// @ts-ignore libp2p-crypto does not support types const cryptoKeys = require('libp2p-crypto/src/keys') const PeerId = require('peer-id') const varint = require('varint') const uint8arraysEquals = require('uint8arrays/equals') const { codes } = require('../../errors') -const Protobuf = require('./envelope.proto') +const { Envelope: Protobuf } = require('./envelope') /** * @typedef {import('libp2p-interfaces/src/record/types').Record} Record @@ -49,12 +50,12 @@ class Envelope { const publicKey = cryptoKeys.marshalPublicKey(this.peerId.pubKey) - this._marshal = Protobuf.Envelope.encode({ - public_key: publicKey, - payload_type: this.payloadType, + this._marshal = Protobuf.encode({ + publicKey: publicKey, + payloadType: this.payloadType, payload: this.payload, signature: this.signature - }) + }).finish() return this._marshal } @@ -124,12 +125,12 @@ const formatSignaturePayload = (domain, payloadType, payload) => { * @returns {Promise} */ Envelope.createFromProtobuf = async (data) => { - const envelopeData = Protobuf.Envelope.decode(data) - const peerId = await PeerId.createFromPubKey(envelopeData.public_key) + const envelopeData = Protobuf.decode(data) + const peerId = await PeerId.createFromPubKey(envelopeData.publicKey) return new Envelope({ peerId, - payloadType: envelopeData.payload_type, + payloadType: envelopeData.payloadType, payload: envelopeData.payload, signature: envelopeData.signature }) diff --git a/src/record/peer-record/index.js b/src/record/peer-record/index.js index 32d018abc5..dcdc7b6285 100644 --- a/src/record/peer-record/index.js +++ b/src/record/peer-record/index.js @@ -1,18 +1,17 @@ 'use strict' -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const PeerId = require('peer-id') const arrayEquals = require('libp2p-utils/src/array-equals') -const Protobuf = require('./peer-record.proto') +const { PeerRecord: Protobuf } = require('./peer-record') const { ENVELOPE_DOMAIN_PEER_RECORD, ENVELOPE_PAYLOAD_TYPE_PEER_RECORD } = require('./consts') /** - * @typedef {import('peer-id')} PeerId - * @typedef {import('multiaddr')} Multiaddr + * @typedef {import('../../peer-store/address-book.js').Address} Address * @typedef {import('libp2p-interfaces/src/record/types').Record} Record */ @@ -52,13 +51,13 @@ class PeerRecord { return this._marshal } - this._marshal = Protobuf.PeerRecord.encode({ - peer_id: this.peerId.toBytes(), + this._marshal = Protobuf.encode({ + peerId: this.peerId.toBytes(), seq: this.seqNumber, addresses: this.multiaddrs.map((m) => ({ multiaddr: m.bytes })) - }) + }).finish() return this._marshal } @@ -100,12 +99,11 @@ class PeerRecord { * @returns {PeerRecord} */ PeerRecord.createFromProtobuf = (buf) => { - // Decode - const peerRecord = Protobuf.PeerRecord.decode(buf) + const peerRecord = Protobuf.decode(buf) - const peerId = PeerId.createFromBytes(peerRecord.peer_id) - const multiaddrs = (peerRecord.addresses || []).map((a) => multiaddr(a.multiaddr)) - const seqNumber = peerRecord.seq + const peerId = PeerId.createFromBytes(peerRecord.peerId) + const multiaddrs = (peerRecord.addresses || []).map((a) => new Multiaddr(a.multiaddr)) + const seqNumber = Number(peerRecord.seq) return new PeerRecord({ peerId, multiaddrs, seqNumber }) } diff --git a/src/record/peer-record/peer-record.d.ts b/src/record/peer-record/peer-record.d.ts new file mode 100644 index 0000000000..a851b53307 --- /dev/null +++ b/src/record/peer-record/peer-record.d.ts @@ -0,0 +1,133 @@ +import * as $protobuf from "protobufjs"; +/** Properties of a PeerRecord. */ +export interface IPeerRecord { + + /** PeerRecord peerId */ + peerId?: (Uint8Array|null); + + /** PeerRecord seq */ + seq?: (number|null); + + /** PeerRecord addresses */ + addresses?: (PeerRecord.IAddressInfo[]|null); +} + +/** Represents a PeerRecord. */ +export class PeerRecord implements IPeerRecord { + + /** + * Constructs a new PeerRecord. + * @param [p] Properties to set + */ + constructor(p?: IPeerRecord); + + /** PeerRecord peerId. */ + public peerId: Uint8Array; + + /** PeerRecord seq. */ + public seq: number; + + /** PeerRecord addresses. */ + public addresses: PeerRecord.IAddressInfo[]; + + /** + * Encodes the specified PeerRecord message. Does not implicitly {@link PeerRecord.verify|verify} messages. + * @param m PeerRecord message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: IPeerRecord, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a PeerRecord message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns PeerRecord + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): PeerRecord; + + /** + * Creates a PeerRecord message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns PeerRecord + */ + public static fromObject(d: { [k: string]: any }): PeerRecord; + + /** + * Creates a plain object from a PeerRecord message. Also converts values to other types if specified. + * @param m PeerRecord + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: PeerRecord, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this PeerRecord to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; +} + +export namespace PeerRecord { + + /** Properties of an AddressInfo. */ + interface IAddressInfo { + + /** AddressInfo multiaddr */ + multiaddr?: (Uint8Array|null); + } + + /** Represents an AddressInfo. */ + class AddressInfo implements IAddressInfo { + + /** + * Constructs a new AddressInfo. + * @param [p] Properties to set + */ + constructor(p?: PeerRecord.IAddressInfo); + + /** AddressInfo multiaddr. */ + public multiaddr: Uint8Array; + + /** + * Encodes the specified AddressInfo message. Does not implicitly {@link PeerRecord.AddressInfo.verify|verify} messages. + * @param m AddressInfo message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: PeerRecord.IAddressInfo, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes an AddressInfo message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns AddressInfo + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): PeerRecord.AddressInfo; + + /** + * Creates an AddressInfo message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns AddressInfo + */ + public static fromObject(d: { [k: string]: any }): PeerRecord.AddressInfo; + + /** + * Creates a plain object from an AddressInfo message. Also converts values to other types if specified. + * @param m AddressInfo + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: PeerRecord.AddressInfo, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this AddressInfo to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + } +} diff --git a/src/record/peer-record/peer-record.js b/src/record/peer-record/peer-record.js new file mode 100644 index 0000000000..e851b146b7 --- /dev/null +++ b/src/record/peer-record/peer-record.js @@ -0,0 +1,367 @@ +/*eslint-disable*/ +"use strict"; + +var $protobuf = require("protobufjs/minimal"); + +// Common aliases +var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; + +// Exported root namespace +var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); + +$root.PeerRecord = (function() { + + /** + * Properties of a PeerRecord. + * @exports IPeerRecord + * @interface IPeerRecord + * @property {Uint8Array|null} [peerId] PeerRecord peerId + * @property {number|null} [seq] PeerRecord seq + * @property {Array.|null} [addresses] PeerRecord addresses + */ + + /** + * Constructs a new PeerRecord. + * @exports PeerRecord + * @classdesc Represents a PeerRecord. + * @implements IPeerRecord + * @constructor + * @param {IPeerRecord=} [p] Properties to set + */ + function PeerRecord(p) { + this.addresses = []; + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + /** + * PeerRecord peerId. + * @member {Uint8Array} peerId + * @memberof PeerRecord + * @instance + */ + PeerRecord.prototype.peerId = $util.newBuffer([]); + + /** + * PeerRecord seq. + * @member {number} seq + * @memberof PeerRecord + * @instance + */ + PeerRecord.prototype.seq = $util.Long ? $util.Long.fromBits(0,0,true) : 0; + + /** + * PeerRecord addresses. + * @member {Array.} addresses + * @memberof PeerRecord + * @instance + */ + PeerRecord.prototype.addresses = $util.emptyArray; + + /** + * Encodes the specified PeerRecord message. Does not implicitly {@link PeerRecord.verify|verify} messages. + * @function encode + * @memberof PeerRecord + * @static + * @param {IPeerRecord} m PeerRecord message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + PeerRecord.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.peerId != null && Object.hasOwnProperty.call(m, "peerId")) + w.uint32(10).bytes(m.peerId); + if (m.seq != null && Object.hasOwnProperty.call(m, "seq")) + w.uint32(16).uint64(m.seq); + if (m.addresses != null && m.addresses.length) { + for (var i = 0; i < m.addresses.length; ++i) + $root.PeerRecord.AddressInfo.encode(m.addresses[i], w.uint32(26).fork()).ldelim(); + } + return w; + }; + + /** + * Decodes a PeerRecord message from the specified reader or buffer. + * @function decode + * @memberof PeerRecord + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {PeerRecord} PeerRecord + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + PeerRecord.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.PeerRecord(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + m.peerId = r.bytes(); + break; + case 2: + m.seq = r.uint64(); + break; + case 3: + if (!(m.addresses && m.addresses.length)) + m.addresses = []; + m.addresses.push($root.PeerRecord.AddressInfo.decode(r, r.uint32())); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates a PeerRecord message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof PeerRecord + * @static + * @param {Object.} d Plain object + * @returns {PeerRecord} PeerRecord + */ + PeerRecord.fromObject = function fromObject(d) { + if (d instanceof $root.PeerRecord) + return d; + var m = new $root.PeerRecord(); + if (d.peerId != null) { + if (typeof d.peerId === "string") + $util.base64.decode(d.peerId, m.peerId = $util.newBuffer($util.base64.length(d.peerId)), 0); + else if (d.peerId.length) + m.peerId = d.peerId; + } + if (d.seq != null) { + if ($util.Long) + (m.seq = $util.Long.fromValue(d.seq)).unsigned = true; + else if (typeof d.seq === "string") + m.seq = parseInt(d.seq, 10); + else if (typeof d.seq === "number") + m.seq = d.seq; + else if (typeof d.seq === "object") + m.seq = new $util.LongBits(d.seq.low >>> 0, d.seq.high >>> 0).toNumber(true); + } + if (d.addresses) { + if (!Array.isArray(d.addresses)) + throw TypeError(".PeerRecord.addresses: array expected"); + m.addresses = []; + for (var i = 0; i < d.addresses.length; ++i) { + if (typeof d.addresses[i] !== "object") + throw TypeError(".PeerRecord.addresses: object expected"); + m.addresses[i] = $root.PeerRecord.AddressInfo.fromObject(d.addresses[i]); + } + } + return m; + }; + + /** + * Creates a plain object from a PeerRecord message. Also converts values to other types if specified. + * @function toObject + * @memberof PeerRecord + * @static + * @param {PeerRecord} m PeerRecord + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + PeerRecord.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.arrays || o.defaults) { + d.addresses = []; + } + if (o.defaults) { + if (o.bytes === String) + d.peerId = ""; + else { + d.peerId = []; + if (o.bytes !== Array) + d.peerId = $util.newBuffer(d.peerId); + } + if ($util.Long) { + var n = new $util.Long(0, 0, true); + d.seq = o.longs === String ? n.toString() : o.longs === Number ? n.toNumber() : n; + } else + d.seq = o.longs === String ? "0" : 0; + } + if (m.peerId != null && m.hasOwnProperty("peerId")) { + d.peerId = o.bytes === String ? $util.base64.encode(m.peerId, 0, m.peerId.length) : o.bytes === Array ? Array.prototype.slice.call(m.peerId) : m.peerId; + } + if (m.seq != null && m.hasOwnProperty("seq")) { + if (typeof m.seq === "number") + d.seq = o.longs === String ? String(m.seq) : m.seq; + else + d.seq = o.longs === String ? $util.Long.prototype.toString.call(m.seq) : o.longs === Number ? new $util.LongBits(m.seq.low >>> 0, m.seq.high >>> 0).toNumber(true) : m.seq; + } + if (m.addresses && m.addresses.length) { + d.addresses = []; + for (var j = 0; j < m.addresses.length; ++j) { + d.addresses[j] = $root.PeerRecord.AddressInfo.toObject(m.addresses[j], o); + } + } + return d; + }; + + /** + * Converts this PeerRecord to JSON. + * @function toJSON + * @memberof PeerRecord + * @instance + * @returns {Object.} JSON object + */ + PeerRecord.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + PeerRecord.AddressInfo = (function() { + + /** + * Properties of an AddressInfo. + * @memberof PeerRecord + * @interface IAddressInfo + * @property {Uint8Array|null} [multiaddr] AddressInfo multiaddr + */ + + /** + * Constructs a new AddressInfo. + * @memberof PeerRecord + * @classdesc Represents an AddressInfo. + * @implements IAddressInfo + * @constructor + * @param {PeerRecord.IAddressInfo=} [p] Properties to set + */ + function AddressInfo(p) { + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + /** + * AddressInfo multiaddr. + * @member {Uint8Array} multiaddr + * @memberof PeerRecord.AddressInfo + * @instance + */ + AddressInfo.prototype.multiaddr = $util.newBuffer([]); + + /** + * Encodes the specified AddressInfo message. Does not implicitly {@link PeerRecord.AddressInfo.verify|verify} messages. + * @function encode + * @memberof PeerRecord.AddressInfo + * @static + * @param {PeerRecord.IAddressInfo} m AddressInfo message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + AddressInfo.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.multiaddr != null && Object.hasOwnProperty.call(m, "multiaddr")) + w.uint32(10).bytes(m.multiaddr); + return w; + }; + + /** + * Decodes an AddressInfo message from the specified reader or buffer. + * @function decode + * @memberof PeerRecord.AddressInfo + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {PeerRecord.AddressInfo} AddressInfo + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + AddressInfo.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.PeerRecord.AddressInfo(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + m.multiaddr = r.bytes(); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates an AddressInfo message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof PeerRecord.AddressInfo + * @static + * @param {Object.} d Plain object + * @returns {PeerRecord.AddressInfo} AddressInfo + */ + AddressInfo.fromObject = function fromObject(d) { + if (d instanceof $root.PeerRecord.AddressInfo) + return d; + var m = new $root.PeerRecord.AddressInfo(); + if (d.multiaddr != null) { + if (typeof d.multiaddr === "string") + $util.base64.decode(d.multiaddr, m.multiaddr = $util.newBuffer($util.base64.length(d.multiaddr)), 0); + else if (d.multiaddr.length) + m.multiaddr = d.multiaddr; + } + return m; + }; + + /** + * Creates a plain object from an AddressInfo message. Also converts values to other types if specified. + * @function toObject + * @memberof PeerRecord.AddressInfo + * @static + * @param {PeerRecord.AddressInfo} m AddressInfo + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + AddressInfo.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.defaults) { + if (o.bytes === String) + d.multiaddr = ""; + else { + d.multiaddr = []; + if (o.bytes !== Array) + d.multiaddr = $util.newBuffer(d.multiaddr); + } + } + if (m.multiaddr != null && m.hasOwnProperty("multiaddr")) { + d.multiaddr = o.bytes === String ? $util.base64.encode(m.multiaddr, 0, m.multiaddr.length) : o.bytes === Array ? Array.prototype.slice.call(m.multiaddr) : m.multiaddr; + } + return d; + }; + + /** + * Converts this AddressInfo to JSON. + * @function toJSON + * @memberof PeerRecord.AddressInfo + * @instance + * @returns {Object.} JSON object + */ + AddressInfo.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return AddressInfo; + })(); + + return PeerRecord; +})(); + +module.exports = $root; diff --git a/src/record/peer-record/peer-record.proto.js b/src/record/peer-record/peer-record.proto similarity index 52% rename from src/record/peer-record/peer-record.proto.js rename to src/record/peer-record/peer-record.proto index 0ebb3b90d0..6b740dc80f 100644 --- a/src/record/peer-record/peer-record.proto.js +++ b/src/record/peer-record/peer-record.proto @@ -1,14 +1,5 @@ -'use strict' +syntax = "proto3"; -const protons = require('protons') - -// PeerRecord messages contain information that is useful to share with other peers. -// Currently, a PeerRecord contains the public listen addresses for a peer, but this -// is expected to expand to include other information in the future. -// PeerRecords are designed to be serialized to bytes and placed inside of -// SignedEnvelopes before sharing with other peers. -/** @type {{PeerRecord: import('../../types').MessageProto}} */ -module.exports = protons(` message PeerRecord { // AddressInfo is a wrapper around a binary multiaddr. It is defined as a // separate message to allow us to add per-address metadata in the future. @@ -24,5 +15,4 @@ message PeerRecord { // addresses is a list of public listen addresses for the peer. repeated AddressInfo addresses = 3; -} -`) +} \ No newline at end of file diff --git a/src/registrar.js b/src/registrar.js index 367f110c80..2a2f9f4084 100644 --- a/src/registrar.js +++ b/src/registrar.js @@ -1,8 +1,8 @@ 'use strict' const debug = require('debug') -const log = Object.assign(debug('libp2p:peer-store'), { - error: debug('libp2p:peer-store:err') +const log = Object.assign(debug('libp2p:registrar'), { + error: debug('libp2p:registrar:err') }) const errcode = require('err-code') @@ -16,7 +16,11 @@ const Topology = require('libp2p-interfaces/src/topology') * @typedef {import('./peer-store')} PeerStore * @typedef {import('./connection-manager')} ConnectionManager * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection - * @typedef {import('libp2p-interfaces/src/topology')} Topology + * @typedef {import('./').HandlerProps} HandlerProps + */ + +/** + * */ /** @@ -38,20 +42,28 @@ class Registrar { /** * Map of topologies * - * @type {Map} + * @type {Map} */ this.topologies = new Map() + /** @type {(protocols: string[]|string, handler: (props: HandlerProps) => void) => void} */ + // @ts-ignore handle is not optional this._handle = undefined this._onDisconnect = this._onDisconnect.bind(this) this.connectionManager.on('peer:disconnect', this._onDisconnect) } + /** + * @returns {(protocols: string[]|string, handler: (props: HandlerProps) => void) => void} + */ get handle () { return this._handle } + /** + * @param {(protocols: string[]|string, handler: (props: HandlerProps) => void) => void} handle + */ set handle (handle) { this._handle = handle } @@ -103,12 +115,11 @@ class Registrar { * Remove a disconnected peer from the record * * @param {Connection} connection - * @param {Error} [error] * @returns {void} */ - _onDisconnect (connection, error) { + _onDisconnect (connection) { for (const [, topology] of this.topologies) { - topology.disconnect(connection.remotePeer, error) + topology.disconnect(connection.remotePeer) } } } diff --git a/src/transport-manager.js b/src/transport-manager.js index 7b41b87d39..1993edf80c 100644 --- a/src/transport-manager.js +++ b/src/transport-manager.js @@ -12,10 +12,10 @@ const errCode = require('err-code') const { updateSelfPeerRecord } = require('./record/utils') /** - * @typedef {import('multiaddr')} Multiaddr + * @typedef {import('multiaddr').Multiaddr} Multiaddr * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection - * @typedef {import('libp2p-interfaces/src/transport/types').TransportFactory} TransportFactory - * @typedef {import('libp2p-interfaces/src/transport/types').Transport} Transport + * @typedef {import('libp2p-interfaces/src/transport/types').TransportFactory} TransportFactory + * @typedef {import('libp2p-interfaces/src/transport/types').Transport} Transport * * @typedef {Object} TransportManagerProperties * @property {import('./')} libp2p @@ -121,6 +121,7 @@ class TransportManager { * @returns {Multiaddr[]} */ getAddrs () { + /** @type {Multiaddr[]} */ let addrs = [] for (const listeners of this._listeners.values()) { for (const listener of listeners) { diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index 16c00ef109..0000000000 --- a/src/types.ts +++ /dev/null @@ -1,103 +0,0 @@ - -// Insecure Message types -export enum KeyType { - RSA = 0, - Ed25519 = 1, - Secp256k1 = 2, - ECDSA = 3 -} - -// Protobufs -export type MessageProto = { - encode(value: any): Uint8Array - decode(bytes: Uint8Array): any -} - -export type SUCCESS = 100; -export type HOP_SRC_ADDR_TOO_LONG = 220; -export type HOP_DST_ADDR_TOO_LONG = 221; -export type HOP_SRC_MULTIADDR_INVALID = 250; -export type HOP_DST_MULTIADDR_INVALID = 251; -export type HOP_NO_CONN_TO_DST = 260; -export type HOP_CANT_DIAL_DST = 261; -export type HOP_CANT_OPEN_DST_STREAM = 262; -export type HOP_CANT_SPEAK_RELAY = 270; -export type HOP_CANT_RELAY_TO_SELF = 280; -export type STOP_SRC_ADDR_TOO_LONG = 320; -export type STOP_DST_ADDR_TOO_LONG = 321; -export type STOP_SRC_MULTIADDR_INVALID = 350; -export type STOP_DST_MULTIADDR_INVALID = 351; -export type STOP_RELAY_REFUSED = 390; -export type MALFORMED_MESSAGE = 400; - -export type CircuitStatus = SUCCESS | HOP_SRC_ADDR_TOO_LONG | HOP_DST_ADDR_TOO_LONG - | HOP_SRC_MULTIADDR_INVALID | HOP_DST_MULTIADDR_INVALID | HOP_NO_CONN_TO_DST - | HOP_CANT_DIAL_DST | HOP_CANT_OPEN_DST_STREAM | HOP_CANT_SPEAK_RELAY | HOP_CANT_RELAY_TO_SELF - | STOP_SRC_ADDR_TOO_LONG | STOP_DST_ADDR_TOO_LONG | STOP_SRC_MULTIADDR_INVALID - | STOP_DST_MULTIADDR_INVALID | STOP_RELAY_REFUSED | MALFORMED_MESSAGE - -export type HOP = 1; -export type STOP = 2; -export type STATUS = 3; -export type CAN_HOP = 4; - -export type CircuitType = HOP | STOP | STATUS | CAN_HOP - -export type CircuitPeer = { - id: Uint8Array - addrs: Uint8Array[] -} - -export type CircuitRequest = { - type: CircuitType - dstPeer: CircuitPeer - srcPeer: CircuitPeer -} - -export type CircuitMessageProto = { - encode(value: any): Uint8Array - decode(bytes: Uint8Array): any - Status: { - SUCCESS: SUCCESS, - HOP_SRC_ADDR_TOO_LONG: HOP_SRC_ADDR_TOO_LONG, - HOP_DST_ADDR_TOO_LONG: HOP_DST_ADDR_TOO_LONG, - HOP_SRC_MULTIADDR_INVALID: HOP_SRC_MULTIADDR_INVALID, - HOP_DST_MULTIADDR_INVALID: HOP_DST_MULTIADDR_INVALID, - HOP_NO_CONN_TO_DST: HOP_NO_CONN_TO_DST, - HOP_CANT_DIAL_DST: HOP_CANT_DIAL_DST, - HOP_CANT_OPEN_DST_STREAM: HOP_CANT_OPEN_DST_STREAM, - HOP_CANT_SPEAK_RELAY: HOP_CANT_SPEAK_RELAY, - HOP_CANT_RELAY_TO_SELF: HOP_CANT_RELAY_TO_SELF, - STOP_SRC_ADDR_TOO_LONG: STOP_SRC_ADDR_TOO_LONG, - STOP_DST_ADDR_TOO_LONG: STOP_DST_ADDR_TOO_LONG, - STOP_SRC_MULTIADDR_INVALID: STOP_SRC_MULTIADDR_INVALID, - STOP_DST_MULTIADDR_INVALID: STOP_DST_MULTIADDR_INVALID, - STOP_RELAY_REFUSED: STOP_RELAY_REFUSED, - MALFORMED_MESSAGE: MALFORMED_MESSAGE - }, - Type: { - HOP: HOP, - STOP: STOP, - STATUS: STATUS, - CAN_HOP: CAN_HOP - } -} - -export interface EventEmitterFactory { - new(): EventEmitter; -} - -export interface EventEmitter { - addListener(event: string | symbol, listener: (...args: any[]) => void); - on(event: string | symbol, listener: (...args: any[]) => void); - once(event: string | symbol, listener: (...args: any[]) => void); - removeListener(event: string | symbol, listener: (...args: any[]) => void); - off(event: string | symbol, listener: (...args: any[]) => void); - removeAllListeners(event?: string | symbol); - setMaxListeners(n: number); - getMaxListeners(): number; - listeners(event: string | symbol): Function[]; // eslint-disable-line @typescript-eslint/ban-types - rawListeners(event: string | symbol): Function[]; // eslint-disable-line @typescript-eslint/ban-types - emit(event: string | symbol, ...args: any[]): boolean; - listenerCount(event: string | symbol): number; -} diff --git a/src/upgrader.js b/src/upgrader.js index 7e7ec22a95..6f5f3f0169 100644 --- a/src/upgrader.js +++ b/src/upgrader.js @@ -5,10 +5,12 @@ const log = Object.assign(debug('libp2p:upgrader'), { error: debug('libp2p:upgrader:err') }) const errCode = require('err-code') +// @ts-ignore multistream-select does not export types const Multistream = require('multistream-select') const { Connection } = require('libp2p-interfaces/src/connection') const PeerId = require('peer-id') const { pipe } = require('it-pipe') +// @ts-ignore mutable-proxy does not export types const mutableProxy = require('mutable-proxy') const { codes } = require('./errors') @@ -19,7 +21,8 @@ const { codes } = require('./errors') * @typedef {import('libp2p-interfaces/src/stream-muxer/types').Muxer} Muxer * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream * @typedef {import('libp2p-interfaces/src/crypto/types').Crypto} Crypto - * @typedef {import('multiaddr')} Multiaddr + * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection + * @typedef {import('multiaddr').Multiaddr} Multiaddr */ /** @@ -36,8 +39,8 @@ class Upgrader { * @param {import('./metrics')} [options.metrics] * @param {Map} [options.cryptos] * @param {Map} [options.muxers] - * @param {(Connection) => void} options.onConnection - Called when a connection is upgraded - * @param {(Connection) => void} options.onConnectionEnd + * @param {(connection: Connection) => void} options.onConnection - Called when a connection is upgraded + * @param {(connection: Connection) => void} options.onConnectionEnd */ constructor ({ localPeer, @@ -51,6 +54,7 @@ class Upgrader { this.metrics = metrics this.cryptos = cryptos this.muxers = muxers + /** @type {import("./pnet") | null} */ this.protector = null this.protocols = new Map() this.onConnection = onConnection @@ -216,16 +220,18 @@ class Upgrader { Muxer, remotePeer }) { + /** @type {import("libp2p-interfaces/src/stream-muxer/types").Muxer} */ let muxer let newStream - // eslint-disable-next-line prefer-const - let connection + /** @type {Connection} */ + let connection // eslint-disable-line prefer-const if (Muxer) { // Create the muxer muxer = new Muxer({ // Run anytime a remote stream is created onStream: async muxedStream => { + if (!connection) return const mss = new Multistream.Listener(muxedStream) try { const { stream, protocol } = await mss.handle(Array.from(this.protocols.keys())) @@ -243,7 +249,7 @@ class Upgrader { } }) - newStream = async protocols => { + newStream = async (/** @type {string | string[]} */ protocols) => { log('%s: starting new stream on %s', direction, protocols) const muxedStream = muxer.newStream() const mss = new Multistream.Dialer(muxedStream) @@ -302,12 +308,12 @@ class Upgrader { encryption: cryptoProtocol }, newStream: newStream || errConnectionNotMultiplexed, - getStreams: () => muxer ? muxer.streams : errConnectionNotMultiplexed, - close: async (err) => { + getStreams: () => muxer ? muxer.streams : errConnectionNotMultiplexed(), + close: async (/** @type {Error | undefined} */ err) => { await maConn.close(err) // Ensure remaining streams are aborted if (muxer) { - muxer.streams.map(stream => stream.abort(err)) + muxer.streams.map(stream => stream.abort()) } } }) @@ -371,7 +377,7 @@ class Upgrader { * @private * @async * @param {PeerId} localPeer - The initiators PeerId - * @param {*} connection + * @param {MultiaddrConnection} connection * @param {PeerId} remotePeerId * @param {Map} cryptos * @returns {Promise} An encrypted connection, remote peer `PeerId` and the protocol of the `Crypto` used diff --git a/test/addresses/address-manager.spec.js b/test/addresses/address-manager.spec.js index c27ef2992d..1074700864 100644 --- a/test/addresses/address-manager.spec.js +++ b/test/addresses/address-manager.spec.js @@ -2,7 +2,7 @@ /* eslint-env mocha */ const { expect } = require('aegir/utils/chai') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const PeerId = require('peer-id') const AddressManager = require('../../src/address-manager') @@ -37,8 +37,8 @@ describe('Address Manager', () => { const listenMultiaddrs = am.getListenAddrs() expect(listenMultiaddrs.length).to.equal(2) - expect(listenMultiaddrs[0].equals(multiaddr(listenAddresses[0]))).to.equal(true) - expect(listenMultiaddrs[1].equals(multiaddr(listenAddresses[1]))).to.equal(true) + expect(listenMultiaddrs[0].equals(new Multiaddr(listenAddresses[0]))).to.equal(true) + expect(listenMultiaddrs[1].equals(new Multiaddr(listenAddresses[1]))).to.equal(true) }) it('should return announce multiaddrs on get', () => { @@ -52,7 +52,7 @@ describe('Address Manager', () => { const announceMultiaddrs = am.getAnnounceAddrs() expect(announceMultiaddrs.length).to.equal(1) - expect(announceMultiaddrs[0].equals(multiaddr(announceAddreses[0]))).to.equal(true) + expect(announceMultiaddrs[0].equals(new Multiaddr(announceAddreses[0]))).to.equal(true) }) it('should add observed addresses', () => { diff --git a/test/addresses/addresses.node.js b/test/addresses/addresses.node.js index 719e52721a..00d93ca13b 100644 --- a/test/addresses/addresses.node.js +++ b/test/addresses/addresses.node.js @@ -4,7 +4,7 @@ const { expect } = require('aegir/utils/chai') const sinon = require('sinon') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const isLoopback = require('libp2p-utils/src/multiaddr/is-loopback') const { AddressesOptions } = require('./utils') @@ -111,9 +111,9 @@ describe('libp2p.multiaddrs', () => { expect(libp2p.multiaddrs.length).to.equal(0) // Stub transportManager addresses to add a public address - const stubMa = multiaddr('/ip4/120.220.10.1/tcp/1000') + const stubMa = new Multiaddr('/ip4/120.220.10.1/tcp/1000') sinon.stub(libp2p.transportManager, 'getAddrs').returns([ - ...listenAddresses.map((a) => multiaddr(a)), + ...listenAddresses.map((a) => new Multiaddr(a)), stubMa ]) diff --git a/test/content-routing/content-routing.node.js b/test/content-routing/content-routing.node.js index 09a11f250f..ff2079a505 100644 --- a/test/content-routing/content-routing.node.js +++ b/test/content-routing/content-routing.node.js @@ -11,7 +11,7 @@ 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') +const { Multiaddr } = require('multiaddr') const drain = require('it-drain') const all = require('it-all') @@ -28,6 +28,8 @@ describe('content-routing', () => { }) }) + after(() => node.stop()) + it('.findProviders should return an error', async () => { try { for await (const _ of node.contentRouting.findProviders('a cid')) {} // eslint-disable-line @@ -277,7 +279,7 @@ describe('content-routing', () => { const result = { id: providerPeerId, multiaddrs: [ - multiaddr('/ip4/123.123.123.123/tcp/49320') + new Multiaddr('/ip4/123.123.123.123/tcp/49320') ] } @@ -301,7 +303,7 @@ describe('content-routing', () => { const result = { id: providerPeerId, multiaddrs: [ - multiaddr('/ip4/123.123.123.123/tcp/49320') + new Multiaddr('/ip4/123.123.123.123/tcp/49320') ] } @@ -327,7 +329,7 @@ describe('content-routing', () => { const result = { id: providerPeerId, multiaddrs: [ - multiaddr('/ip4/123.123.123.123/tcp/49320') + new Multiaddr('/ip4/123.123.123.123/tcp/49320') ] } @@ -348,13 +350,13 @@ describe('content-routing', () => { const result1 = { id: providerPeerId, multiaddrs: [ - multiaddr('/ip4/123.123.123.123/tcp/49320') + new Multiaddr('/ip4/123.123.123.123/tcp/49320') ] } const result2 = { id: providerPeerId, multiaddrs: [ - multiaddr('/ip4/213.213.213.213/tcp/2344') + new Multiaddr('/ip4/213.213.213.213/tcp/2344') ] } diff --git a/test/content-routing/dht/operation.node.js b/test/content-routing/dht/operation.node.js index 6f8a4a0b52..946d92748f 100644 --- a/test/content-routing/dht/operation.node.js +++ b/test/content-routing/dht/operation.node.js @@ -3,7 +3,7 @@ const { expect } = require('aegir/utils/chai') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const pWaitFor = require('p-wait-for') const mergeOptions = require('merge-options') const uint8ArrayFromString = require('uint8arrays/from-string') @@ -12,8 +12,8 @@ const { create } = require('../../../src') const { subsystemOptions, subsystemMulticodecs } = require('./utils') const peerUtils = require('../../utils/creators/peer') -const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/8000') -const remoteListenAddr = multiaddr('/ip4/127.0.0.1/tcp/8001') +const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/8000') +const remoteListenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/8001') describe('DHT subsystem operates correctly', () => { let peerId, remotePeerId diff --git a/test/core/ping.node.js b/test/core/ping.node.js index 510e5e3cb7..4bbd4f76cc 100644 --- a/test/core/ping.node.js +++ b/test/core/ping.node.js @@ -23,6 +23,8 @@ describe('ping', () => { nodes[1].peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs) }) + afterEach(() => Promise.all(nodes.map(n => n.stop()))) + 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) @@ -56,6 +58,7 @@ describe('ping', () => { if (firstInvocation) { firstInvocation = false + // eslint-disable-next-line no-unreachable-loop for await (const data of stream) { return { value: data, diff --git a/test/dialing/direct.node.js b/test/dialing/direct.node.js index 33a73d0fbe..1f8a5dfee7 100644 --- a/test/dialing/direct.node.js +++ b/test/dialing/direct.node.js @@ -6,7 +6,7 @@ const sinon = require('sinon') const Transport = require('libp2p-tcp') const Muxer = require('libp2p-mplex') const { NOISE: Crypto } = require('libp2p-noise') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const PeerId = require('peer-id') const delay = require('delay') const pDefer = require('p-defer') @@ -33,8 +33,8 @@ const createMockConnection = require('../utils/mockConnection') const Peers = require('../fixtures/peers') const { createPeerId } = require('../utils/creators/peer') -const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0') -const unsupportedAddr = multiaddr('/ip4/127.0.0.1/tcp/9999/ws/p2p/QmckxVrJw1Yo8LqvmDJNUmdAsKtSbiKWmrXJFyKmUraBoN') +const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') +const unsupportedAddr = new Multiaddr('/ip4/127.0.0.1/tcp/9999/ws/p2p/QmckxVrJw1Yo8LqvmDJNUmdAsKtSbiKWmrXJFyKmUraBoN') describe('Dialing (direct, TCP)', () => { let remoteTM @@ -147,7 +147,7 @@ describe('Dialing (direct, TCP)', () => { const dialer = new Dialer({ transportManager: localTM, peerStore, - timeout: 50 + dialTimeout: 50 }) sinon.stub(localTM, 'dial').callsFake(async (addr, options) => { expect(options.signal).to.exist() @@ -165,13 +165,13 @@ describe('Dialing (direct, TCP)', () => { it('should dial to the max concurrency', async () => { const addrs = [ - multiaddr('/ip4/0.0.0.0/tcp/8000'), - multiaddr('/ip4/0.0.0.0/tcp/8001'), - multiaddr('/ip4/0.0.0.0/tcp/8002') + new Multiaddr('/ip4/0.0.0.0/tcp/8000'), + new Multiaddr('/ip4/0.0.0.0/tcp/8001'), + new Multiaddr('/ip4/0.0.0.0/tcp/8002') ] const dialer = new Dialer({ transportManager: localTM, - concurrency: 2, + maxParallelDials: 2, peerStore: { addressBook: { add: () => {}, diff --git a/test/dialing/direct.spec.js b/test/dialing/direct.spec.js index 782d947164..a074e02f97 100644 --- a/test/dialing/direct.spec.js +++ b/test/dialing/direct.spec.js @@ -10,7 +10,7 @@ const Transport = require('libp2p-websockets') const filters = require('libp2p-websockets/src/filters') const Muxer = require('libp2p-mplex') const { NOISE: Crypto } = require('libp2p-noise') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const AggregateError = require('aggregate-error') const { AbortError } = require('libp2p-interfaces/src/transport/errors') @@ -26,7 +26,7 @@ const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser') const mockUpgrader = require('../utils/mockUpgrader') const createMockConnection = require('../utils/mockConnection') const { createPeerId } = require('../utils/creators/peer') -const unsupportedAddr = multiaddr('/ip4/127.0.0.1/tcp/9999/ws/p2p/QmckxVrJw1Yo8LqvmDJNUmdAsKtSbiKWmrXJFyKmUraBoN') +const unsupportedAddr = new Multiaddr('/ip4/127.0.0.1/tcp/9999/ws/p2p/QmckxVrJw1Yo8LqvmDJNUmdAsKtSbiKWmrXJFyKmUraBoN') const remoteAddr = MULTIADDRS_WEBSOCKETS[0] describe('Dialing (direct, WebSockets)', () => { @@ -52,7 +52,7 @@ describe('Dialing (direct, WebSockets)', () => { it('should have appropriate defaults', () => { const dialer = new Dialer({ transportManager: localTM, peerStore }) - expect(dialer.concurrency).to.equal(Constants.MAX_PARALLEL_DIALS) + expect(dialer.maxParallelDials).to.equal(Constants.MAX_PARALLEL_DIALS) expect(dialer.timeout).to.equal(Constants.DIAL_TIMEOUT) }) @@ -155,7 +155,7 @@ describe('Dialing (direct, WebSockets)', () => { it('should abort dials on queue task timeout', async () => { const dialer = new Dialer({ transportManager: localTM, - timeout: 50, + dialTimeout: 50, peerStore: { addressBook: { add: () => {}, @@ -179,9 +179,9 @@ describe('Dialing (direct, WebSockets)', () => { it('should sort addresses on dial', async () => { const peerMultiaddrs = [ - multiaddr('/ip4/127.0.0.1/tcp/15001/ws'), - multiaddr('/ip4/20.0.0.1/tcp/15001/ws'), - multiaddr('/ip4/30.0.0.1/tcp/15001/ws') + new Multiaddr('/ip4/127.0.0.1/tcp/15001/ws'), + new Multiaddr('/ip4/20.0.0.1/tcp/15001/ws'), + new Multiaddr('/ip4/30.0.0.1/tcp/15001/ws') ] sinon.spy(addressSort, 'publicAddressesFirst') @@ -190,7 +190,7 @@ describe('Dialing (direct, WebSockets)', () => { const dialer = new Dialer({ transportManager: localTM, addressSorter: addressSort.publicAddressesFirst, - concurrency: 3, + maxParallelDials: 3, peerStore }) @@ -211,7 +211,7 @@ describe('Dialing (direct, WebSockets)', () => { it('should dial to the max concurrency', async () => { const dialer = new Dialer({ transportManager: localTM, - concurrency: 2, + maxParallelDials: 2, peerStore: { addressBook: { set: () => {}, @@ -249,7 +249,7 @@ describe('Dialing (direct, WebSockets)', () => { it('.destroy should abort pending dials', async () => { const dialer = new Dialer({ transportManager: localTM, - concurrency: 2, + maxParallelDials: 2, peerStore: { addressBook: { set: () => {}, @@ -318,8 +318,8 @@ describe('Dialing (direct, WebSockets)', () => { }) expect(libp2p.dialer).to.exist() - expect(libp2p.dialer.concurrency).to.equal(Constants.MAX_PARALLEL_DIALS) - expect(libp2p.dialer.perPeerLimit).to.equal(Constants.MAX_PER_PEER_DIALS) + expect(libp2p.dialer.maxParallelDials).to.equal(Constants.MAX_PARALLEL_DIALS) + expect(libp2p.dialer.maxDialsPerPeer).to.equal(Constants.MAX_PER_PEER_DIALS) expect(libp2p.dialer.timeout).to.equal(Constants.DIAL_TIMEOUT) // Ensure the dialer also has the transport manager expect(libp2p.transportManager).to.equal(libp2p.dialer.transportManager) @@ -349,8 +349,8 @@ describe('Dialing (direct, WebSockets)', () => { libp2p = await Libp2p.create(config) expect(libp2p.dialer).to.exist() - expect(libp2p.dialer.concurrency).to.equal(config.dialer.maxParallelDials) - expect(libp2p.dialer.perPeerLimit).to.equal(config.dialer.maxDialsPerPeer) + expect(libp2p.dialer.maxParallelDials).to.equal(config.dialer.maxParallelDials) + expect(libp2p.dialer.maxDialsPerPeer).to.equal(config.dialer.maxDialsPerPeer) expect(libp2p.dialer.timeout).to.equal(config.dialer.dialTimeout) }) diff --git a/test/dialing/resolver.spec.js b/test/dialing/resolver.spec.js index 094f657a6d..f5088ae982 100644 --- a/test/dialing/resolver.spec.js +++ b/test/dialing/resolver.spec.js @@ -4,8 +4,8 @@ const { expect } = require('aegir/utils/chai') const sinon = require('sinon') -const multiaddr = require('multiaddr') -const { Resolver } = require('multiaddr/src/resolvers/dns') +const { Multiaddr } = require('multiaddr') +const Resolver = require('multiaddr/src/resolvers/dns') const { codes: ErrorCodes } = require('../../src/errors') @@ -39,7 +39,7 @@ describe('Dialing (resolvable addresses)', () => { config: { ...baseOptions, addresses: { - listen: [multiaddr(`${relayAddr}/p2p-circuit`)] + listen: [new Multiaddr(`${relayAddr}/p2p-circuit`)] }, config: { ...baseOptions.config, @@ -60,8 +60,8 @@ describe('Dialing (resolvable addresses)', () => { 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)) + const dialAddr = new Multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId}`) + const relayedAddrFetched = new Multiaddr(relayedAddr(remoteId)) // Transport spy const transport = libp2p.transportManager._transports.get('Circuit') @@ -82,8 +82,8 @@ describe('Dialing (resolvable addresses)', () => { 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)) + const dialAddr = new Multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId}`) + const relayedAddrFetched = new Multiaddr(relayedAddr(remoteId)) // Transport spy const transport = libp2p.transportManager._transports.get('Circuit') @@ -114,10 +114,10 @@ describe('Dialing (resolvable addresses)', () => { // 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}`) + const dialAddr = new Multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId}`) // Stub resolver - const dnsMa = multiaddr(`/dns4/ams-1.remote.libp2p.io/tcp/443/wss/p2p/${remoteId}`) + const dnsMa = new 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}`] @@ -135,8 +135,8 @@ describe('Dialing (resolvable addresses)', () => { 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)) + const dialAddr = new Multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId}`) + const relayedAddrFetched = new Multiaddr(relayedAddr(remoteId)) // Transport spy const transport = libp2p.transportManager._transports.get('Circuit') @@ -159,7 +159,7 @@ describe('Dialing (resolvable addresses)', () => { 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}`) + const dialAddr = new Multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId}`) // Stub resolver const stubResolve = sinon.stub(Resolver.prototype, 'resolveTxt') diff --git a/test/fixtures/browser.js b/test/fixtures/browser.js index f5f33259dc..8bcec6a116 100644 --- a/test/fixtures/browser.js +++ b/test/fixtures/browser.js @@ -1,7 +1,7 @@ 'use strict' -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') module.exports.MULTIADDRS_WEBSOCKETS = [ - multiaddr('/ip4/127.0.0.1/tcp/15001/ws/p2p/12D3KooWHFKTMzwerBtsVmtz4ZZEQy2heafxzWw6wNn5PPYkBxJ5') + new Multiaddr('/ip4/127.0.0.1/tcp/15001/ws/p2p/12D3KooWHFKTMzwerBtsVmtz4ZZEQy2heafxzWw6wNn5PPYkBxJ5') ] diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js index d7b0d8b2bf..65d2a77f20 100644 --- a/test/identify/index.spec.js +++ b/test/identify/index.spec.js @@ -7,7 +7,7 @@ const sinon = require('sinon') const { EventEmitter } = require('events') const PeerId = require('peer-id') const duplexPair = require('it-pair/duplex') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const pWaitFor = require('p-wait-for') const unit8ArrayToString = require('uint8arrays/to-string') @@ -25,7 +25,7 @@ const AddressManager = require('../../src/address-manager') const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser') const remoteAddr = MULTIADDRS_WEBSOCKETS[0] -const listenMaddrs = [multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] +const listenMaddrs = [new Multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] describe('Identify', () => { let localPeer, localPeerStore, localAddressManager @@ -74,7 +74,7 @@ describe('Identify', () => { } }) - const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234') + const observedAddr = new Multiaddr('/ip4/127.0.0.1/tcp/1234') const localConnectionMock = { newStream: () => {}, remotePeer } const remoteConnectionMock = { remoteAddr: observedAddr } @@ -110,6 +110,7 @@ describe('Identify', () => { // LEGACY it('should be able to identify another peer with no certified peer records support', async () => { + const agentVersion = `js-libp2p/${pkg.version}` const localIdentify = new IdentifyService({ libp2p: { peerId: localPeer, @@ -118,7 +119,7 @@ describe('Identify', () => { peerStore: localPeerStore, multiaddrs: listenMaddrs, isStarted: () => true, - _options: { host: {} } + _options: { host: { agentVersion } } } }) @@ -130,11 +131,11 @@ describe('Identify', () => { peerStore: remotePeerStore, multiaddrs: listenMaddrs, isStarted: () => true, - _options: { host: {} } + _options: { host: { agentVersion } } } }) - const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234') + const observedAddr = new Multiaddr('/ip4/127.0.0.1/tcp/1234') const localConnectionMock = { newStream: () => {}, remotePeer } const remoteConnectionMock = { remoteAddr: observedAddr } @@ -162,7 +163,7 @@ describe('Identify', () => { const metadataArgs = localIdentify.peerStore.metadataBook.set.firstCall.args expect(metadataArgs[0].id.bytes).to.equal(remotePeer.bytes) expect(metadataArgs[1]).to.equal('AgentVersion') - expect(unit8ArrayToString(metadataArgs[2])).to.equal(`js-libp2p/${pkg.version}`) + expect(unit8ArrayToString(metadataArgs[2])).to.equal(agentVersion) // Validate the remote peer gets updated in the peer store const call = localIdentify.peerStore.addressBook.set.firstCall @@ -192,7 +193,7 @@ describe('Identify', () => { } }) - const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234') + const observedAddr = new Multiaddr('/ip4/127.0.0.1/tcp/1234') const localConnectionMock = { newStream: () => {}, remotePeer: localPeer } const remoteConnectionMock = { remoteAddr: observedAddr } @@ -500,7 +501,7 @@ describe('Identify', () => { 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')]) + libp2p.peerStore.addressBook.add(libp2p.peerId, [new 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) diff --git a/test/keychain/keychain.spec.js b/test/keychain/keychain.spec.js index 1b74c91eb2..55e1511097 100644 --- a/test/keychain/keychain.spec.js +++ b/test/keychain/keychain.spec.js @@ -27,8 +27,8 @@ describe('keychain', () => { datastore1 = new MemoryDatastore() datastore2 = new MemoryDatastore() - ks = new Keychain(datastore2, { passPhrase: passPhrase }) - emptyKeystore = new Keychain(datastore1, { passPhrase: passPhrase }) + ks = new Keychain(datastore2, { pass: passPhrase }) + emptyKeystore = new Keychain(datastore1, { pass: passPhrase }) await datastore1.open() await datastore2.open() @@ -44,11 +44,11 @@ describe('keychain', () => { }) it('needs a NIST SP 800-132 non-weak pass phrase', () => { - expect(() => new Keychain(datastore2, { passPhrase: '< 20 character' })).to.throw() + expect(() => new Keychain(datastore2, { pass: '< 20 character' })).to.throw() }) it('needs a store to persist a key', () => { - expect(() => new Keychain(null, { passPhrase: passPhrase })).to.throw() + expect(() => new Keychain(null, { pass: passPhrase })).to.throw() }) it('has default options', () => { @@ -56,12 +56,12 @@ describe('keychain', () => { }) it('supports supported hashing alorithms', () => { - const ok = new Keychain(datastore2, { passPhrase: passPhrase, dek: { hash: 'sha2-256' } }) + const ok = new Keychain(datastore2, { pass: 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() + expect(() => new Keychain(datastore2, { pass: passPhrase, dek: { hash: 'my-hash' } })).to.throw() }) it('can list keys without a password', async () => { @@ -72,7 +72,7 @@ describe('keychain', () => { 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 keychainWithPassword = new Keychain(datastore2, { pass: `hello-${Date.now()}-${Date.now()}` }) const name = `key-${Math.random()}` const { id } = await keychainWithPassword.createKey(name, 'ed25519') @@ -82,7 +82,7 @@ describe('keychain', () => { 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 keychainWithPassword = new Keychain(datastore2, { pass: `hello-${Date.now()}-${Date.now()}` }) const name = `key-${Math.random()}` expect(await keychainWithPassword.createKey(name, 'ed25519')).to.have.property('name', name) @@ -99,7 +99,7 @@ describe('keychain', () => { it('can generate options', () => { const options = Keychain.generateOptions() - options.passPhrase = passPhrase + options.pass = passPhrase const chain = new Keychain(datastore2, options) expect(chain).to.exist() }) diff --git a/test/nat-manager/nat-manager.node.js b/test/nat-manager/nat-manager.node.js index 55acb0babd..5f5562e0b0 100644 --- a/test/nat-manager/nat-manager.node.js +++ b/test/nat-manager/nat-manager.node.js @@ -71,7 +71,7 @@ describe('Nat Manager (TCP)', () => { } } - afterEach(() => Promise.all(teardown)) + afterEach(() => Promise.all(teardown.map(t => t()))) it('should map TCP connections to external ports', async () => { const { diff --git a/test/peer-discovery/index.node.js b/test/peer-discovery/index.node.js index 63976cb83c..3f6b3f33ea 100644 --- a/test/peer-discovery/index.node.js +++ b/test/peer-discovery/index.node.js @@ -10,14 +10,14 @@ const Bootstrap = require('libp2p-bootstrap') const crypto = require('libp2p-crypto') const KadDht = require('libp2p-kad-dht') const MulticastDNS = require('libp2p-mdns') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const uint8ArrayToString = require('uint8arrays/to-string') const Libp2p = require('../../src') const baseOptions = require('../utils/base-options') const { createPeerId } = require('../utils/creators/peer') -const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0') +const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') describe('peer discovery scenarios', () => { let peerId, remotePeerId1, remotePeerId2 @@ -30,6 +30,7 @@ describe('peer discovery scenarios', () => { afterEach(async () => { libp2p && await libp2p.stop() }) + it('should ignore self on discovery', async () => { libp2p = new Libp2p(mergeOptions(baseOptions, { peerId, diff --git a/test/peer-discovery/index.spec.js b/test/peer-discovery/index.spec.js index 2fd037a0ef..72248621b0 100644 --- a/test/peer-discovery/index.spec.js +++ b/test/peer-discovery/index.spec.js @@ -7,7 +7,7 @@ const sinon = require('sinon') const defer = require('p-defer') const mergeOptions = require('merge-options') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const WebRTCStar = require('libp2p-webrtc-star') const Libp2p = require('../../src') @@ -38,7 +38,7 @@ describe('peer discovery', () => { } }) - libp2p.peerStore.addressBook.set(remotePeerId, [multiaddr('/ip4/165.1.1.1/tcp/80')]) + libp2p.peerStore.addressBook.set(remotePeerId, [new Multiaddr('/ip4/165.1.1.1/tcp/80')]) const deferred = defer() sinon.stub(libp2p.dialer, 'connectToPeer').callsFake((remotePeerId) => { @@ -89,6 +89,10 @@ describe('peer discovery', () => { [peerId] = await createPeerId() }) + afterEach(async () => { + libp2p && await libp2p.stop() + }) + it('should add discovery module if present in transports and enabled', async () => { libp2p = new Libp2p(mergeOptions(baseOptions, { peerId, diff --git a/test/peer-routing/peer-routing.node.js b/test/peer-routing/peer-routing.node.js index 2badd3597a..5413c3f189 100644 --- a/test/peer-routing/peer-routing.node.js +++ b/test/peer-routing/peer-routing.node.js @@ -15,7 +15,7 @@ const all = require('it-all') const ipfsHttpClient = require('ipfs-http-client') const DelegatedPeerRouter = require('libp2p-delegated-peer-routing') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const PeerId = require('peer-id') const peerUtils = require('../utils/creators/peer') @@ -31,6 +31,8 @@ describe('peer-routing', () => { }) }) + after(() => node.stop()) + it('.findPeer should return an error', async () => { await expect(node.peerRouting.findPeer('a cid')) .to.eventually.be.rejected() @@ -360,7 +362,7 @@ describe('peer-routing', () => { const results = { id: remotePeerId, multiaddrs: [ - multiaddr('/ip4/123.123.123.123/tcp/38982') + new Multiaddr('/ip4/123.123.123.123/tcp/38982') ] } @@ -400,7 +402,7 @@ describe('peer-routing', () => { const result = { id: remotePeerId, multiaddrs: [ - multiaddr('/ip4/123.123.123.123/tcp/38982') + new Multiaddr('/ip4/123.123.123.123/tcp/38982') ] } @@ -422,7 +424,7 @@ describe('peer-routing', () => { const results = [{ id: remotePeerId, multiaddrs: [ - multiaddr('/ip4/123.123.123.123/tcp/38982') + new Multiaddr('/ip4/123.123.123.123/tcp/38982') ] }] @@ -456,8 +458,8 @@ describe('peer-routing', () => { it('should be enabled and start by default', async () => { const results = [ - { id: peerIds[0], multiaddrs: [multiaddr('/ip4/30.0.0.1/tcp/2000')] }, - { id: peerIds[1], multiaddrs: [multiaddr('/ip4/32.0.0.1/tcp/2000')] } + { id: peerIds[0], multiaddrs: [new Multiaddr('/ip4/30.0.0.1/tcp/2000')] }, + { id: peerIds[1], multiaddrs: [new Multiaddr('/ip4/32.0.0.1/tcp/2000')] } ] ;[node] = await peerUtils.createPeer({ @@ -533,7 +535,7 @@ describe('peer-routing', () => { }) sinon.stub(node._dht, 'getClosestPeers').callsFake(function * () { - yield { id: peerIds[0], multiaddrs: [multiaddr('/ip4/30.0.0.1/tcp/2000')] } + yield { id: peerIds[0], multiaddrs: [new Multiaddr('/ip4/30.0.0.1/tcp/2000')] } }) await node.start() diff --git a/test/peer-store/address-book.spec.js b/test/peer-store/address-book.spec.js index 0adae21d61..48eb7180bc 100644 --- a/test/peer-store/address-book.spec.js +++ b/test/peer-store/address-book.spec.js @@ -4,7 +4,7 @@ const { expect } = require('aegir/utils/chai') const { Buffer } = require('buffer') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const arrayEquals = require('libp2p-utils/src/array-equals') const addressSort = require('libp2p-utils/src/address-sort') const PeerId = require('peer-id') @@ -19,9 +19,9 @@ const { codes: { ERR_INVALID_PARAMETERS } } = require('../../src/errors') -const addr1 = multiaddr('/ip4/127.0.0.1/tcp/8000') -const addr2 = multiaddr('/ip4/20.0.0.1/tcp/8001') -const addr3 = multiaddr('/ip4/127.0.0.1/tcp/8002') +const addr1 = new Multiaddr('/ip4/127.0.0.1/tcp/8000') +const addr2 = new Multiaddr('/ip4/20.0.0.1/tcp/8001') +const addr3 = new Multiaddr('/ip4/127.0.0.1/tcp/8002') describe('addressBook', () => { let peerId diff --git a/test/peer-store/peer-store.node.js b/test/peer-store/peer-store.node.js index 8c322a2cce..794de82c2f 100644 --- a/test/peer-store/peer-store.node.js +++ b/test/peer-store/peer-store.node.js @@ -21,6 +21,8 @@ describe('libp2p.peerStore', () => { }) }) + afterEach(() => Promise.all([libp2p, remoteLibp2p].map(l => l.stop()))) + it('adds peer address to AddressBook and keys to the keybook when establishing connection', async () => { const remoteIdStr = remoteLibp2p.peerId.toB58String() diff --git a/test/peer-store/peer-store.spec.js b/test/peer-store/peer-store.spec.js index 39c2c7187d..c316240bdd 100644 --- a/test/peer-store/peer-store.spec.js +++ b/test/peer-store/peer-store.spec.js @@ -4,15 +4,15 @@ const { expect } = require('aegir/utils/chai') const PeerStore = require('../../src/peer-store') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const uint8ArrayFromString = require('uint8arrays/from-string') const peerUtils = require('../utils/creators/peer') -const addr1 = multiaddr('/ip4/127.0.0.1/tcp/8000') -const addr2 = multiaddr('/ip4/127.0.0.1/tcp/8001') -const addr3 = multiaddr('/ip4/127.0.0.1/tcp/8002') -const addr4 = multiaddr('/ip4/127.0.0.1/tcp/8003') +const addr1 = new Multiaddr('/ip4/127.0.0.1/tcp/8000') +const addr2 = new Multiaddr('/ip4/127.0.0.1/tcp/8001') +const addr3 = new Multiaddr('/ip4/127.0.0.1/tcp/8002') +const addr4 = new Multiaddr('/ip4/127.0.0.1/tcp/8003') const proto1 = '/protocol1' const proto2 = '/protocol2' diff --git a/test/peer-store/persisted-peer-store.spec.js b/test/peer-store/persisted-peer-store.spec.js index e1d7047629..9c669efd52 100644 --- a/test/peer-store/persisted-peer-store.spec.js +++ b/test/peer-store/persisted-peer-store.spec.js @@ -8,7 +8,7 @@ const Envelope = require('../../src/record/envelope') const PeerRecord = require('../../src/record/peer-record') const PeerStore = require('../../src/peer-store/persistent') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const { MemoryDatastore } = require('interface-datastore') const uint8ArrayFromString = require('uint8arrays/from-string') @@ -65,7 +65,7 @@ describe('Persisted PeerStore', () => { it('should store peerStore content on datastore after peer marked as dirty (threshold 1)', async () => { const [peer] = await peerUtils.createPeerId({ number: 2 }) - const multiaddrs = [multiaddr('/ip4/156.10.1.22/tcp/1000')] + const multiaddrs = [new Multiaddr('/ip4/156.10.1.22/tcp/1000')] const protocols = ['/ping/1.0.0'] const spyDirty = sinon.spy(peerStore, '_addDirtyPeer') const spyDs = sinon.spy(datastore, 'batch') @@ -115,8 +115,8 @@ describe('Persisted PeerStore', () => { 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') + new Multiaddr('/ip4/156.10.1.22/tcp/1000'), + new Multiaddr('/ip4/156.10.1.23/tcp/1000') ] const protocols = ['/ping/1.0.0'] @@ -173,7 +173,7 @@ describe('Persisted PeerStore', () => { it('should delete content from the datastore on delete', async () => { const [peer] = await peerUtils.createPeerId() - const multiaddrs = [multiaddr('/ip4/156.10.1.22/tcp/1000')] + const multiaddrs = [new Multiaddr('/ip4/156.10.1.22/tcp/1000')] const protocols = ['/ping/1.0.0'] const commitSpy = sinon.spy(peerStore, '_commitData') @@ -221,7 +221,7 @@ describe('Persisted PeerStore', () => { it('should store certified peer records after peer marked as dirty (threshold 1)', async () => { const [peerId] = await peerUtils.createPeerId() - const multiaddrs = [multiaddr('/ip4/156.10.1.22/tcp/1000')] + const multiaddrs = [new Multiaddr('/ip4/156.10.1.22/tcp/1000')] const spyDirty = sinon.spy(peerStore, '_addDirtyPeer') const spyDs = sinon.spy(datastore, 'batch') const commitSpy = sinon.spy(peerStore, '_commitData') @@ -266,8 +266,8 @@ describe('Persisted PeerStore', () => { 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') + new Multiaddr('/ip4/156.10.1.22/tcp/1000'), + new Multiaddr('/ip4/156.10.1.23/tcp/1000') ] const peerRecord0 = new PeerRecord({ peerId: peers[0], @@ -332,7 +332,7 @@ describe('Persisted PeerStore', () => { it('should delete certified peer records from the datastore on delete', async () => { const [peer] = await peerUtils.createPeerId() - const multiaddrs = [multiaddr('/ip4/156.10.1.22/tcp/1000')] + const multiaddrs = [new Multiaddr('/ip4/156.10.1.22/tcp/1000')] const commitSpy = sinon.spy(peerStore, '_commitData') await peerStore.start() @@ -395,7 +395,7 @@ describe('Persisted PeerStore', () => { const peers = await peerUtils.createPeerId({ number: 2 }) - const multiaddrs = [multiaddr('/ip4/156.10.1.22/tcp/1000')] + const multiaddrs = [new Multiaddr('/ip4/156.10.1.22/tcp/1000')] const protocols = ['/ping/1.0.0'] await peerStore.start() @@ -540,8 +540,8 @@ describe('libp2p.peerStore (Persisted)', () => { const commitSpy = sinon.spy(libp2p.peerStore, '_commitData') const peers = await peerUtils.createPeerId({ number: 3 }) const multiaddrs = [ - multiaddr('/ip4/156.10.1.22/tcp/1000'), - multiaddr('/ip4/156.10.1.23/tcp/1000') + new Multiaddr('/ip4/156.10.1.22/tcp/1000'), + new Multiaddr('/ip4/156.10.1.23/tcp/1000') ] const protocols = ['/ping/1.0.0'] @@ -572,6 +572,11 @@ describe('libp2p.peerStore (Persisted)', () => { datastore: memoryDatastore, peerStore: { persistence: true + }, + config: { + peerDiscovery: { + autoDial: false + } } } }) diff --git a/test/pubsub/configuration.node.js b/test/pubsub/configuration.node.js index 12b0b584a6..998b886ee5 100644 --- a/test/pubsub/configuration.node.js +++ b/test/pubsub/configuration.node.js @@ -3,13 +3,13 @@ const { expect } = require('aegir/utils/chai') const mergeOptions = require('merge-options') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const { create } = require('../../src') const { baseOptions, subsystemOptions } = require('./utils') const peerUtils = require('../utils/creators/peer') -const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0') +const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') describe('Pubsub subsystem is configurable', () => { let libp2p diff --git a/test/pubsub/implementations.node.js b/test/pubsub/implementations.node.js index 3243d94b94..165df61bc1 100644 --- a/test/pubsub/implementations.node.js +++ b/test/pubsub/implementations.node.js @@ -12,14 +12,14 @@ const { multicodec: floodsubMulticodec } = require('libp2p-floodsub') const { multicodec: gossipsubMulticodec } = require('libp2p-gossipsub') const uint8ArrayToString = require('uint8arrays/to-string') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const { create } = require('../../src') const { baseOptions } = require('./utils') const peerUtils = require('../utils/creators/peer') -const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0') -const remoteListenAddr = multiaddr('/ip4/127.0.0.1/tcp/0') +const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') +const remoteListenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') describe('Pubsub subsystem is able to use different implementations', () => { let peerId, remotePeerId diff --git a/test/pubsub/operation.node.js b/test/pubsub/operation.node.js index 2ebe39dae4..2103438373 100644 --- a/test/pubsub/operation.node.js +++ b/test/pubsub/operation.node.js @@ -7,15 +7,15 @@ const sinon = require('sinon') const pWaitFor = require('p-wait-for') const pDefer = require('p-defer') const mergeOptions = require('merge-options') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const uint8ArrayToString = require('uint8arrays/to-string') const { create } = require('../../src') const { subsystemOptions, subsystemMulticodecs } = require('./utils') const peerUtils = require('../utils/creators/peer') -const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0') -const remoteListenAddr = multiaddr('/ip4/127.0.0.1/tcp/0') +const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') +const remoteListenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') describe('Pubsub subsystem operates correctly', () => { let peerId, remotePeerId diff --git a/test/record/peer-record.spec.js b/test/record/peer-record.spec.js index e8b8ed64cd..7865e2dfad 100644 --- a/test/record/peer-record.spec.js +++ b/test/record/peer-record.spec.js @@ -4,7 +4,7 @@ const { expect } = require('aegir/utils/chai') const tests = require('libp2p-interfaces/src/record/tests') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const PeerId = require('peer-id') const Envelope = require('../../src/record/envelope') @@ -60,7 +60,7 @@ describe('PeerRecord', () => { it('creates a peer record with provided data', () => { const multiaddrs = [ - multiaddr('/ip4/127.0.0.1/tcp/2000') + new Multiaddr('/ip4/127.0.0.1/tcp/2000') ] const seqNumber = Date.now() const peerRecord = new PeerRecord({ peerId, multiaddrs, seqNumber }) @@ -75,7 +75,7 @@ describe('PeerRecord', () => { it('marshals and unmarshals a peer record', () => { const multiaddrs = [ - multiaddr('/ip4/127.0.0.1/tcp/2000') + new Multiaddr('/ip4/127.0.0.1/tcp/2000') ] const seqNumber = Date.now() const peerRecord = new PeerRecord({ peerId, multiaddrs, seqNumber }) @@ -115,12 +115,12 @@ describe('PeerRecord', () => { it('equals returns false if the peer record has a different multiaddrs', () => { const multiaddrs = [ - multiaddr('/ip4/127.0.0.1/tcp/2000') + new Multiaddr('/ip4/127.0.0.1/tcp/2000') ] const peerRecord0 = new PeerRecord({ peerId, multiaddrs }) const multiaddrs1 = [ - multiaddr('/ip4/127.0.0.1/tcp/2001') + new Multiaddr('/ip4/127.0.0.1/tcp/2001') ] const peerRecord1 = new PeerRecord({ peerId, multiaddrs: multiaddrs1 }) @@ -136,7 +136,7 @@ describe('PeerRecord inside Envelope', () => { before(async () => { [peerId] = await peerUtils.createPeerId() const multiaddrs = [ - multiaddr('/ip4/127.0.0.1/tcp/2000') + new Multiaddr('/ip4/127.0.0.1/tcp/2000') ] const seqNumber = Date.now() peerRecord = new PeerRecord({ peerId, multiaddrs, seqNumber }) diff --git a/test/relay/auto-relay.node.js b/test/relay/auto-relay.node.js index 2411f48a57..a254b0475d 100644 --- a/test/relay/auto-relay.node.js +++ b/test/relay/auto-relay.node.js @@ -9,7 +9,7 @@ const nock = require('nock') const ipfsHttpClient = require('ipfs-http-client') const DelegatedContentRouter = require('libp2p-delegated-content-routing') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const Libp2p = require('../../src') const { relay: relayMulticodec } = require('../../src/circuit/multicodec') @@ -204,7 +204,7 @@ describe('auto-relay', () => { 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`) + const relayedMultiaddr2 = new Multiaddr(`${relayLibp2p1.multiaddrs[0]}/p2p/${relayLibp2p1.peerId.toB58String()}/p2p-circuit`) libp2p.peerStore.addressBook.add(relayLibp2p2.peerId, [relayedMultiaddr2]) await libp2p.dial(relayLibp2p2.peerId) @@ -473,7 +473,7 @@ describe('auto-relay', () => { protocol: 'http', port: 60197 }), [ - multiaddr('/ip4/0.0.0.0/tcp/60197') + new Multiaddr('/ip4/0.0.0.0/tcp/60197') ]) const opts = { diff --git a/test/relay/relay.node.js b/test/relay/relay.node.js index 163bd83105..033f21a3fc 100644 --- a/test/relay/relay.node.js +++ b/test/relay/relay.node.js @@ -4,7 +4,7 @@ const { expect } = require('aegir/utils/chai') const sinon = require('sinon') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const { collect } = require('streaming-iterables') const pipe = require('it-pipe') const AggregateError = require('aggregate-error') @@ -67,7 +67,7 @@ describe('Dialing (via relay, TCP)', () => { .encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerId.toB58String()}`) const tcpAddrs = dstLibp2p.transportManager.getAddrs() - sinon.stub(dstLibp2p.addressManager, 'listen').value([multiaddr(`/p2p-circuit${relayAddr}/p2p/${relayIdString}`)]) + sinon.stub(dstLibp2p.addressManager, 'listen').value([new Multiaddr(`/p2p-circuit${relayAddr}/p2p/${relayIdString}`)]) await dstLibp2p.transportManager.listen(dstLibp2p.addressManager.getListenAddrs()) expect(dstLibp2p.transportManager.getAddrs()).to.have.deep.members([...tcpAddrs, dialAddr.decapsulate('p2p')]) @@ -152,7 +152,7 @@ describe('Dialing (via relay, TCP)', () => { // Connect the destination peer and the relay const tcpAddrs = dstLibp2p.transportManager.getAddrs() - sinon.stub(dstLibp2p.addressManager, 'getListenAddrs').returns([multiaddr(`${relayAddr}/p2p-circuit`)]) + sinon.stub(dstLibp2p.addressManager, 'getListenAddrs').returns([new Multiaddr(`${relayAddr}/p2p-circuit`)]) await dstLibp2p.transportManager.listen(dstLibp2p.addressManager.getListenAddrs()) expect(dstLibp2p.transportManager.getAddrs()).to.have.deep.members([...tcpAddrs, dialAddr.decapsulate('p2p')]) diff --git a/test/transports/transport-manager.node.js b/test/transports/transport-manager.node.js index 6c335d92bf..13854ce031 100644 --- a/test/transports/transport-manager.node.js +++ b/test/transports/transport-manager.node.js @@ -9,13 +9,13 @@ 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 { Multiaddr } = require('multiaddr') const mockUpgrader = require('../utils/mockUpgrader') const sinon = require('sinon') const Peers = require('../fixtures/peers') const addrs = [ - multiaddr('/ip4/127.0.0.1/tcp/0'), - multiaddr('/ip4/127.0.0.1/tcp/0') + new Multiaddr('/ip4/127.0.0.1/tcp/0'), + new Multiaddr('/ip4/127.0.0.1/tcp/0') ] describe('Transport Manager (TCP)', () => { diff --git a/test/transports/transport-manager.spec.js b/test/transports/transport-manager.spec.js index 5c6c08b06e..75bb1b9193 100644 --- a/test/transports/transport-manager.spec.js +++ b/test/transports/transport-manager.spec.js @@ -4,7 +4,7 @@ const { expect } = require('aegir/utils/chai') const sinon = require('sinon') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const Transport = require('libp2p-websockets') const filters = require('libp2p-websockets/src/filters') const { NOISE: Crypto } = require('libp2p-noise') @@ -19,7 +19,7 @@ const { FaultTolerance } = require('../../src/transport-manager') const Peers = require('../fixtures/peers') const PeerId = require('peer-id') -const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0') +const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') describe('Transport Manager (WebSockets)', () => { let tm @@ -76,7 +76,7 @@ describe('Transport Manager (WebSockets)', () => { it('should fail to dial an unsupported address', async () => { tm.add(Transport.prototype[Symbol.toStringTag], Transport, { filter: filters.all }) - const addr = multiaddr('/ip4/127.0.0.1/tcp/0') + const addr = new Multiaddr('/ip4/127.0.0.1/tcp/0') await expect(tm.dial(addr)) .to.eventually.be.rejected() .and.to.have.property('code', ErrorCodes.ERR_TRANSPORT_UNAVAILABLE) @@ -191,7 +191,7 @@ describe('libp2p.transportManager (dial only)', () => { libp2p = new Libp2p({ peerId, addresses: { - listen: [multiaddr('/ip4/127.0.0.1/tcp/0')] + listen: [new Multiaddr('/ip4/127.0.0.1/tcp/0')] }, modules: { transport: [Transport], @@ -213,7 +213,7 @@ describe('libp2p.transportManager (dial only)', () => { libp2p = new Libp2p({ peerId, addresses: { - listen: [multiaddr('/ip4/127.0.0.1/tcp/0')] + listen: [new Multiaddr('/ip4/127.0.0.1/tcp/0')] }, transportManager: { faultTolerance: FaultTolerance.NO_FATAL @@ -231,7 +231,7 @@ describe('libp2p.transportManager (dial only)', () => { libp2p = new Libp2p({ peerId, addresses: { - listen: [multiaddr('/ip4/127.0.0.1/tcp/12345/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3/p2p-circuit')] + listen: [new Multiaddr('/ip4/127.0.0.1/tcp/12345/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3/p2p-circuit')] }, transportManager: { faultTolerance: FaultTolerance.NO_FATAL diff --git a/test/ts-use/package.json b/test/ts-use/package.json new file mode 100644 index 0000000000..c3a06e4a81 --- /dev/null +++ b/test/ts-use/package.json @@ -0,0 +1,24 @@ +{ + "name": "ts-use", + "private": true, + "dependencies": { + "datastore-level": "^4.0.0", + "ipfs-http-client": "^49.0.4", + "libp2p": "file:../..", + "libp2p-bootstrap": "^0.12.2", + "libp2p-delegated-content-routing": "^0.9.0", + "libp2p-delegated-peer-routing": "^0.8.2", + "libp2p-gossipsub": "^0.8.0", + "libp2p-interfaces": "^0.8.4", + "libp2p-kad-dht": "^0.21.0", + "libp2p-mplex": "^0.10.2", + "libp2p-noise": "^2.0.5", + "libp2p-tcp": "^0.15.3", + "libp2p-websockets": "^0.15.3", + "peer-id": "^0.14.3" + }, + "scripts": { + "build": "npx tsc", + "test": "npm install && npx -p typescript tsc --noEmit" + } +} diff --git a/test/ts-use/src/main.ts b/test/ts-use/src/main.ts new file mode 100644 index 0000000000..4dc4efe4cb --- /dev/null +++ b/test/ts-use/src/main.ts @@ -0,0 +1,192 @@ +import Libp2p = require('libp2p') + +const TCP = require('libp2p-tcp') +const WEBSOCKETS = require('libp2p-websockets') +const NOISE = require('libp2p-noise') +const MPLEX = require('libp2p-mplex') +const Gossipsub = require('libp2p-gossipsub') +const DHT = require('libp2p-kad-dht') + +const { dnsaddrResolver } = require('multiaddr/src/resolvers') +const { publicAddressesFirst } = require('libp2p-utils/src/address-sort') + +const { SignaturePolicy } = require('libp2p-interfaces/src/pubsub/signature-policy') +const { FaultTolerance } = require('libp2p/src/transport-manager') +const filters = require('libp2p-websockets/src/filters') + +const Bootstrap = require('libp2p-bootstrap') +const LevelStore = require('datastore-level') + +const ipfsHttpClient = require('ipfs-http-client') +const DelegatedPeerRouter = require('libp2p-delegated-peer-routing') +const DelegatedContentRouter = require('libp2p-delegated-content-routing') +const PeerId = require('peer-id') + +// Known peers addresses +const bootstrapMultiaddrs = [ + '/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb', + '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN' +] +const transportKey = WEBSOCKETS.prototype[Symbol.toStringTag] + +async function main() { + // create a peerId + const peerId = await PeerId.create() + + const delegatedPeerRouting = new DelegatedPeerRouter(ipfsHttpClient({ + host: 'node0.delegate.ipfs.io', // In production you should setup your own delegates + protocol: 'https', + port: 443 + })) + + const delegatedContentRouting = new DelegatedContentRouter(peerId, ipfsHttpClient({ + host: 'node0.delegate.ipfs.io', // In production you should setup your own delegates + protocol: 'https', + port: 443 + })) + + const libp2p = await Libp2p.create({ + peerId, + addresses: { + listen: ['/ip4/127.0.0.1/tcp/8000', '/ip4/127.0.0.1/tcp/8001/ws'] + }, + modules: { + transport: [TCP, WEBSOCKETS], + streamMuxer: [MPLEX], + connEncryption: [NOISE], + peerDiscovery: [Bootstrap], + pubsub: Gossipsub, + dht: DHT, + contentRouting: [delegatedContentRouting], + peerRouting: [delegatedPeerRouting] + }, + peerRouting: { + refreshManager: { + enabled: true, + interval: 1000, + bootDelay: 11111 + } + }, + dialer: { + maxParallelDials: 100, + maxDialsPerPeer: 4, + dialTimeout: 30e3, + resolvers: { + dnsaddr: dnsaddrResolver + }, + addressSorter: publicAddressesFirst + }, + connectionManager: { + maxConnections: Infinity, + minConnections: 0, + pollInterval: 2000, + defaultPeerValue: 1, + maxData: Infinity, + maxSentData: Infinity, + maxReceivedData: Infinity, + maxEventLoopDelay: Infinity, + movingAverageInterval: 60000 + }, + transportManager: { + faultTolerance: FaultTolerance.NO_FATAL + }, + metrics: { + enabled: true, + computeThrottleMaxQueueSize: 1000, + computeThrottleTimeout: 2000, + movingAverageIntervals: [ + 60 * 1000, // 1 minute + 5 * 60 * 1000, // 5 minutes + 15 * 60 * 1000 // 15 minutes + ], + maxOldPeersRetention: 50 + }, + datastore: new LevelStore('path/to/store'), + peerStore: { + persistence: false, + threshold: 5 + }, + keychain: { + pass: 'notsafepassword123456789', + datastore: new LevelStore('path/to/store-keys') + }, + config: { + peerDiscovery: { + autoDial: true, + [Bootstrap.tag]: { + enabled: true, + list: bootstrapMultiaddrs // provide array of multiaddrs + } + }, + dht: { + enabled: true, + kBucketSize: 20, + randomWalk: { + enabled: true, // Allows to disable discovery (enabled by default) + interval: 300e3, + timeout: 10e3 + } + }, + nat: { + description: 'my-node', // set as the port mapping description on the router, defaults the current libp2p version and your peer id + enabled: true, // defaults to true + gateway: '192.168.1.1', // leave unset to auto-discover + externalIp: '80.1.1.1', // leave unset to auto-discover + ttl: 7200, // TTL for port mappings (min 20 minutes) + keepAlive: true, // Refresh port mapping after TTL expires + pmp: { + enabled: false, // defaults to false + } + }, + relay: { + 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 + }, + 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 + } + }, + transport: { + [transportKey]: { + filter: filters.all + } + }, + pubsub: { // The pubsub options (and defaults) can be found in the pubsub router documentation + enabled: true, + emitSelf: false, // whether the node should emit to self on publish + globalSignaturePolicy: SignaturePolicy.StrictSign // message signing policy + } + } + }) + + libp2p.connectionManager.on('peer:connect', (connection) => { + console.log(`Connected to ${connection.remotePeer.toB58String()}`) + }) + + + + // Listen for new connections to peers + libp2p.connectionManager.on('peer:connect', (connection) => { + console.log(`Connected to ${connection.remotePeer.toB58String()}`) + }) + + // Listen for peers disconnecting + libp2p.connectionManager.on('peer:disconnect', (connection) => { + console.log(`Disconnected from ${connection.remotePeer.toB58String()}`) + }) + + + await libp2p.start() + console.log('started') + await libp2p.stop() +} + +main() diff --git a/test/ts-use/tsconfig.json b/test/ts-use/tsconfig.json new file mode 100644 index 0000000000..8cd47e1e86 --- /dev/null +++ b/test/ts-use/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "strict": true, + "noImplicitAny": true, + "skipLibCheck": true + } +} \ No newline at end of file diff --git a/test/upgrading/upgrader.spec.js b/test/upgrading/upgrader.spec.js index 96df354952..b9f276a7ed 100644 --- a/test/upgrading/upgrader.spec.js +++ b/test/upgrading/upgrader.spec.js @@ -4,7 +4,7 @@ const { expect } = require('aegir/utils/chai') const sinon = require('sinon') const Muxer = require('libp2p-mplex') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const PeerId = require('peer-id') const pipe = require('it-pipe') const { collect } = require('streaming-iterables') @@ -22,8 +22,8 @@ const { codes } = require('../../src/errors') const mockMultiaddrConnPair = require('../utils/mockMultiaddrConn') const Peers = require('../fixtures/peers') const addrs = [ - multiaddr('/ip4/127.0.0.1/tcp/0'), - multiaddr('/ip4/127.0.0.1/tcp/0') + new Multiaddr('/ip4/127.0.0.1/tcp/0'), + new Multiaddr('/ip4/127.0.0.1/tcp/0') ] describe('Upgrader', () => { diff --git a/test/utils/creators/peer.js b/test/utils/creators/peer.js index 2c77cb63a0..c9f213f44e 100644 --- a/test/utils/creators/peer.js +++ b/test/utils/creators/peer.js @@ -2,14 +2,14 @@ const pTimes = require('p-times') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const PeerId = require('peer-id') const Libp2p = require('../../../src') const Peers = require('../../fixtures/peers') const defaultOptions = require('../base-options.browser') -const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0') +const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') /** * Create libp2p nodes. diff --git a/test/utils/mockConnection.js b/test/utils/mockConnection.js index b85c036ebb..66c659fb16 100644 --- a/test/utils/mockConnection.js +++ b/test/utils/mockConnection.js @@ -2,7 +2,7 @@ const pipe = require('it-pipe') const { Connection } = require('libp2p-interfaces/src/connection') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const Muxer = require('libp2p-mplex') const Multistream = require('multistream-select') const pair = require('it-pair') @@ -13,8 +13,8 @@ const mockMultiaddrConnPair = require('./mockMultiaddrConn') const peerUtils = require('./creators/peer') module.exports = async (properties = {}) => { - const localAddr = multiaddr('/ip4/127.0.0.1/tcp/8080') - const remoteAddr = multiaddr('/ip4/127.0.0.1/tcp/8081') + const localAddr = new Multiaddr('/ip4/127.0.0.1/tcp/8080') + const remoteAddr = new Multiaddr('/ip4/127.0.0.1/tcp/8081') const [localPeer, remotePeer] = await peerUtils.createPeerId({ number: 2 }) const openStreams = [] diff --git a/tsconfig.json b/tsconfig.json index 5b9a618c43..d9083a6a34 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,5 +5,14 @@ }, "include": [ "src" + ], + "exclude": [ + "src/circuit/protocol/index.js", // exclude generated file + "src/identify/message.js", // exclude generated file + "src/insecure/proto.js", // exclude generated file + "src/peer-store/persistent/pb/address-book.js", // exclude generated file + "src/peer-store/persistent/pb/proto-book.js", // exclude generated file + "src/record/peer-record/peer-record.js", // exclude generated file + "src/record/envelope/envelope.js" // exclude generated file ] } \ No newline at end of file From 9d1b917e8aa5915a7b8990bc09f8616eb7fd13a0 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 15 Apr 2021 09:52:55 +0200 Subject: [PATCH 144/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 668e840c3e..fdda1672ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.30.12", + "version": "0.31.0-rc.0", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From 0aed9fe1b370292bf5b44b56371b0dfe5c1fd364 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 15 Apr 2021 09:52:56 +0200 Subject: [PATCH 145/447] chore: release version v0.31.0-rc.0 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31704e47ca..ac8088c500 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# [0.31.0-rc.0](https://github.com/libp2p/js-libp2p/compare/v0.30.12...v0.31.0-rc.0) (2021-04-15) + + + ## [0.30.12](https://github.com/libp2p/js-libp2p/compare/v0.30.11...v0.30.12) (2021-03-27) From b4fb9b7bf266ba03c4462c0a41b1c2691e4e88d4 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 12 Feb 2021 11:22:11 +0100 Subject: [PATCH 146/447] fix: address book should not emit peer event if no addresses are known --- src/circuit/auto-relay.js | 17 +++++++---- src/index.js | 2 +- src/peer-store/address-book.js | 5 ++++ test/peer-store/address-book.spec.js | 17 +++++++++++ test/relay/auto-relay.node.js | 45 ++++++++++++++++++++++++++++ 5 files changed, 80 insertions(+), 6 deletions(-) diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js index a99bb14d32..c5c5d3e959 100644 --- a/src/circuit/auto-relay.js +++ b/src/circuit/auto-relay.js @@ -231,8 +231,7 @@ class AutoRelay { // 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()) + await this._tryToListenOnRelay(peerId) // Check if already listening on enough relays if (this._listenRelays.size >= this.maxListeners) { @@ -247,12 +246,11 @@ class AutoRelay { if (!provider.multiaddrs.length) { continue } - const peerId = provider.id + const peerId = provider.id this._peerStore.addressBook.add(peerId, provider.multiaddrs) - const connection = await this._libp2p.dial(peerId) - await this._addListenRelay(connection, peerId.toB58String()) + await this._tryToListenOnRelay(peerId) // Check if already listening on enough relays if (this._listenRelays.size >= this.maxListeners) { @@ -263,6 +261,15 @@ class AutoRelay { log.error(err) } } + + async _tryToListenOnRelay (peerId) { + try { + const connection = await this._libp2p.dial(peerId) + await this._addListenRelay(connection, peerId.toB58String()) + } catch (err) { + log.error(`could not connect and listen on known hop relay ${peerId.toB58String()}`) + } + } } module.exports = AutoRelay diff --git a/src/index.js b/src/index.js index 02fa4f4ed5..4494ebac5d 100644 --- a/src/index.js +++ b/src/index.js @@ -700,7 +700,7 @@ class Libp2p extends EventEmitter { try { await this.dialer.connectToPeer(peerId) } catch (err) { - log.error('could not connect to discovered peer', err) + log.error(`could not connect to discovered peer ${peerId.toB58String()} with ${err}`) } } } diff --git a/src/peer-store/address-book.js b/src/peer-store/address-book.js index 595781c068..8b94acc584 100644 --- a/src/peer-store/address-book.js +++ b/src/peer-store/address-book.js @@ -229,6 +229,11 @@ class AddressBook extends Book { const addresses = this._toAddresses(multiaddrs) const id = peerId.toB58String() + // Not add unavailable addresses + if (!addresses.length) { + return this + } + const entry = this.data.get(id) if (entry && entry.addresses) { diff --git a/test/peer-store/address-book.spec.js b/test/peer-store/address-book.spec.js index 48eb7180bc..512fc8fd35 100644 --- a/test/peer-store/address-book.spec.js +++ b/test/peer-store/address-book.spec.js @@ -186,6 +186,23 @@ describe('addressBook', () => { throw new Error('invalid multiaddr should throw error') }) + it('does not emit event if no addresses are added', async () => { + const defer = pDefer() + + peerStore.on('peer', () => { + defer.reject() + }) + + ab.add(peerId, []) + + // Wait 50ms for incorrect second event + setTimeout(() => { + defer.resolve() + }, 50) + + await defer.promise + }) + it('adds the new content and emits change event', () => { const defer = pDefer() diff --git a/test/relay/auto-relay.node.js b/test/relay/auto-relay.node.js index a254b0475d..4f0aa05cc7 100644 --- a/test/relay/auto-relay.node.js +++ b/test/relay/auto-relay.node.js @@ -3,6 +3,7 @@ const { expect } = require('aegir/utils/chai') const delay = require('delay') +const pDefer = require('p-defer') const pWaitFor = require('p-wait-for') const sinon = require('sinon') const nock = require('nock') @@ -371,6 +372,50 @@ describe('auto-relay', () => { expect(autoRelay1._listenRelays.size).to.equal(1) expect(relayLibp2p1.connectionManager.size).to.eql(1) }) + + it('should not fail when trying to dial unreachable peers to add as hop relay and replaced removed ones', async () => { + const defer = pDefer() + // 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) + + // Stub dial + sinon.stub(relayLibp2p1, 'dial').callsFake(() => { + defer.resolve() + return Promise.reject(new Error('failed to 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 failed dial + await defer.promise + }) }) describe('flows with 2 max listeners', () => { From 828a32d4f578ab8e57970cd8ddb30f1a7838786b Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 15 Apr 2021 14:38:25 +0200 Subject: [PATCH 147/447] chore: add jsdoc --- src/circuit/auto-relay.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js index c5c5d3e959..613c473ef5 100644 --- a/src/circuit/auto-relay.js +++ b/src/circuit/auto-relay.js @@ -262,6 +262,9 @@ class AutoRelay { } } + /** + * @param {PeerId} peerId + */ async _tryToListenOnRelay (peerId) { try { const connection = await this._libp2p.dial(peerId) From a93cca91783dad43512c20f5d32af23670634488 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 16 Apr 2021 09:27:44 +0200 Subject: [PATCH 148/447] chore: update release template with examples testing (#911) --- RELEASE.md | 1 - 1 file changed, 1 deletion(-) diff --git a/RELEASE.md b/RELEASE.md index 6ad0393da2..33991ebf36 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -25,7 +25,6 @@ - [ ] [js-ipfs](https://github.com/ipfs/js-ipfs) - 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 From 21c9aeecb13440238aa6b0fb5a6731d2f87d4938 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 16 Apr 2021 15:49:57 +0200 Subject: [PATCH 149/447] fix: dial protocol should throw if no protocol is provided (#914) BREAKING CHANGE: dialProtocol does not return connection when no protocols are provided --- src/errors.js | 1 + src/index.js | 15 ++++++--------- test/dialing/direct.node.js | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/errors.js b/src/errors.js index 14752b2706..0b73b983db 100644 --- a/src/errors.js +++ b/src/errors.js @@ -11,6 +11,7 @@ exports.codes = { PUBSUB_NOT_STARTED: 'ERR_PUBSUB_NOT_STARTED', DHT_NOT_STARTED: 'ERR_DHT_NOT_STARTED', CONN_ENCRYPTION_REQUIRED: 'ERR_CONN_ENCRYPTION_REQUIRED', + ERR_INVALID_PROTOCOLS_FOR_STREAM: 'ERR_INVALID_PROTOCOLS_FOR_STREAM', ERR_CONNECTION_ENDED: 'ERR_CONNECTION_ENDED', ERR_CONNECTION_FAILED: 'ERR_CONNECTION_FAILED', ERR_NODE_NOT_STARTED: 'ERR_NODE_NOT_STARTED', diff --git a/src/index.js b/src/index.js index 02fa4f4ed5..fb961f0e77 100644 --- a/src/index.js +++ b/src/index.js @@ -462,26 +462,23 @@ class Libp2p extends EventEmitter { } /** - * Dials to the provided peer and handshakes with the given protocol. + * Dials to the provided peer and tries to handshake with the given protocols in order. * If successful, the known metadata of the peer will be added to the nodes `peerStore`, - * and the `Connection` will be returned + * and the `MuxedStream` will be returned together with the successful negotiated protocol. * * @async * @param {PeerId|Multiaddr|string} peer - The peer to dial * @param {string[]|string} protocols * @param {object} [options] * @param {AbortSignal} [options.signal] - * @returns {Promise} */ async dialProtocol (peer, protocols, options) { - const connection = await this._dial(peer, options) - - // If a protocol was provided, create a new stream - if (protocols && protocols.length) { - return connection.newStream(protocols) + if (!protocols || !protocols.length) { + throw errCode(new Error('no protocols were provided to open a stream'), codes.ERR_INVALID_PROTOCOLS_FOR_STREAM) } - return connection + const connection = await this._dial(peer, options) + return connection.newStream(protocols) } /** diff --git a/test/dialing/direct.node.js b/test/dialing/direct.node.js index 1f8a5dfee7..1a1b508929 100644 --- a/test/dialing/direct.node.js +++ b/test/dialing/direct.node.js @@ -352,6 +352,25 @@ describe('Dialing (direct, TCP)', () => { await pWaitFor(() => remoteConn.streams.length === 0) }) + it('should throw when using dialProtocol with no protocols', async () => { + libp2p = new Libp2p({ + peerId, + modules: { + transport: [Transport], + streamMuxer: [Muxer], + connEncryption: [Crypto] + } + }) + + await expect(libp2p.dialProtocol(remotePeerId)) + .to.eventually.be.rejectedWith(Error) + .and.to.have.property('code', ErrorCodes.ERR_INVALID_PROTOCOLS_FOR_STREAM) + + await expect(libp2p.dialProtocol(remotePeerId, [])) + .to.eventually.be.rejectedWith(Error) + .and.to.have.property('code', ErrorCodes.ERR_INVALID_PROTOCOLS_FOR_STREAM) + }) + it('should be able to use hangup to close connections', async () => { libp2p = new Libp2p({ peerId, From ffe122d47e24e0cb1dd35a7a845ae4cb0a04f07b Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 16 Apr 2021 15:55:22 +0200 Subject: [PATCH 150/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fdda1672ad..5d0e2fe819 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.31.0-rc.0", + "version": "0.31.0-rc.1", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From 55ee332907dc097c2579079b16c1ff12b0251a26 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 16 Apr 2021 15:55:22 +0200 Subject: [PATCH 151/447] chore: release version v0.31.0-rc.1 --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac8088c500..43e77fdf88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +# [0.31.0-rc.1](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.0...v0.31.0-rc.1) (2021-04-16) + + +### Bug Fixes + +* dial protocol should throw if no protocol is provided ([#914](https://github.com/libp2p/js-libp2p/issues/914)) ([21c9aee](https://github.com/libp2p/js-libp2p/commit/21c9aeecb13440238aa6b0fb5a6731d2f87d4938)) + + +### BREAKING CHANGES + +* dialProtocol does not return connection when no protocols are provided + + + # [0.31.0-rc.0](https://github.com/libp2p/js-libp2p/compare/v0.30.12...v0.31.0-rc.0) (2021-04-15) From 3d0a79eff3bc34a5bdc8ffa31e9b09345a02ad9d Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 16 Apr 2021 16:10:22 +0100 Subject: [PATCH 152/447] fix: metrics stats and moving averages types (#915) * fix: give stats initial values Otherwise the compiler cannot derive the type and thinks `stats.snapshot` returns `{}` * fix: add type shape to moving averages as well --- src/metrics/stats.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/metrics/stats.js b/src/metrics/stats.js index 0153624154..8d76140031 100644 --- a/src/metrics/stats.js +++ b/src/metrics/stats.js @@ -2,7 +2,7 @@ 'use strict' const EventEmitter = require('events') -const Big = require('bignumber.js') +const { BigNumber: Big } = require('bignumber.js') const MovingAverage = require('moving-average') const retimer = require('retimer') @@ -19,11 +19,17 @@ class Stats extends EventEmitter { this._options = options this._queue = [] - this._stats = {} + + /** @type {{ dataReceived: Big, dataSent: Big }} */ + this._stats = { + dataReceived: Big(0), + dataSent: Big(0) + } this._frequencyLastTime = Date.now() this._frequencyAccumulators = {} + /** @type {{ dataReceived: MovingAverage[], dataSent: MovingAverage[] }} */ this._movingAverages = {} this._update = this._update.bind(this) From 49f04cbe70f50001005568cddf1b5380821e2c4c Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 16 Apr 2021 17:12:57 +0200 Subject: [PATCH 153/447] chore: update contributors --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5d0e2fe819..06961bdea1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.31.0-rc.1", + "version": "0.31.0-rc.2", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", @@ -174,8 +174,8 @@ "David Dias ", "Jacob Heun ", "Vasco Santos ", - "Alan Shaw ", "Alex Potsides ", + "Alan Shaw ", "Cayman ", "Pedro Teixeira ", "Friedel Ziegelmayer ", From 906315ce73c52e1cf70d96e7d0168e6337e40eff Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 16 Apr 2021 17:12:57 +0200 Subject: [PATCH 154/447] chore: release version v0.31.0-rc.2 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43e77fdf88..0e26ce8b00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# [0.31.0-rc.2](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.1...v0.31.0-rc.2) (2021-04-16) + + +### Bug Fixes + +* metrics stats and moving averages types ([#915](https://github.com/libp2p/js-libp2p/issues/915)) ([3d0a79e](https://github.com/libp2p/js-libp2p/commit/3d0a79eff3bc34a5bdc8ffa31e9b09345a02ad9d)) + + + # [0.31.0-rc.1](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.0...v0.31.0-rc.1) (2021-04-16) From 2af692fb4de572168524ae684608fc6526de4ef7 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Mon, 19 Apr 2021 13:08:00 +0100 Subject: [PATCH 155/447] fix: remove inline arg types from function definitions (#916) Co-authored-by: Vasco Santos --- package.json | 2 +- src/upgrader.js | 7 ++++--- test/ts-use/package.json | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 06961bdea1..d92f89fb07 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "it-pipe": "^1.1.0", "it-take": "1.0.0", "libp2p-crypto": "^0.19.0", - "libp2p-interfaces": "^0.10.0", + "libp2p-interfaces": "^0.10.1", "libp2p-utils": "^0.3.1", "mafmt": "^9.0.0", "merge-options": "^3.0.4", diff --git a/src/upgrader.js b/src/upgrader.js index 6f5f3f0169..cdf4706c46 100644 --- a/src/upgrader.js +++ b/src/upgrader.js @@ -222,6 +222,7 @@ class Upgrader { }) { /** @type {import("libp2p-interfaces/src/stream-muxer/types").Muxer} */ let muxer + /** @type {import("libp2p-interfaces/src/connection/connection").CreatedMuxedStream | undefined} */ let newStream /** @type {Connection} */ let connection // eslint-disable-line prefer-const @@ -249,7 +250,7 @@ class Upgrader { } }) - newStream = async (/** @type {string | string[]} */ protocols) => { + newStream = async (protocols) => { log('%s: starting new stream on %s', direction, protocols) const muxedStream = muxer.newStream() const mss = new Multistream.Dialer(muxedStream) @@ -309,8 +310,8 @@ class Upgrader { }, newStream: newStream || errConnectionNotMultiplexed, getStreams: () => muxer ? muxer.streams : errConnectionNotMultiplexed(), - close: async (/** @type {Error | undefined} */ err) => { - await maConn.close(err) + close: async () => { + await maConn.close() // Ensure remaining streams are aborted if (muxer) { muxer.streams.map(stream => stream.abort()) diff --git a/test/ts-use/package.json b/test/ts-use/package.json index c3a06e4a81..106551f195 100644 --- a/test/ts-use/package.json +++ b/test/ts-use/package.json @@ -9,7 +9,7 @@ "libp2p-delegated-content-routing": "^0.9.0", "libp2p-delegated-peer-routing": "^0.8.2", "libp2p-gossipsub": "^0.8.0", - "libp2p-interfaces": "^0.8.4", + "libp2p-interfaces": "^0.10.1", "libp2p-kad-dht": "^0.21.0", "libp2p-mplex": "^0.10.2", "libp2p-noise": "^2.0.5", From 6f4e7ceeacced0dca0462e6090b3b612df2e75e2 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 19 Apr 2021 14:12:25 +0200 Subject: [PATCH 156/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d92f89fb07..b06976b314 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.31.0-rc.2", + "version": "0.31.0-rc.3", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From 3f7dde3e13e3fcd4e3d3c3d64cde5a7bdc82ed54 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 19 Apr 2021 14:12:25 +0200 Subject: [PATCH 157/447] chore: release version v0.31.0-rc.3 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e26ce8b00..042000cfc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# [0.31.0-rc.3](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.2...v0.31.0-rc.3) (2021-04-19) + + +### Bug Fixes + +* remove inline arg types from function definitions ([#916](https://github.com/libp2p/js-libp2p/issues/916)) ([2af692f](https://github.com/libp2p/js-libp2p/commit/2af692fb4de572168524ae684608fc6526de4ef7)) + + + # [0.31.0-rc.2](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.1...v0.31.0-rc.2) (2021-04-16) From 06e8f3dd42432e4b37ab7904b02abde7d1cadda3 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 20 Apr 2021 09:31:16 +0200 Subject: [PATCH 158/447] fix: do not add abort signals to useless addresses (#913) --- src/dialer/index.js | 8 ++++++-- test/core/listening.node.js | 2 +- test/dialing/direct.node.js | 28 ++++++++++++++++++++++++---- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/dialer/index.js b/src/dialer/index.js index 1225e1cc28..31919a1f44 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -110,7 +110,7 @@ class Dialer { 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) + throw errCode(new Error('The dial request has no valid addresses'), codes.ERR_NO_VALID_ADDRESSES) } const pendingDial = this._pendingDials.get(dialTarget.id) || this._createPendingDial(dialTarget, options) @@ -134,6 +134,7 @@ 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. + * Multiaddrs not supported by the available transports will be filtered out. * * @private * @param {PeerId|Multiaddr|string} peer - A PeerId or Multiaddr @@ -162,9 +163,12 @@ class Dialer { resolvedAddrs.forEach(ra => addrs.push(ra)) } + // Multiaddrs not supported by the available transports will be filtered out. + const supportedAddrs = addrs.filter(a => this.transportManager.transportForMultiaddr(a)) + return { id: id.toB58String(), - addrs + addrs: supportedAddrs } } diff --git a/test/core/listening.node.js b/test/core/listening.node.js index fe202b1ab1..7e948ca869 100644 --- a/test/core/listening.node.js +++ b/test/core/listening.node.js @@ -45,7 +45,7 @@ describe('Listening', () => { expect(addrs.length).to.be.at.least(2) for (const addr of addrs) { const opts = addr.toOptions() - expect(opts.family).to.equal('ipv4') + expect(opts.family).to.equal(4) expect(opts.transport).to.equal('tcp') expect(opts.host).to.match(/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/) expect(opts.port).to.be.gt(0) diff --git a/test/dialing/direct.node.js b/test/dialing/direct.node.js index 1a1b508929..7750a35773 100644 --- a/test/dialing/direct.node.js +++ b/test/dialing/direct.node.js @@ -98,8 +98,8 @@ describe('Dialing (direct, TCP)', () => { const dialer = new Dialer({ transportManager: localTM, peerStore }) await expect(dialer.connectToPeer(unsupportedAddr)) - .to.eventually.be.rejectedWith(AggregateError) - .and.to.have.nested.property('._errors[0].code', ErrorCodes.ERR_TRANSPORT_UNAVAILABLE) + .to.eventually.be.rejectedWith(Error) + .and.to.have.nested.property('.code', ErrorCodes.ERR_NO_VALID_ADDRESSES) }) it('should fail to connect if peer has no known addresses', async () => { @@ -139,8 +139,28 @@ describe('Dialing (direct, TCP)', () => { const peerId = await PeerId.createFromJSON(Peers[0]) await expect(dialer.connectToPeer(peerId)) - .to.eventually.be.rejectedWith(AggregateError) - .and.to.have.nested.property('._errors[0].code', ErrorCodes.ERR_TRANSPORT_UNAVAILABLE) + .to.eventually.be.rejectedWith(Error) + .and.to.have.nested.property('.code', ErrorCodes.ERR_NO_VALID_ADDRESSES) + }) + + it('should only try to connect to addresses supported by the transports configured', async () => { + const remoteAddrs = remoteTM.getAddrs() + const dialer = new Dialer({ + transportManager: localTM, + peerStore: { + addressBook: { + add: () => { }, + getMultiaddrsForPeer: () => [...remoteAddrs, unsupportedAddr] + } + } + }) + const peerId = await PeerId.createFromJSON(Peers[0]) + + sinon.spy(localTM, 'dial') + const connection = await dialer.connectToPeer(peerId) + expect(localTM.dial.callCount).to.equal(remoteAddrs.length) + expect(connection).to.exist() + await connection.close() }) it('should abort dials on queue task timeout', async () => { From 3ffeb4ebe6320093d0b8d65a1b3b68f4183cd50f Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 20 Apr 2021 11:23:31 +0200 Subject: [PATCH 159/447] chore: apply suggestions from code review Co-authored-by: Irakli Gozalishvili --- src/circuit/auto-relay.js | 20 +++++++++++++++----- src/peer-store/address-book.js | 2 +- test/relay/auto-relay.node.js | 2 ++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js index 613c473ef5..20174e9921 100644 --- a/src/circuit/auto-relay.js +++ b/src/circuit/auto-relay.js @@ -31,6 +31,7 @@ const { * * @typedef {Object} AutoRelayOptions * @property {number} [maxListeners = 1] - maximum number of relays to listen. + * @property {(error: Error, msg?: string) => {}} [onError] */ class AutoRelay { @@ -40,7 +41,7 @@ class AutoRelay { * @class * @param {AutoRelayProperties & AutoRelayOptions} props */ - constructor ({ libp2p, maxListeners = 1 }) { + constructor ({ libp2p, maxListeners = 1, onError }) { this._libp2p = libp2p this._peerId = libp2p.peerId this._peerStore = libp2p.peerStore @@ -60,6 +61,15 @@ class AutoRelay { this._peerStore.on('change:protocols', this._onProtocolChange) this._connectionManager.on('peer:disconnect', this._onPeerDisconnected) + + /** + * @param {Error} error + * @param {string} [msg] + */ + this._onError = (error, msg) => { + log.error(msg || error) + onError && onError(error, msg) + } } /** @@ -107,7 +117,7 @@ class AutoRelay { await this._addListenRelay(connection, id) } } catch (err) { - log.error(err) + this._onError(err) } } @@ -160,7 +170,7 @@ class AutoRelay { await this._transportManager.listen([new Multiaddr(listenAddr)]) // Announce multiaddrs will update on listen success by TransportManager event being triggered } catch (err) { - log.error(err) + this._onError(err) this._listenRelays.delete(id) } } @@ -258,7 +268,7 @@ class AutoRelay { } } } catch (err) { - log.error(err) + this._onError(err) } } @@ -270,7 +280,7 @@ class AutoRelay { const connection = await this._libp2p.dial(peerId) await this._addListenRelay(connection, peerId.toB58String()) } catch (err) { - log.error(`could not connect and listen on known hop relay ${peerId.toB58String()}`) + this._onError(err, `could not connect and listen on known hop relay ${peerId.toB58String()}`) } } } diff --git a/src/peer-store/address-book.js b/src/peer-store/address-book.js index 8b94acc584..f04ea453de 100644 --- a/src/peer-store/address-book.js +++ b/src/peer-store/address-book.js @@ -229,7 +229,7 @@ class AddressBook extends Book { const addresses = this._toAddresses(multiaddrs) const id = peerId.toB58String() - // Not add unavailable addresses + // No addresses to be added if (!addresses.length) { return this } diff --git a/test/relay/auto-relay.node.js b/test/relay/auto-relay.node.js index 4f0aa05cc7..3b0481a31d 100644 --- a/test/relay/auto-relay.node.js +++ b/test/relay/auto-relay.node.js @@ -359,6 +359,7 @@ describe('auto-relay', () => { expect(relayLibp2p1.connectionManager.size).to.equal(1) // Spy on dial + sinon.spy(autoRelay1, '_tryToListenOnRelay') sinon.spy(relayLibp2p1, 'dial') // Remove peer used as relay from peerStore and disconnect it @@ -369,6 +370,7 @@ describe('auto-relay', () => { // Wait for other peer connected to be added as listen addr await pWaitFor(() => relayLibp2p1.transportManager.listen.callCount === 2) + expect(autoRelay1._tryToListenOnRelay.callCount).to.equal(1) expect(autoRelay1._listenRelays.size).to.equal(1) expect(relayLibp2p1.connectionManager.size).to.eql(1) }) From b043bca607565cf534771e6cf975288a8ff3030b Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 20 Apr 2021 16:25:48 +0200 Subject: [PATCH 160/447] fix: specify pbjs root (#917) --- package.json | 14 +++++++------- src/circuit/protocol/index.js | 2 +- src/identify/message.js | 2 +- src/insecure/proto.js | 2 +- src/peer-store/persistent/pb/address-book.js | 2 +- src/peer-store/persistent/pb/proto-book.js | 2 +- src/record/envelope/envelope.js | 2 +- src/record/peer-record/peer-record.js | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index b06976b314..85dc960152 100644 --- a/package.json +++ b/package.json @@ -21,13 +21,13 @@ "lint": "aegir lint", "build": "aegir build", "build:proto": "npm run build:proto:circuit && npm run build:proto:identify && npm run build:proto:plaintext && npm run build:proto:address-book && npm run build:proto:proto-book && npm run build:proto:peer-record && npm run build:proto:envelope", - "build:proto:circuit": "pbjs -t static-module -w commonjs --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/circuit/protocol/index.js ./src/circuit/protocol/index.proto", - "build:proto:identify": "pbjs -t static-module -w commonjs --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/identify/message.js ./src/identify/message.proto", - "build:proto:plaintext": "pbjs -t static-module -w commonjs --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/insecure/proto.js ./src/insecure/proto.proto", - "build:proto:address-book": "pbjs -t static-module -w commonjs --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/peer-store/persistent/pb/address-book.js ./src/peer-store/persistent/pb/address-book.proto", - "build:proto:proto-book": "pbjs -t static-module -w commonjs --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/peer-store/persistent/pb/proto-book.js ./src/peer-store/persistent/pb/proto-book.proto", - "build:proto:peer-record": "pbjs -t static-module -w commonjs --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/record/peer-record/peer-record.js ./src/record/peer-record/peer-record.proto", - "build:proto:envelope": "pbjs -t static-module -w commonjs --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/record/envelope/envelope.js ./src/record/envelope/envelope.proto", + "build:proto:circuit": "pbjs -t static-module -w commonjs -r libp2p-circuit --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/circuit/protocol/index.js ./src/circuit/protocol/index.proto", + "build:proto:identify": "pbjs -t static-module -w commonjs -r libp2p-identify --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/identify/message.js ./src/identify/message.proto", + "build:proto:plaintext": "pbjs -t static-module -w commonjs -r libp2p-plaintext --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/insecure/proto.js ./src/insecure/proto.proto", + "build:proto:address-book": "pbjs -t static-module -w commonjs -r libp2p-address-book --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/peer-store/persistent/pb/address-book.js ./src/peer-store/persistent/pb/address-book.proto", + "build:proto:proto-book": "pbjs -t static-module -w commonjs -r libp2p-proto-book --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/peer-store/persistent/pb/proto-book.js ./src/peer-store/persistent/pb/proto-book.proto", + "build:proto:peer-record": "pbjs -t static-module -w commonjs -r libp2p-peer-record --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/record/peer-record/peer-record.js ./src/record/peer-record/peer-record.proto", + "build:proto:envelope": "pbjs -t static-module -w commonjs -r libp2p-envelope --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/record/envelope/envelope.js ./src/record/envelope/envelope.proto", "build:proto-types": "npm run build:proto-types:circuit && npm run build:proto-types:identify && npm run build:proto-types:plaintext && npm run build:proto-types:address-book && npm run build:proto-types:proto-book && npm run build:proto-types:peer-record && npm run build:proto-types:envelope", "build:proto-types:circuit": "pbts -o src/circuit/protocol/index.d.ts src/circuit/protocol/index.js", "build:proto-types:identify": "pbts -o src/identify/message.d.ts src/identify/message.js", diff --git a/src/circuit/protocol/index.js b/src/circuit/protocol/index.js index be12eb9b37..d929debce0 100644 --- a/src/circuit/protocol/index.js +++ b/src/circuit/protocol/index.js @@ -7,7 +7,7 @@ var $protobuf = require("protobufjs/minimal"); var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; // Exported root namespace -var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); +var $root = $protobuf.roots["libp2p-circuit"] || ($protobuf.roots["libp2p-circuit"] = {}); $root.CircuitRelay = (function() { diff --git a/src/identify/message.js b/src/identify/message.js index 9f4d6e4ec2..f4f04febdb 100644 --- a/src/identify/message.js +++ b/src/identify/message.js @@ -7,7 +7,7 @@ var $protobuf = require("protobufjs/minimal"); var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; // Exported root namespace -var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); +var $root = $protobuf.roots["libp2p-identify"] || ($protobuf.roots["libp2p-identify"] = {}); $root.Identify = (function() { diff --git a/src/insecure/proto.js b/src/insecure/proto.js index 7c0161f60d..ab43d4a9ee 100644 --- a/src/insecure/proto.js +++ b/src/insecure/proto.js @@ -7,7 +7,7 @@ var $protobuf = require("protobufjs/minimal"); var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; // Exported root namespace -var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); +var $root = $protobuf.roots["libp2p-plaintext"] || ($protobuf.roots["libp2p-plaintext"] = {}); $root.Exchange = (function() { diff --git a/src/peer-store/persistent/pb/address-book.js b/src/peer-store/persistent/pb/address-book.js index 489a36ca4d..f45bc94214 100644 --- a/src/peer-store/persistent/pb/address-book.js +++ b/src/peer-store/persistent/pb/address-book.js @@ -7,7 +7,7 @@ var $protobuf = require("protobufjs/minimal"); var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; // Exported root namespace -var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); +var $root = $protobuf.roots["libp2p-address-book"] || ($protobuf.roots["libp2p-address-book"] = {}); $root.Addresses = (function() { diff --git a/src/peer-store/persistent/pb/proto-book.js b/src/peer-store/persistent/pb/proto-book.js index fea33f9933..fc3633ddd7 100644 --- a/src/peer-store/persistent/pb/proto-book.js +++ b/src/peer-store/persistent/pb/proto-book.js @@ -7,7 +7,7 @@ var $protobuf = require("protobufjs/minimal"); var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; // Exported root namespace -var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); +var $root = $protobuf.roots["libp2p-proto-book"] || ($protobuf.roots["libp2p-proto-book"] = {}); $root.Protocols = (function() { diff --git a/src/record/envelope/envelope.js b/src/record/envelope/envelope.js index ff102b2470..8741154eac 100644 --- a/src/record/envelope/envelope.js +++ b/src/record/envelope/envelope.js @@ -7,7 +7,7 @@ var $protobuf = require("protobufjs/minimal"); var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; // Exported root namespace -var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); +var $root = $protobuf.roots["libp2p-envelope"] || ($protobuf.roots["libp2p-envelope"] = {}); $root.Envelope = (function() { diff --git a/src/record/peer-record/peer-record.js b/src/record/peer-record/peer-record.js index e851b146b7..9f95667090 100644 --- a/src/record/peer-record/peer-record.js +++ b/src/record/peer-record/peer-record.js @@ -7,7 +7,7 @@ var $protobuf = require("protobufjs/minimal"); var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; // Exported root namespace -var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); +var $root = $protobuf.roots["libp2p-peer-record"] || ($protobuf.roots["libp2p-peer-record"] = {}); $root.PeerRecord = (function() { From 975e77991e67dd9bff790b83df7bd6fa5ddcfc67 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 20 Apr 2021 16:11:23 +0100 Subject: [PATCH 161/447] fix: add clientMode dht arg and upgrade interface-datastore (#918) --- package.json | 2 +- src/index.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 85dc960152..955a352cea 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "es6-promisify": "^6.1.1", "events": "^3.3.0", "hashlru": "^2.3.0", - "interface-datastore": "^3.0.3", + "interface-datastore": "^4.0.0", "ipfs-utils": "^6.0.0", "it-all": "^1.0.4", "it-buffer": "^0.1.2", diff --git a/src/index.js b/src/index.js index fb961f0e77..c47006ca32 100644 --- a/src/index.js +++ b/src/index.js @@ -66,6 +66,7 @@ const { updateSelfPeerRecord } = require('./record/utils') * @property {boolean} [enabled = false] * @property {number} [kBucketSize = 20] * @property {RandomWalkOptions} [randomWalk] + * @property {boolean} [clientMode] * * @typedef {Object} KeychainOptions * @property {Datastore} [datastore] From c381be3510c823d2fecca952720ae2f3d4231ead Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 20 Apr 2021 17:14:16 +0200 Subject: [PATCH 162/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 955a352cea..56f507b49f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.31.0-rc.3", + "version": "0.31.0-rc.4", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From 7eb2cea570c63131da8bd62a53a76acd4dc60c62 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 20 Apr 2021 17:14:17 +0200 Subject: [PATCH 163/447] chore: release version v0.31.0-rc.4 --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 042000cfc6..4653af161b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# [0.31.0-rc.4](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.3...v0.31.0-rc.4) (2021-04-20) + + +### Bug Fixes + +* add clientMode dht arg and upgrade interface-datastore ([#918](https://github.com/libp2p/js-libp2p/issues/918)) ([975e779](https://github.com/libp2p/js-libp2p/commit/975e77991e67dd9bff790b83df7bd6fa5ddcfc67)) +* do not add abort signals to useless addresses ([#913](https://github.com/libp2p/js-libp2p/issues/913)) ([06e8f3d](https://github.com/libp2p/js-libp2p/commit/06e8f3dd42432e4b37ab7904b02abde7d1cadda3)) +* specify pbjs root ([#917](https://github.com/libp2p/js-libp2p/issues/917)) ([b043bca](https://github.com/libp2p/js-libp2p/commit/b043bca607565cf534771e6cf975288a8ff3030b)) + + + # [0.31.0-rc.3](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.2...v0.31.0-rc.3) (2021-04-19) From 6456a0fff812ce7d99fa7d09a4ffc9e264f4faa8 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Wed, 21 Apr 2021 18:45:50 +1000 Subject: [PATCH 164/447] chore: browser example dependencies must be installed at the root first (#921) Fixes #920 --- examples/libp2p-in-the-browser/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/libp2p-in-the-browser/README.md b/examples/libp2p-in-the-browser/README.md index fbb25a8bc9..e98a5ad69e 100644 --- a/examples/libp2p-in-the-browser/README.md +++ b/examples/libp2p-in-the-browser/README.md @@ -4,9 +4,13 @@ This example leverages the [Parcel.js bundler](https://parceljs.org/) to compile ## Setup -In order to run the example, first install the dependencies from same directory as this README: +In order to run the example: + +- Install dependencey at the root of the js-libp2p repository (if not already done), +- then, install the dependencies from same directory as this README: ``` +npm install cd ./examples/libp2p-in-the-browser npm install ``` From cc1f4af879a58e94538591851d0085ff98cd2641 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 21 Apr 2021 13:28:37 +0200 Subject: [PATCH 165/447] fix: dht configuration selectors and validators (#919) --- src/index.js | 5 +++++ test/ts-use/package.json | 1 + test/ts-use/src/main.ts | 9 +++++++++ 3 files changed, 15 insertions(+) diff --git a/src/index.js b/src/index.js index 4ed09b98ca..4d063be9cb 100644 --- a/src/index.js +++ b/src/index.js @@ -56,6 +56,9 @@ const { updateSelfPeerRecord } = require('./record/utils') * @property {MuxedStream} stream * @property {string} protocol * + * @typedef {{ [key: string]: function (Uint8Array, Uint8Array[]): number }} DhtSelectors + * @typedef {{ [key: string]: { func: (key: Uint8Array, value: Uint8Array) => Promise }}} DhtValidators + * * @typedef {Object} RandomWalkOptions * @property {boolean} [enabled = false] * @property {number} [queriesPerPeriod = 1] @@ -67,6 +70,8 @@ const { updateSelfPeerRecord } = require('./record/utils') * @property {number} [kBucketSize = 20] * @property {RandomWalkOptions} [randomWalk] * @property {boolean} [clientMode] + * @property {DhtSelectors} [selectors] + * @property {DhtValidators} [validators] * * @typedef {Object} KeychainOptions * @property {Datastore} [datastore] diff --git a/test/ts-use/package.json b/test/ts-use/package.json index 106551f195..0d02583bb8 100644 --- a/test/ts-use/package.json +++ b/test/ts-use/package.json @@ -13,6 +13,7 @@ "libp2p-kad-dht": "^0.21.0", "libp2p-mplex": "^0.10.2", "libp2p-noise": "^2.0.5", + "libp2p-record": "^0.10.2", "libp2p-tcp": "^0.15.3", "libp2p-websockets": "^0.15.3", "peer-id": "^0.14.3" diff --git a/test/ts-use/src/main.ts b/test/ts-use/src/main.ts index 4dc4efe4cb..a1d2ac61a4 100644 --- a/test/ts-use/src/main.ts +++ b/test/ts-use/src/main.ts @@ -1,4 +1,5 @@ import Libp2p = require('libp2p') +import Libp2pRecord = require('libp2p-record') const TCP = require('libp2p-tcp') const WEBSOCKETS = require('libp2p-websockets') @@ -22,6 +23,7 @@ const DelegatedPeerRouter = require('libp2p-delegated-peer-routing') const DelegatedContentRouter = require('libp2p-delegated-content-routing') const PeerId = require('peer-id') + // Known peers addresses const bootstrapMultiaddrs = [ '/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb', @@ -125,6 +127,13 @@ async function main() { enabled: true, // Allows to disable discovery (enabled by default) interval: 300e3, timeout: 10e3 + }, + clientMode: true, + validators: { + pk: Libp2pRecord.validator.validators.pk + }, + selectors: { + pk: Libp2pRecord.selection.selectors.pk } }, nat: { From 086b0ec0df2fac93845d0a0a6b2e2464e869afcd Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 21 Apr 2021 13:55:21 +0100 Subject: [PATCH 166/447] fix: demand pubsub subclass instead of pubsub instance (#922) Changes the `Libp2pModules.pubsub` property to be a class that maybe extends `PubsubBaseProtocol` instead of an instance of that class. --- src/index.js | 2 +- src/pubsub-adapter.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 4d063be9cb..5e573b2577 100644 --- a/src/index.js +++ b/src/index.js @@ -107,7 +107,7 @@ const { updateSelfPeerRecord } = require('./record/utils') * @property {PeerRoutingModule[]} [peerRouting] * @property {ContentRoutingModule[]} [contentRouting] * @property {Object} [dht] - * @property {Pubsub} [pubsub] + * @property {{new(...args: any[]): Pubsub}} [pubsub] * @property {Protector} [connProtector] * * @typedef {Object} Libp2pOptions diff --git a/src/pubsub-adapter.js b/src/pubsub-adapter.js index e965ab5c4b..8ccbf90f48 100644 --- a/src/pubsub-adapter.js +++ b/src/pubsub-adapter.js @@ -7,12 +7,13 @@ */ /** - * @param {import("libp2p-interfaces/src/pubsub")} PubsubRouter + * @param {{new(...args: any[]): PubsubRouter}} PubsubRouter * @param {import('.')} libp2p * @param {{ enabled: boolean; } & import(".").PubsubLocalOptions & import("libp2p-interfaces/src/pubsub").PubsubOptions} options */ function pubsubAdapter (PubsubRouter, libp2p, options) { - // @ts-ignore Pubsub constructor type not defined + /** @type {PubsubRouter & { _subscribeAdapter: PubsubRouter['subscribe'], _unsubscribeAdapter: PubsubRouter['unsubscribe'] }} */ + // @ts-ignore we set the extra _subscribeAdapter and _unsubscribeAdapter properties afterwards const pubsub = new PubsubRouter(libp2p, options) pubsub._subscribeAdapter = pubsub.subscribe pubsub._unsubscribeAdapter = pubsub.unsubscribe From 54e502afcb894ca312cd34a8589b0c9ee4dc3b23 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 21 Apr 2021 15:01:31 +0200 Subject: [PATCH 167/447] chore: update contributors --- package.json | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index 56f507b49f..2c8cb55ac5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.31.0-rc.4", + "version": "0.31.0-rc.5", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", @@ -172,8 +172,8 @@ }, "contributors": [ "David Dias ", - "Jacob Heun ", "Vasco Santos ", + "Jacob Heun ", "Alex Potsides ", "Alan Shaw ", "Cayman ", @@ -181,49 +181,50 @@ "Friedel Ziegelmayer ", "Maciej Krüger ", "Hugo Dias ", - "Volker Mische ", "dirkmc ", + "Volker Mische ", "Richard Littauer ", "a1300 ", "Elven ", - "Andrew Nesbitt ", - "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", "Giovanni T. Parra ", - "Ryan Bell ", + "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", "Thomas Eizinger ", "Samlior ", - "Didrik Nordström ", - "Julien Bouquillon ", - "Kevin Kwok ", - "Kevin Lacker ", - "Miguel Mota ", + "Ryan Bell ", + "Andrew Nesbitt ", + "Didrik Nordström ", "Nuno Nogueira ", "Philipp Muens ", - "RasmusErik Voel Jensen ", "Smite Chow ", "Soeren ", "Sönke Hahn ", "TJKoury ", "Tiago Alves ", - "Daijiro Wachi ", - "Cindy Wu ", - "Chris Bratlien ", "Yusef Napora ", "Zane Starr ", - "Bernd Strehl ", "ebinks ", - "Ethan Lam ", "isan_rivkin ", "robertkiel ", + "RasmusErik Voel Jensen ", "Aleksei ", + "Bernd Strehl ", + "Chris Bratlien ", + "Cindy Wu ", + "Daijiro Wachi ", + "Diogo Silva ", + "Dmitriy Ryajov ", + "Ethan Lam ", "Fei Liu ", "Felipe Martins ", "Florian-Merle ", "Francis Gulotta ", - "Dmitriy Ryajov ", + "Franck Royer ", "Henrique Dias ", "Irakli Gozalishvili ", - "Diogo Silva ", - "Joel Gustafson " + "Joel Gustafson ", + "Julien Bouquillon ", + "Kevin Kwok ", + "Kevin Lacker ", + "Miguel Mota " ] } From 64f3af897bd6dae62894dcc355077b8e18935f3a Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 21 Apr 2021 15:01:31 +0200 Subject: [PATCH 168/447] chore: release version v0.31.0-rc.5 --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4653af161b..6b9bcd6e6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# [0.31.0-rc.5](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.4...v0.31.0-rc.5) (2021-04-21) + + +### Bug Fixes + +* address book should not emit peer event if no addresses are known ([b4fb9b7](https://github.com/libp2p/js-libp2p/commit/b4fb9b7bf266ba03c4462c0a41b1c2691e4e88d4)) +* demand pubsub subclass instead of pubsub instance ([#922](https://github.com/libp2p/js-libp2p/issues/922)) ([086b0ec](https://github.com/libp2p/js-libp2p/commit/086b0ec0df2fac93845d0a0a6b2e2464e869afcd)) +* dht configuration selectors and validators ([#919](https://github.com/libp2p/js-libp2p/issues/919)) ([cc1f4af](https://github.com/libp2p/js-libp2p/commit/cc1f4af879a58e94538591851d0085ff98cd2641)) + + + # [0.31.0-rc.4](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.3...v0.31.0-rc.4) (2021-04-20) From 88b04156bf614650c2b14d49b12e969c5eecf04d Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 22 Apr 2021 09:53:55 +0200 Subject: [PATCH 169/447] fix: keychain optional pw and use interfaces for validators and selectors instead (#924) --- package.json | 2 +- src/index.js | 7 ++----- src/keychain/index.js | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 2c8cb55ac5..ed0662c6f7 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "it-pipe": "^1.1.0", "it-take": "1.0.0", "libp2p-crypto": "^0.19.0", - "libp2p-interfaces": "^0.10.1", + "libp2p-interfaces": "^0.10.3", "libp2p-utils": "^0.3.1", "mafmt": "^9.0.0", "merge-options": "^3.0.4", diff --git a/src/index.js b/src/index.js index 5e573b2577..deccf80aaf 100644 --- a/src/index.js +++ b/src/index.js @@ -56,9 +56,6 @@ const { updateSelfPeerRecord } = require('./record/utils') * @property {MuxedStream} stream * @property {string} protocol * - * @typedef {{ [key: string]: function (Uint8Array, Uint8Array[]): number }} DhtSelectors - * @typedef {{ [key: string]: { func: (key: Uint8Array, value: Uint8Array) => Promise }}} DhtValidators - * * @typedef {Object} RandomWalkOptions * @property {boolean} [enabled = false] * @property {number} [queriesPerPeriod = 1] @@ -70,8 +67,8 @@ const { updateSelfPeerRecord } = require('./record/utils') * @property {number} [kBucketSize = 20] * @property {RandomWalkOptions} [randomWalk] * @property {boolean} [clientMode] - * @property {DhtSelectors} [selectors] - * @property {DhtValidators} [validators] + * @property {import('libp2p-interfaces/src/types').DhtSelectors} [selectors] + * @property {import('libp2p-interfaces/src/types').DhtValidators} [validators] * * @typedef {Object} KeychainOptions * @property {Datastore} [datastore] diff --git a/src/keychain/index.js b/src/keychain/index.js index 0cf13d0515..05c187a412 100644 --- a/src/keychain/index.js +++ b/src/keychain/index.js @@ -26,7 +26,7 @@ require('node-forge/lib/sha512') * @property {number} keyLength * * @typedef {Object} KeychainOptions - * @property {string} pass + * @property {string} [pass] * @property {DekOptions} [dek] */ From 97da0ba740cd2b210932cd1e324b3519e7382e3c Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 22 Apr 2021 10:09:18 +0200 Subject: [PATCH 170/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ed0662c6f7..13698f2f1a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.31.0-rc.5", + "version": "0.31.0-rc.6", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From 5372f7af2f8f1653384504af15efea8ba776534b Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 22 Apr 2021 10:09:18 +0200 Subject: [PATCH 171/447] chore: release version v0.31.0-rc.6 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b9bcd6e6b..0b0029a4ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# [0.31.0-rc.6](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.5...v0.31.0-rc.6) (2021-04-22) + + +### Bug Fixes + +* keychain optional pw and use interfaces for validators and selectors instead ([#924](https://github.com/libp2p/js-libp2p/issues/924)) ([88b0415](https://github.com/libp2p/js-libp2p/commit/88b04156bf614650c2b14d49b12e969c5eecf04d)) + + + # [0.31.0-rc.5](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.4...v0.31.0-rc.5) (2021-04-21) From f23fd4b7c70425111322457e03bdcfa5fc17c292 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 27 Apr 2021 13:24:31 +0200 Subject: [PATCH 172/447] chore: update noise (#926) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 13698f2f1a..a109be3ddf 100644 --- a/package.json +++ b/package.json @@ -156,7 +156,7 @@ "libp2p-kad-dht": "^0.21.0", "libp2p-mdns": "^0.16.0", "libp2p-mplex": "^0.10.1", - "libp2p-noise": "^2.0.0", + "libp2p-noise": "^3.0.0", "libp2p-tcp": "^0.15.1", "libp2p-webrtc-star": "^0.22.0", "libp2p-websockets": "^0.15.0", From ef4393649f113580afb0838a3ed04cf452c1bdcc Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 27 Apr 2021 17:13:34 +0200 Subject: [PATCH 173/447] chore: remove chai deps (#929) --- package.json | 4 +--- test/keychain/cms-interop.spec.js | 3 +-- test/keychain/keychain.spec.js | 3 +-- test/peer-store/key-book.spec.js | 3 +-- test/peer-store/metadata-book.spec.js | 3 +-- test/peer-store/peer-store.node.js | 3 +-- test/record/envelope.spec.js | 3 +-- 7 files changed, 7 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index a109be3ddf..f3e6064ed6 100644 --- a/package.json +++ b/package.json @@ -136,10 +136,8 @@ "@types/node-forge": "^0.9.7", "@types/varint": "^6.0.0", "abortable-iterator": "^3.0.0", - "aegir": "^33.0.0", + "aegir": "^33.1.1", "buffer": "^6.0.3", - "chai-bytes": "^0.1.2", - "chai-string": "^1.5.0", "delay": "^5.0.0", "interop-libp2p": "^0.3.0", "into-stream": "^6.0.0", diff --git a/test/keychain/cms-interop.spec.js b/test/keychain/cms-interop.spec.js index 546d163c79..c21b6305e0 100644 --- a/test/keychain/cms-interop.spec.js +++ b/test/keychain/cms-interop.spec.js @@ -2,8 +2,7 @@ /* eslint-env mocha */ 'use strict' -const { chai, expect } = require('aegir/utils/chai') -chai.use(require('chai-string')) +const { expect } = require('aegir/utils/chai') const uint8ArrayFromString = require('uint8arrays/from-string') const uint8ArrayToString = require('uint8arrays/to-string') const { MemoryDatastore } = require('interface-datastore') diff --git a/test/keychain/keychain.spec.js b/test/keychain/keychain.spec.js index 55e1511097..99ade94224 100644 --- a/test/keychain/keychain.spec.js +++ b/test/keychain/keychain.spec.js @@ -2,9 +2,8 @@ /* eslint-env mocha */ 'use strict' -const { chai, expect } = require('aegir/utils/chai') +const { expect } = require('aegir/utils/chai') const fail = expect.fail -chai.use(require('chai-string')) const uint8ArrayFromString = require('uint8arrays/from-string') const uint8ArrayToString = require('uint8arrays/to-string') diff --git a/test/peer-store/key-book.spec.js b/test/peer-store/key-book.spec.js index af41a334e1..4e06ea2f4d 100644 --- a/test/peer-store/key-book.spec.js +++ b/test/peer-store/key-book.spec.js @@ -1,8 +1,7 @@ 'use strict' /* eslint-env mocha */ -const { chai, expect } = require('aegir/utils/chai') -chai.use(require('chai-bytes')) +const { expect } = require('aegir/utils/chai') const sinon = require('sinon') const PeerStore = require('../../src/peer-store') diff --git a/test/peer-store/metadata-book.spec.js b/test/peer-store/metadata-book.spec.js index 155b220599..e75ae419bd 100644 --- a/test/peer-store/metadata-book.spec.js +++ b/test/peer-store/metadata-book.spec.js @@ -1,8 +1,7 @@ 'use strict' /* eslint-env mocha */ -const { chai, expect } = require('aegir/utils/chai') -chai.use(require('chai-bytes')) +const { expect } = require('aegir/utils/chai') const uint8ArrayFromString = require('uint8arrays/from-string') const pDefer = require('p-defer') diff --git a/test/peer-store/peer-store.node.js b/test/peer-store/peer-store.node.js index 794de82c2f..d7a462ad2c 100644 --- a/test/peer-store/peer-store.node.js +++ b/test/peer-store/peer-store.node.js @@ -1,8 +1,7 @@ 'use strict' /* eslint-env mocha */ -const { chai, expect } = require('aegir/utils/chai') -chai.use(require('chai-bytes')) +const { expect } = require('aegir/utils/chai') const sinon = require('sinon') const baseOptions = require('../utils/base-options') diff --git a/test/record/envelope.spec.js b/test/record/envelope.spec.js index d95a925ecb..a99ad834c5 100644 --- a/test/record/envelope.spec.js +++ b/test/record/envelope.spec.js @@ -1,8 +1,7 @@ 'use strict' /* eslint-env mocha */ -const { chai, expect } = require('aegir/utils/chai') -chai.use(require('chai-bytes')) +const { expect } = require('aegir/utils/chai') const uint8arrayFromString = require('uint8arrays/from-string') const uint8arrayEquals = require('uint8arrays/equals') const Envelope = require('../../src/record/envelope') From ac370fc9679b51da8cee3791b6dd268d0695d136 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 27 Apr 2021 17:14:48 +0200 Subject: [PATCH 174/447] fix: address book guarantees no replicated entries are added (#927) --- src/peer-store/address-book.js | 11 +++++++---- test/peer-store/address-book.spec.js | 8 ++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/peer-store/address-book.js b/src/peer-store/address-book.js index f04ea453de..eda90bdcc3 100644 --- a/src/peer-store/address-book.js +++ b/src/peer-store/address-book.js @@ -307,10 +307,13 @@ class AddressBook extends Book { throw errcode(new Error(`multiaddr ${addr} must be an instance of multiaddr`), ERR_INVALID_PARAMETERS) } - addresses.push({ - multiaddr: addr, - isCertified - }) + // Guarantee no replicates + if (!addresses.find((a) => a.multiaddr.equals(addr))) { + addresses.push({ + multiaddr: addr, + isCertified + }) + } }) return addresses diff --git a/test/peer-store/address-book.spec.js b/test/peer-store/address-book.spec.js index 512fc8fd35..ea71ed0b32 100644 --- a/test/peer-store/address-book.spec.js +++ b/test/peer-store/address-book.spec.js @@ -287,6 +287,14 @@ describe('addressBook', () => { await defer.promise }) + + it('does not add replicated content', () => { + // set 1 + ab.set(peerId, [addr1, addr1]) + + const addresses = ab.get(peerId) + expect(addresses).to.have.lengthOf(1) + }) }) describe('addressBook.get', () => { From ed494f03ae460270f14621bbb0f85f70c4ce6745 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 27 Apr 2021 17:18:12 +0200 Subject: [PATCH 175/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f3e6064ed6..6e45e8b441 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.31.0-rc.6", + "version": "0.31.0-rc.7", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From 5282708263df4894e222445e501686b27d00c16e Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 27 Apr 2021 17:18:12 +0200 Subject: [PATCH 176/447] chore: release version v0.31.0-rc.7 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b0029a4ce..de2813a2d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# [0.31.0-rc.7](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.6...v0.31.0-rc.7) (2021-04-27) + + +### Bug Fixes + +* address book guarantees no replicated entries are added ([#927](https://github.com/libp2p/js-libp2p/issues/927)) ([ac370fc](https://github.com/libp2p/js-libp2p/commit/ac370fc9679b51da8cee3791b6dd268d0695d136)) + + + # [0.31.0-rc.6](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.5...v0.31.0-rc.6) (2021-04-22) From e9543eb2e191265ac0ec77c5a96c9ef3f4d9dc57 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 28 Apr 2021 15:39:48 +0200 Subject: [PATCH 177/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6e45e8b441..be73d51f7c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.31.0-rc.7", + "version": "0.31.0", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From d76356e56a1e46726eeb5abda14a132e85837261 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 28 Apr 2021 15:39:48 +0200 Subject: [PATCH 178/447] chore: release version v0.31.0 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index de2813a2d2..b3360af987 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# [0.31.0](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.7...v0.31.0) (2021-04-28) + + + # [0.31.0-rc.7](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.6...v0.31.0-rc.7) (2021-04-27) From 2572f3e034ba1f87428a3adf4352b7da99c23267 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 29 Apr 2021 10:10:57 +0200 Subject: [PATCH 179/447] chore: update libp2p dht and gossipsub to releases (#933) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index be73d51f7c..4ee93ca446 100644 --- a/package.json +++ b/package.json @@ -150,8 +150,8 @@ "libp2p-delegated-content-routing": "^0.10.0", "libp2p-delegated-peer-routing": "^0.9.0", "libp2p-floodsub": "^0.25.0", - "libp2p-gossipsub": "^0.8.0", - "libp2p-kad-dht": "^0.21.0", + "libp2p-gossipsub": "^0.9.0", + "libp2p-kad-dht": "^0.22.0", "libp2p-mdns": "^0.16.0", "libp2p-mplex": "^0.10.1", "libp2p-noise": "^3.0.0", From f860ffb3e7b36c8c9a49cf1f0621552f8085ee55 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 30 Apr 2021 09:30:41 +0200 Subject: [PATCH 180/447] chore: add migration guide to 0.31 (#912) --- doc/migrations/v0.30-v0.31.md | 123 ++++++++++++++++++++++++++++++++++ package.json | 8 +-- 2 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 doc/migrations/v0.30-v0.31.md diff --git a/doc/migrations/v0.30-v0.31.md b/doc/migrations/v0.30-v0.31.md new file mode 100644 index 0000000000..8ccb12d1ba --- /dev/null +++ b/doc/migrations/v0.30-v0.31.md @@ -0,0 +1,123 @@ + +# Migrating to libp2p@31 + +A migration guide for refactoring your application code from libp2p v0.30.x to v0.31.0. + +## Table of Contents + +- [Types](#types) +- [API](#api) +- [Module Updates](#module-updates) + +## Types + +Most of the type definitions in the libp2p configuration were `any` or were not included before this release. This might cause breaking changes on upstream projects relying on the previous provided types, as well as to libp2p modules implemented by the libp2p community. + +## API + +### Core API + +`libp2p.dialProtocol` does not accept empty or null protocols returning a connection anymore and `dial` must be used instead. + +```js +const connection = await libp2p.dialProtocol(peerId) +``` + +**After** + +```js +const connection = await libp2p.dial(peerId) +``` + +### Connection Manager Options + +We updated the connection manager options naming in `libp2p@0.29` but kept it backward compatible until now. + +**Before** + +```js +const node = await Libp2p.create({ + connectionManager: { + minPeers: 0 + } +}) +``` + +**After** + +```js +const node = await Libp2p.create({ + connectionManager: { + minConnections: 0 + } +}) +``` + +You can see full details on how to configure the connection manager [here](https://github.com/libp2p/js-libp2p/blob/master/doc/CONFIGURATION.md#configuring-connection-manager). + +### Dialer and Keychain components + +Internal property names to create a libp2p `Dialer` and `Keychain` were updated to reflect the properties naming in the libp2p configuration. These are internal modules of libp2p core and should not impact most of the users, but as it is possible to use them separately here follow the changes: + +***Before** + +```js +const dialer = new Dialer({ + transportManager, + peerStore, + concurrency, + perPeerLimit, + timeout, + resolvers, + addressSorter +}) + +const keychain = new Keychain(datastore, { + passPhrase +}) +``` + +**After** + +```js +this.dialer = new Dialer({ + transportManager, + peerStore, + maxParallelDials, + maxDialsPerPeer, + dialTimeout, + resolvers, + addressSorter +}) + +const keychain = new Keychain(datastore, { + pass +}) +``` + +## Module Updates + +With this release you should update the following libp2p modules if you are relying on them: + + + +```json +"libp2p-bootstrap": "^0.12.3", +"libp2p-crypto": "^0.19.4", +"libp2p-interfaces": "^0.10.0", +"libp2p-delegated-content-routing": "^0.10.0", +"libp2p-delegated-peer-routing": "^0.9.0", +"libp2p-floodsub": "^0.25.1", +"libp2p-gossipsub": "^0.9.0", +"libp2p-kad-dht": "^0.22.0", +"libp2p-mdns": "^0.16.0", +"libp2p-noise": "^3.0.0", +"libp2p-tcp": "^0.15.4", +"libp2p-webrtc-star": "^0.22.2", +"libp2p-websockets": "^0.15.6" +``` + +One of the main changes in this new release is the update to `multiaddr@9.0.0`. This should also be updated in upstream projects to avoid several multiaddr versions in the bundle and to avoid potential problems when libp2p interacts with provided outdated multiaddr instances. diff --git a/package.json b/package.json index 4ee93ca446..0d7dd61ebf 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "it-merge": "1.0.0", "it-pipe": "^1.1.0", "it-take": "1.0.0", - "libp2p-crypto": "^0.19.0", + "libp2p-crypto": "^0.19.4", "libp2p-interfaces": "^0.10.3", "libp2p-utils": "^0.3.1", "mafmt": "^9.0.0", @@ -155,9 +155,9 @@ "libp2p-mdns": "^0.16.0", "libp2p-mplex": "^0.10.1", "libp2p-noise": "^3.0.0", - "libp2p-tcp": "^0.15.1", - "libp2p-webrtc-star": "^0.22.0", - "libp2p-websockets": "^0.15.0", + "libp2p-tcp": "^0.15.4", + "libp2p-webrtc-star": "^0.22.2", + "libp2p-websockets": "^0.15.6", "multihashes": "^4.0.2", "nock": "^13.0.3", "p-defer": "^3.0.0", From 302bb9005891aa06b70a5f354bfac6b2d5a3c3b8 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 30 Apr 2021 13:20:12 +0200 Subject: [PATCH 181/447] fix: event emitter and interfaces types for discovery and routing (#934) --- package.json | 4 ++-- src/address-manager/index.js | 2 +- src/connection-manager/index.js | 2 +- src/connection-manager/latency-monitor.js | 2 +- src/connection-manager/visibility-change-emitter.js | 2 +- src/content-routing/index.js | 2 +- src/index.js | 12 ++++++------ src/metrics/stats.js | 4 ++-- src/peer-routing.js | 2 +- src/peer-store/index.js | 2 +- src/upgrader.js | 7 ++++++- 11 files changed, 23 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 0d7dd61ebf..7431e5756a 100644 --- a/package.json +++ b/package.json @@ -104,11 +104,11 @@ "it-pipe": "^1.1.0", "it-take": "1.0.0", "libp2p-crypto": "^0.19.4", - "libp2p-interfaces": "^0.10.3", + "libp2p-interfaces": "^0.10.4", "libp2p-utils": "^0.3.1", "mafmt": "^9.0.0", "merge-options": "^3.0.4", - "moving-average": "^1.0.0", + "@vascosantos/moving-average": "^1.1.0", "multiaddr": "^9.0.1", "multicodec": "^3.0.1", "multihashing-async": "^2.1.2", diff --git a/src/address-manager/index.js b/src/address-manager/index.js index bc4024c4be..25de94b61f 100644 --- a/src/address-manager/index.js +++ b/src/address-manager/index.js @@ -1,6 +1,6 @@ 'use strict' -const EventEmitter = require('events') +const { EventEmitter } = require('events') const { Multiaddr } = require('multiaddr') const PeerId = require('peer-id') diff --git a/src/connection-manager/index.js b/src/connection-manager/index.js index 985d26f145..7a865127b0 100644 --- a/src/connection-manager/index.js +++ b/src/connection-manager/index.js @@ -11,7 +11,7 @@ const LatencyMonitor = require('./latency-monitor') // @ts-ignore retimer does not have types const retimer = require('retimer') -const EventEmitter = require('events') +const { EventEmitter } = require('events') const PeerId = require('peer-id') diff --git a/src/connection-manager/latency-monitor.js b/src/connection-manager/latency-monitor.js index 5253c2ae71..6c3061b91b 100644 --- a/src/connection-manager/latency-monitor.js +++ b/src/connection-manager/latency-monitor.js @@ -5,7 +5,7 @@ * This code is based on `latency-monitor` (https://github.com/mlucool/latency-monitor) by `mlucool` (https://github.com/mlucool), available under Apache License 2.0 (https://github.com/mlucool/latency-monitor/blob/master/LICENSE) */ -const EventEmitter = require('events') +const { EventEmitter } = require('events') const VisibilityChangeEmitter = require('./visibility-change-emitter') const debug = require('debug')('latency-monitor:LatencyMonitor') diff --git a/src/connection-manager/visibility-change-emitter.js b/src/connection-manager/visibility-change-emitter.js index 9efb6ffb33..ebe5e7d076 100644 --- a/src/connection-manager/visibility-change-emitter.js +++ b/src/connection-manager/visibility-change-emitter.js @@ -6,7 +6,7 @@ */ 'use strict' -const EventEmitter = require('events') +const { EventEmitter } = require('events') const debug = require('debug')('latency-monitor:VisibilityChangeEmitter') diff --git a/src/content-routing/index.js b/src/content-routing/index.js index 44f1d9c580..00211f71ff 100644 --- a/src/content-routing/index.js +++ b/src/content-routing/index.js @@ -16,7 +16,7 @@ const { pipe } = require('it-pipe') * @typedef {import('peer-id')} PeerId * @typedef {import('multiaddr').Multiaddr} Multiaddr * @typedef {import('cids')} CID - * @typedef {import('libp2p-interfaces/src/content-routing/types')} ContentRoutingModule + * @typedef {import('libp2p-interfaces/src/content-routing/types').ContentRouting} ContentRoutingModule */ /** diff --git a/src/index.js b/src/index.js index deccf80aaf..999bcb2685 100644 --- a/src/index.js +++ b/src/index.js @@ -4,7 +4,7 @@ const debug = require('debug') const log = Object.assign(debug('libp2p'), { error: debug('libp2p:err') }) -const EventEmitter = require('events') +const { EventEmitter } = require('events') const errCode = require('err-code') const PeerId = require('peer-id') @@ -40,9 +40,9 @@ const { updateSelfPeerRecord } = require('./record/utils') * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream * @typedef {import('libp2p-interfaces/src/transport/types').TransportFactory} TransportFactory * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxerFactory} MuxerFactory - * @typedef {import('libp2p-interfaces/src/content-routing/types')} ContentRoutingModule - * @typedef {import('libp2p-interfaces/src/peer-discovery/types')} PeerDiscoveryModule - * @typedef {import('libp2p-interfaces/src/peer-routing/types')} PeerRoutingModule + * @typedef {import('libp2p-interfaces/src/content-routing/types').ContentRouting} ContentRoutingModule + * @typedef {import('libp2p-interfaces/src/peer-discovery/types').PeerDiscoveryFactory} PeerDiscoveryFactory + * @typedef {import('libp2p-interfaces/src/peer-routing/types').PeerRouting} PeerRoutingModule * @typedef {import('libp2p-interfaces/src/crypto/types').Crypto} Crypto * @typedef {import('libp2p-interfaces/src/pubsub')} Pubsub * @typedef {import('libp2p-interfaces/src/pubsub').PubsubOptions} PubsubOptions @@ -100,7 +100,7 @@ const { updateSelfPeerRecord } = require('./record/utils') * @property {TransportFactory[]} transport * @property {MuxerFactory[]} streamMuxer * @property {Crypto[]} connEncryption - * @property {PeerDiscoveryModule[]} [peerDiscovery] + * @property {PeerDiscoveryFactory[]} [peerDiscovery] * @property {PeerRoutingModule[]} [peerRouting] * @property {ContentRoutingModule[]} [contentRouting] * @property {Object} [dht] @@ -714,7 +714,7 @@ class Libp2p extends EventEmitter { */ async _setupPeerDiscovery () { /** - * @param {PeerDiscoveryModule} DiscoveryService + * @param {PeerDiscoveryFactory} DiscoveryService */ const setupService = (DiscoveryService) => { let config = { diff --git a/src/metrics/stats.js b/src/metrics/stats.js index 8d76140031..23056fac2a 100644 --- a/src/metrics/stats.js +++ b/src/metrics/stats.js @@ -1,9 +1,9 @@ // @ts-nocheck 'use strict' -const EventEmitter = require('events') +const { EventEmitter } = require('events') const { BigNumber: Big } = require('bignumber.js') -const MovingAverage = require('moving-average') +const MovingAverage = require('@vascosantos/moving-average') const retimer = require('retimer') class Stats extends EventEmitter { diff --git a/src/peer-routing.js b/src/peer-routing.js index e280936182..0ff6f7b8ce 100644 --- a/src/peer-routing.js +++ b/src/peer-routing.js @@ -25,7 +25,7 @@ const { /** * @typedef {import('peer-id')} PeerId * @typedef {import('multiaddr').Multiaddr} Multiaddr - * @typedef {import('libp2p-interfaces/src/peer-routing/types')} PeerRoutingModule + * @typedef {import('libp2p-interfaces/src/peer-routing/types').PeerRouting} PeerRoutingModule */ /** diff --git a/src/peer-store/index.js b/src/peer-store/index.js index 0f2f989f79..b3df1bbb94 100644 --- a/src/peer-store/index.js +++ b/src/peer-store/index.js @@ -2,7 +2,7 @@ const errcode = require('err-code') -const EventEmitter = require('events') +const { EventEmitter } = require('events') const PeerId = require('peer-id') const AddressBook = require('./address-book') diff --git a/src/upgrader.js b/src/upgrader.js index cdf4706c46..8ee6c65412 100644 --- a/src/upgrader.js +++ b/src/upgrader.js @@ -137,7 +137,12 @@ class Upgrader { * @returns {Promise} */ async upgradeOutbound (maConn) { - const remotePeerId = PeerId.createFromB58String(maConn.remoteAddr.getPeerId()) + const idStr = maConn.remoteAddr.getPeerId() + if (!idStr) { + throw errCode(new Error('outbound connection must have a peer id'), codes.ERR_INVALID_MULTIADDR) + } + + const remotePeerId = PeerId.createFromB58String(idStr) let encryptedConn let remotePeer From 150e4f97c1209130d017d44a670e8e29617a518e Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 30 Apr 2021 13:40:28 +0200 Subject: [PATCH 182/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7431e5756a..83ce76c28f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.31.0", + "version": "0.31.1", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From e5187d02baab59df8be7c328db3daa8cc67b611c Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 30 Apr 2021 13:40:29 +0200 Subject: [PATCH 183/447] chore: release version v0.31.1 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3360af987..3a4ea9b974 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.31.1](https://github.com/libp2p/js-libp2p/compare/v0.31.0...v0.31.1) (2021-04-30) + + +### Bug Fixes + +* event emitter and interfaces types for discovery and routing ([#934](https://github.com/libp2p/js-libp2p/issues/934)) ([302bb90](https://github.com/libp2p/js-libp2p/commit/302bb9005891aa06b70a5f354bfac6b2d5a3c3b8)) + + + # [0.31.0](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.7...v0.31.0) (2021-04-28) From b5a9eb208763efa027d0b4caae87c515b6f5869b Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 30 Apr 2021 15:42:34 +0200 Subject: [PATCH 184/447] fix: moving averages record types (#935) --- src/metrics/stats.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/metrics/stats.js b/src/metrics/stats.js index 23056fac2a..267f3a2ed3 100644 --- a/src/metrics/stats.js +++ b/src/metrics/stats.js @@ -6,6 +6,10 @@ const { BigNumber: Big } = require('bignumber.js') const MovingAverage = require('@vascosantos/moving-average') const retimer = require('retimer') +/** + * @typedef {import('@vascosantos/moving-average').IMovingAverage} IMovingAverage + */ + class Stats extends EventEmitter { /** * A queue based manager for stat processing @@ -29,7 +33,7 @@ class Stats extends EventEmitter { this._frequencyLastTime = Date.now() this._frequencyAccumulators = {} - /** @type {{ dataReceived: MovingAverage[], dataSent: MovingAverage[] }} */ + /** @type {{ dataReceived: IMovingAverage[], dataSent: IMovingAverage[] }} */ this._movingAverages = {} this._update = this._update.bind(this) From 556f0203dbbbd08d8502252845c65fb2c3628cb4 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 30 Apr 2021 15:45:44 +0200 Subject: [PATCH 185/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 83ce76c28f..a655992d10 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.31.1", + "version": "0.31.2", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From 924585b143c3f375216ce5ec7c7ec4cf485625b0 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 30 Apr 2021 15:45:45 +0200 Subject: [PATCH 186/447] chore: release version v0.31.2 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a4ea9b974..dc609f5521 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.31.2](https://github.com/libp2p/js-libp2p/compare/v0.31.1...v0.31.2) (2021-04-30) + + +### Bug Fixes + +* moving averages record types ([#935](https://github.com/libp2p/js-libp2p/issues/935)) ([b5a9eb2](https://github.com/libp2p/js-libp2p/commit/b5a9eb208763efa027d0b4caae87c515b6f5869b)) + + + ## [0.31.1](https://github.com/libp2p/js-libp2p/compare/v0.31.0...v0.31.1) (2021-04-30) From 8fc6f8af81b1da83d7411422ae39a2a8bd1fda1b Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 4 May 2021 11:14:11 +0100 Subject: [PATCH 187/447] chore: update ipfs-utils dep (#937) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a655992d10..7d6fda0664 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "events": "^3.3.0", "hashlru": "^2.3.0", "interface-datastore": "^4.0.0", - "ipfs-utils": "^6.0.0", + "ipfs-utils": "^7.0.0", "it-all": "^1.0.4", "it-buffer": "^0.1.2", "it-drain": "^1.0.3", From 2fa82b387c40d36ffc87757207d27eb8e805cf19 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 4 May 2021 12:23:57 +0200 Subject: [PATCH 188/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7d6fda0664..5a5354f272 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.31.2", + "version": "0.31.3", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From 4e3fc19623a8658bc7e97bfd2c03ca9dac15fdf8 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 4 May 2021 12:23:58 +0200 Subject: [PATCH 189/447] chore: release version v0.31.3 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc609f5521..6918dadacf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [0.31.3](https://github.com/libp2p/js-libp2p/compare/v0.31.2...v0.31.3) (2021-05-04) + + + ## [0.31.2](https://github.com/libp2p/js-libp2p/compare/v0.31.1...v0.31.2) (2021-04-30) From d372a68692d2037aefcec7c7ef9d29022ce8362b Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 5 May 2021 15:06:56 +0200 Subject: [PATCH 190/447] chore: add github issue templates (#938) --- .github/ISSUE_TEMPLATE/config.yml | 5 ++ .github/ISSUE_TEMPLATE/open_an_issue.md | 55 +++++++++++++++++++ .../ISSUE_TEMPLATE/release.md | 1 - 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/open_an_issue.md rename RELEASE.md => .github/ISSUE_TEMPLATE/release.md (96%) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..8ddb742471 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: libp2p Official Forum + url: https://discuss.libp2p.io + about: For general questions, support requests and discussions diff --git a/.github/ISSUE_TEMPLATE/open_an_issue.md b/.github/ISSUE_TEMPLATE/open_an_issue.md new file mode 100644 index 0000000000..52e9e704e8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/open_an_issue.md @@ -0,0 +1,55 @@ +--- +name: Open an issue +about: For reporting bugs or errors in the JavaScript libp2p implementation +title: '' +labels: need/triage +assignees: '' +--- + + + +- **Version**: + + +- **Platform**: + + +- **Subsystem**: + + +#### Severity: + + +#### Description: + + +#### Steps to reproduce the error: + + diff --git a/RELEASE.md b/.github/ISSUE_TEMPLATE/release.md similarity index 96% rename from RELEASE.md rename to .github/ISSUE_TEMPLATE/release.md index 33991ebf36..aa5f446fbb 100644 --- a/RELEASE.md +++ b/.github/ISSUE_TEMPLATE/release.md @@ -25,7 +25,6 @@ - [ ] [js-ipfs](https://github.com/ipfs/js-ipfs) - Documentation - [ ] Ensure that README.md is up to date - - [ ] 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 From a79c6b50d7fddbcdb1af53efae922cecad4c9a83 Mon Sep 17 00:00:00 2001 From: zeim839 <50573884+zeim839@users.noreply.github.com> Date: Wed, 12 May 2021 18:46:20 +0400 Subject: [PATCH 191/447] fix: peerRouting.findPeer() trying to find self (#941) * throw error if node attempts to find itself Co-authored-by: Vasco Santos --- src/peer-routing.js | 4 ++++ test/peer-routing/peer-routing.node.js | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/peer-routing.js b/src/peer-routing.js index 0ff6f7b8ce..aaf0674530 100644 --- a/src/peer-routing.js +++ b/src/peer-routing.js @@ -104,6 +104,10 @@ class PeerRouting { throw errCode(new Error('No peer routers available'), 'NO_ROUTERS_AVAILABLE') } + if (id.toB58String() === this._peerId.toB58String()) { + throw errCode(new Error('Should not try to find self'), 'ERR_FIND_SELF') + } + const output = await pipe( merge( ...this._routers.map(router => [router.findPeer(id, options)]) diff --git a/test/peer-routing/peer-routing.node.js b/test/peer-routing/peer-routing.node.js index 5413c3f189..5149c7d45a 100644 --- a/test/peer-routing/peer-routing.node.js +++ b/test/peer-routing/peer-routing.node.js @@ -100,6 +100,12 @@ describe('peer-routing', () => { return deferred.promise }) + + it('should error when peer tries to find itself', async () => { + await expect(nodes[0].peerRouting.findPeer(nodes[0].peerId)) + .to.eventually.be.rejected() + .and.to.have.property('code', 'ERR_FIND_SELF') + }) }) describe('via delegate router', () => { @@ -187,6 +193,12 @@ describe('peer-routing', () => { expect(mockApi.isDone()).to.equal(true) }) + it('should error when peer tries to find itself', async () => { + await expect(node.peerRouting.findPeer(node.peerId)) + .to.eventually.be.rejected() + .and.to.have.property('code', 'ERR_FIND_SELF') + }) + it('should error when a peer cannot be found', async () => { const peerKey = 'key of a peer not on the network' const mockApi = nock('http://0.0.0.0:60197') From 890dd059419fef2c6511792c2cf9a4f637abfef4 Mon Sep 17 00:00:00 2001 From: zeim839 <50573884+zeim839@users.noreply.github.com> Date: Wed, 12 May 2021 18:47:29 +0400 Subject: [PATCH 192/447] replaced libp2p with node (#942) Co-authored-by: zeim839 --- doc/CONFIGURATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index f479cb3210..c3548b7f1b 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -515,7 +515,7 @@ const node = await Libp2p.create({ } }) -await libp2p.loadKeychain() +await node.loadKeychain() ``` #### Configuring Dialing From b29d6c9304a4fc2c6d5911aebe28aeee4a1ffbe9 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 12 May 2021 17:05:35 +0200 Subject: [PATCH 193/447] chore: update contributors --- package.json | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 5a5354f272..c9d265cd3a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.31.3", + "version": "0.31.4", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", @@ -183,46 +183,46 @@ "Volker Mische ", "Richard Littauer ", "a1300 ", + "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", + "Andrew Nesbitt ", "Elven ", "Giovanni T. Parra ", - "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", - "Thomas Eizinger ", - "Samlior ", "Ryan Bell ", - "Andrew Nesbitt ", - "Didrik Nordström ", + "Samlior ", + "Thomas Eizinger ", + "Didrik Nordström ", + "Fei Liu ", + "Ethan Lam ", + "Joel Gustafson ", + "Julien Bouquillon ", + "Kevin Kwok ", + "Kevin Lacker ", + "Dmitriy Ryajov ", + "Miguel Mota ", "Nuno Nogueira ", + "Diogo Silva ", "Philipp Muens ", + "RasmusErik Voel Jensen ", "Smite Chow ", "Soeren ", "Sönke Hahn ", "TJKoury ", + "robertkiel ", "Tiago Alves ", + "Daijiro Wachi ", + "Cindy Wu ", "Yusef Napora ", "Zane Starr ", - "ebinks ", - "isan_rivkin ", - "robertkiel ", - "RasmusErik Voel Jensen ", - "Aleksei ", - "Bernd Strehl ", "Chris Bratlien ", - "Cindy Wu ", - "Daijiro Wachi ", - "Diogo Silva ", - "Dmitriy Ryajov ", - "Ethan Lam ", - "Fei Liu ", - "Felipe Martins ", + "Bernd Strehl ", + "ebinks ", "Florian-Merle ", "Francis Gulotta ", "Franck Royer ", + "Felipe Martins ", + "Aleksei ", "Henrique Dias ", - "Irakli Gozalishvili ", - "Joel Gustafson ", - "Julien Bouquillon ", - "Kevin Kwok ", - "Kevin Lacker ", - "Miguel Mota " + "isan_rivkin ", + "Irakli Gozalishvili " ] } From d163ffd224ff83a378a2249d5feab4a3baf855fd Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 12 May 2021 17:05:35 +0200 Subject: [PATCH 194/447] chore: release version v0.31.4 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6918dadacf..9dbfd999ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.31.4](https://github.com/libp2p/js-libp2p/compare/v0.31.3...v0.31.4) (2021-05-12) + + +### Bug Fixes + +* peerRouting.findPeer() trying to find self ([#941](https://github.com/libp2p/js-libp2p/issues/941)) ([a79c6b5](https://github.com/libp2p/js-libp2p/commit/a79c6b50d7fddbcdb1af53efae922cecad4c9a83)) + + + ## [0.31.3](https://github.com/libp2p/js-libp2p/compare/v0.31.2...v0.31.3) (2021-05-04) From 818d2b2a98736f4242694479089396f6070cdad5 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 12 May 2021 18:11:17 +0100 Subject: [PATCH 195/447] fix: store remote agent and protocol version during identify (#943) --- src/identify/index.js | 3 +++ test/identify/index.spec.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/identify/index.js b/src/identify/index.js index 498ea0e1f3..5b85bcf61d 100644 --- a/src/identify/index.js +++ b/src/identify/index.js @@ -189,6 +189,8 @@ class IdentifyService { const envelope = await Envelope.openAndCertify(signedPeerRecord, PeerRecord.DOMAIN) if (this.peerStore.addressBook.consumePeerRecord(envelope)) { this.peerStore.protoBook.set(id, protocols) + this.peerStore.metadataBook.set(id, 'AgentVersion', uint8ArrayFromString(message.agentVersion)) + this.peerStore.metadataBook.set(id, 'ProtocolVersion', uint8ArrayFromString(message.protocolVersion)) return } } catch (err) { @@ -204,6 +206,7 @@ class IdentifyService { this.peerStore.protoBook.set(id, protocols) this.peerStore.metadataBook.set(id, 'AgentVersion', uint8ArrayFromString(message.agentVersion)) + this.peerStore.metadataBook.set(id, 'ProtocolVersion', uint8ArrayFromString(message.protocolVersion)) // TODO: Add and score our observed addr log('received observed address of %s', cleanObservedAddr) diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js index 65d2a77f20..dbf2d73605 100644 --- a/test/identify/index.spec.js +++ b/test/identify/index.spec.js @@ -430,6 +430,39 @@ describe('Identify', () => { await connection.close() }) + it('should store remote agent and protocol versions in metadataBook after connecting', async () => { + libp2p = new Libp2p({ + ...baseOptions, + peerId + }) + + await libp2p.start() + + sinon.spy(libp2p.identifyService, 'identify') + const peerStoreSpyConsumeRecord = sinon.spy(libp2p.peerStore.addressBook, 'consumePeerRecord') + const peerStoreSpyAdd = sinon.spy(libp2p.peerStore.addressBook, 'add') + + const connection = await libp2p.dialer.connectToPeer(remoteAddr) + expect(connection).to.exist() + + // Wait for peer store to be updated + // 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 + await pWaitFor(() => connection.streams.length === 0) + await connection.close() + + const remotePeer = PeerId.createFromB58String(remoteAddr.getPeerId()) + + const storedAgentVersion = libp2p.peerStore.metadataBook.getValue(remotePeer, 'AgentVersion') + const storedProtocolVersion = libp2p.peerStore.metadataBook.getValue(remotePeer, 'ProtocolVersion') + + expect(storedAgentVersion).to.exist() + expect(storedProtocolVersion).to.exist() + }) + it('should push protocol updates to an already connected peer', async () => { libp2p = new Libp2p({ ...baseOptions, From 7bac2045ccc34a2131b10664f1ac1f530f6b1ac5 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 12 May 2021 19:17:22 +0200 Subject: [PATCH 196/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c9d265cd3a..8b28d405ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.31.4", + "version": "0.31.5", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From 538f296b0a7b38b11460a362d8d160ac0e9bbc28 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 12 May 2021 19:17:23 +0200 Subject: [PATCH 197/447] chore: release version v0.31.5 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9dbfd999ae..4629d397f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.31.5](https://github.com/libp2p/js-libp2p/compare/v0.31.4...v0.31.5) (2021-05-12) + + +### Bug Fixes + +* store remote agent and protocol version during identify ([#943](https://github.com/libp2p/js-libp2p/issues/943)) ([818d2b2](https://github.com/libp2p/js-libp2p/commit/818d2b2a98736f4242694479089396f6070cdad5)) + + + ## [0.31.4](https://github.com/libp2p/js-libp2p/compare/v0.31.3...v0.31.4) (2021-05-12) From d22ad8389001931ecdfd9fac6c30ebcca7721093 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 21 May 2021 10:22:11 +0200 Subject: [PATCH 198/447] chore: update libp2p interop (#940) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8b28d405ac..b9f643e99c 100644 --- a/package.json +++ b/package.json @@ -139,7 +139,7 @@ "aegir": "^33.1.1", "buffer": "^6.0.3", "delay": "^5.0.0", - "interop-libp2p": "^0.3.0", + "interop-libp2p": "^0.4.0", "into-stream": "^6.0.0", "ipfs-http-client": "^49.0.4", "it-concat": "^1.0.0", From 478963ad2d195444494c0acc54cb3847a29e117c Mon Sep 17 00:00:00 2001 From: zeim839 <50573884+zeim839@users.noreply.github.com> Date: Thu, 27 May 2021 12:30:19 +0400 Subject: [PATCH 199/447] feat: keychain rotate passphrase (#944) Co-authored-by: Vasco Santos --- src/keychain/index.js | 54 ++++++++++++++++++++- test/keychain/keychain.spec.js | 85 +++++++++++++++++++++++++++++++++- 2 files changed, 137 insertions(+), 2 deletions(-) diff --git a/src/keychain/index.js b/src/keychain/index.js index 05c187a412..97d1a0d682 100644 --- a/src/keychain/index.js +++ b/src/keychain/index.js @@ -1,6 +1,9 @@ /* eslint max-nested-callbacks: ["error", 5] */ 'use strict' - +const debug = require('debug') +const log = Object.assign(debug('libp2p:keychain'), { + error: debug('libp2p:keychain:err') +}) const sanitize = require('sanitize-filename') const mergeOptions = require('merge-options') const crypto = require('libp2p-crypto') @@ -503,6 +506,55 @@ class Keychain { return throwDelayed(errcode(new Error(`Key '${name}' does not exist. ${err.message}`), 'ERR_KEY_NOT_FOUND')) } } + + /** + * Rotate keychain password and re-encrypt all assosciated keys + * + * @param {string} oldPass - The old local keychain password + * @param {string} newPass - The new local keychain password + */ + async rotateKeychainPass (oldPass, newPass) { + if (typeof oldPass !== 'string') { + return throwDelayed(errcode(new Error(`Invalid old pass type '${typeof oldPass}'`), 'ERR_INVALID_OLD_PASS_TYPE')) + } + if (typeof newPass !== 'string') { + return throwDelayed(errcode(new Error(`Invalid new pass type '${typeof newPass}'`), 'ERR_INVALID_NEW_PASS_TYPE')) + } + if (newPass.length < 20) { + return throwDelayed(errcode(new Error(`Invalid pass length ${newPass.length}`), 'ERR_INVALID_PASS_LENGTH')) + } + log('recreating keychain') + const oldDek = privates.get(this).dek + this.opts.pass = newPass + const newDek = newPass + ? crypto.pbkdf2( + newPass, + this.opts.dek.salt, + this.opts.dek.iterationCount, + this.opts.dek.keyLength, + this.opts.dek.hash) + : '' + privates.set(this, { dek: newDek }) + const keys = await this.listKeys() + for (const key of keys) { + const res = await this.store.get(DsName(key.name)) + const pem = uint8ArrayToString(res) + const privateKey = await crypto.keys.import(pem, oldDek) + const password = newDek.toString() + const keyAsPEM = await privateKey.export(password) + + // Update stored key + const batch = this.store.batch() + const keyInfo = { + name: key.name, + id: key.id + } + batch.put(DsName(key.name), uint8ArrayFromString(keyAsPEM)) + batch.put(DsInfoName(key.name), uint8ArrayFromString(JSON.stringify(keyInfo))) + await batch.commit() + } + log('keychain reconstructed') + } } module.exports = Keychain diff --git a/test/keychain/keychain.spec.js b/test/keychain/keychain.spec.js index 99ade94224..9c5047a964 100644 --- a/test/keychain/keychain.spec.js +++ b/test/keychain/keychain.spec.js @@ -9,9 +9,10 @@ const uint8ArrayToString = require('uint8arrays/to-string') const peerUtils = require('../utils/creators/peer') -const { MemoryDatastore } = require('interface-datastore') +const { MemoryDatastore, Key } = require('interface-datastore') const Keychain = require('../../src/keychain') const PeerId = require('peer-id') +const crypto = require('libp2p-crypto') describe('keychain', () => { const passPhrase = 'this is not a secure phrase' @@ -492,6 +493,88 @@ describe('keychain', () => { expect(key).to.have.property('id', rsaKeyInfo.id) }) }) + + describe('rotate keychain passphrase', () => { + let oldPass + let kc + let options + let ds + before(async () => { + ds = new MemoryDatastore() + oldPass = `hello-${Date.now()}-${Date.now()}` + options = { + pass: oldPass, + dek: { + salt: '3Nd/Ya4ENB3bcByNKptb4IR', + iterationCount: 10000, + keyLength: 64, + hash: 'sha2-512' + } + } + kc = new Keychain(ds, options) + await ds.open() + }) + + it('should validate newPass is a string', async () => { + try { + await kc.rotateKeychainPass(oldPass, 1234567890) + } catch (err) { + expect(err).to.exist() + } + }) + + it('should validate oldPass is a string', async () => { + try { + await kc.rotateKeychainPass(1234, 'newInsecurePassword1') + } catch (err) { + expect(err).to.exist() + } + }) + + it('should validate newPass is at least 20 characters', async () => { + try { + await kc.rotateKeychainPass(oldPass, 'not20Chars') + } catch (err) { + expect(err).to.exist() + } + }) + + it('can rotate keychain passphrase', async () => { + await kc.createKey('keyCreatedWithOldPassword', 'rsa', 2048) + await kc.rotateKeychainPass(oldPass, 'newInsecurePassphrase') + + // Get Key PEM from datastore + const dsname = new Key('/pkcs8/' + 'keyCreatedWithOldPassword') + const res = await ds.get(dsname) + const pem = uint8ArrayToString(res) + + const oldDek = options.pass + ? crypto.pbkdf2( + options.pass, + options.dek.salt, + options.dek.iterationCount, + options.dek.keyLength, + options.dek.hash) + : '' + + // eslint-disable-next-line no-constant-condition + const newDek = 'newInsecurePassphrase' + ? crypto.pbkdf2( + 'newInsecurePassphrase', + options.dek.salt, + options.dek.iterationCount, + options.dek.keyLength, + options.dek.hash) + : '' + + // Dek with old password should not work: + await expect(kc.importKey('keyWhosePassChanged', pem, oldDek)) + .to.eventually.be.rejected() + // Dek with new password should work: + await expect(kc.importKey('keyWhosePasswordChanged', pem, newDek)) + .to.eventually.have.property('name', 'keyWhosePasswordChanged') + }).timeout(10000) + }) }) describe('libp2p.keychain', () => { From d6540bf01d192816ea7d1976a00022488c4cf33b Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 27 May 2021 10:48:26 +0200 Subject: [PATCH 200/447] chore: update contributors --- package.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index b9f643e99c..4f8d112caa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.31.5", + "version": "0.31.6", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", @@ -179,8 +179,8 @@ "Friedel Ziegelmayer ", "Maciej Krüger ", "Hugo Dias ", - "dirkmc ", "Volker Mische ", + "dirkmc ", "Richard Littauer ", "a1300 ", "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", @@ -191,38 +191,38 @@ "Samlior ", "Thomas Eizinger ", "Didrik Nordström ", + "Felipe Martins ", "Fei Liu ", - "Ethan Lam ", "Joel Gustafson ", "Julien Bouquillon ", "Kevin Kwok ", "Kevin Lacker ", - "Dmitriy Ryajov ", + "Ethan Lam ", "Miguel Mota ", "Nuno Nogueira ", - "Diogo Silva ", + "Dmitriy Ryajov ", "Philipp Muens ", "RasmusErik Voel Jensen ", + "Diogo Silva ", + "robertkiel ", "Smite Chow ", "Soeren ", "Sönke Hahn ", "TJKoury ", - "robertkiel ", "Tiago Alves ", "Daijiro Wachi ", - "Cindy Wu ", "Yusef Napora ", "Zane Starr ", + "Aleksei ", + "Cindy Wu ", "Chris Bratlien ", - "Bernd Strehl ", "ebinks ", - "Florian-Merle ", + "Bernd Strehl ", "Francis Gulotta ", "Franck Royer ", - "Felipe Martins ", - "Aleksei ", - "Henrique Dias ", + "Florian-Merle ", "isan_rivkin ", + "Henrique Dias ", "Irakli Gozalishvili " ] } From 869d35d852c5d8d4fc7756f83c5f2d7f55486289 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 27 May 2021 10:48:26 +0200 Subject: [PATCH 201/447] chore: release version v0.31.6 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4629d397f4..84b64ce09f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.31.6](https://github.com/libp2p/js-libp2p/compare/v0.31.5...v0.31.6) (2021-05-27) + + +### Features + +* keychain rotate passphrase ([#944](https://github.com/libp2p/js-libp2p/issues/944)) ([478963a](https://github.com/libp2p/js-libp2p/commit/478963ad2d195444494c0acc54cb3847a29e117c)) + + + ## [0.31.5](https://github.com/libp2p/js-libp2p/compare/v0.31.4...v0.31.5) (2021-05-12) From d8ba2848833d9fb8a963d1b7c8d27062c6f829da Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 28 May 2021 13:46:08 +0200 Subject: [PATCH 202/447] fix: chat example with new multiaddr (#946) --- examples/chat/src/dialer.js | 6 +++--- examples/chat/src/listener.js | 2 +- examples/package.json | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/chat/src/dialer.js b/examples/chat/src/dialer.js index 623999847b..2815448bd8 100644 --- a/examples/chat/src/dialer.js +++ b/examples/chat/src/dialer.js @@ -2,11 +2,11 @@ /* eslint-disable no-console */ const PeerId = require('peer-id') -const multiaddr = require('multiaddr') +const { Multiaddr } = require('multiaddr') const createLibp2p = require('./libp2p') const { stdinToStream, streamToConsole } = require('./stream') -async function run() { +async function run () { const [idDialer, idListener] = await Promise.all([ PeerId.createFromJSON(require('./peer-id-dialer')), PeerId.createFromJSON(require('./peer-id-listener')) @@ -30,7 +30,7 @@ async function run() { }) // Dial to the remote peer (the "listener") - const listenerMa = multiaddr(`/ip4/127.0.0.1/tcp/10333/p2p/${idListener.toB58String()}`) + const listenerMa = new Multiaddr(`/ip4/127.0.0.1/tcp/10333/p2p/${idListener.toB58String()}`) const { stream } = await nodeDialer.dialProtocol(listenerMa, '/chat/1.0.0') console.log('Dialer dialed to listener on protocol: /chat/1.0.0') diff --git a/examples/chat/src/listener.js b/examples/chat/src/listener.js index ea7893ee4c..9dc928cadd 100644 --- a/examples/chat/src/listener.js +++ b/examples/chat/src/listener.js @@ -5,7 +5,7 @@ const PeerId = require('peer-id') const createLibp2p = require('./libp2p.js') const { stdinToStream, streamToConsole } = require('./stream') -async function run() { +async function run () { // Create a new libp2p node with the given multi-address const idListener = await PeerId.createFromJSON(require('./peer-id-listener')) const nodeListener = await createLibp2p({ diff --git a/examples/package.json b/examples/package.json index 7746ce4424..4bd37311b7 100644 --- a/examples/package.json +++ b/examples/package.json @@ -10,8 +10,8 @@ "dependencies": { "execa": "^2.1.0", "fs-extra": "^8.1.0", - "libp2p-pubsub-peer-discovery": "^3.0.0", - "libp2p-relay-server": "^0.1.2", + "libp2p-pubsub-peer-discovery": "^4.0.0", + "libp2p-relay-server": "^0.2.0", "p-defer": "^3.0.0", "which": "^2.0.1" }, From 2068c845cbda6b7c753c87e59af19f8babdb1ae9 Mon Sep 17 00:00:00 2001 From: Ryan Bell <25379378+iRyanBell@users.noreply.github.com> Date: Mon, 7 Jun 2021 02:50:21 -0700 Subject: [PATCH 203/447] chore: configuration format fix --- doc/CONFIGURATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index c3548b7f1b..99ea4fcb36 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -282,7 +282,7 @@ const node = await Libp2p.create({ interval: 1000, enabled: true }, - [Bootstrap.tag:] { + [Bootstrap.tag]: { list: [ // A list of bootstrap peers to connect to starting up the node "/ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", "/dnsaddr/bootstrap.libp2p.io/ipfs/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", From 29597947966682b70e8751ddb1d022580557112a Mon Sep 17 00:00:00 2001 From: mcclure Date: Thu, 10 Jun 2021 03:06:26 -0400 Subject: [PATCH 204/447] chore: add more details on DHT configuration in CONFIGURATION.md (#951) --- doc/CONFIGURATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 99ea4fcb36..dd11814451 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -373,7 +373,7 @@ const node = await Libp2p.create({ config: { dht: { // The DHT options (and defaults) can be found in its documentation kBucketSize: 20, - enabled: true, + enabled: true, // This flag is required for DHT to run (disabled by default) randomWalk: { enabled: true, // Allows to disable discovery (enabled by default) interval: 300e3, From cd152f122ffabe4ead9734e60552ed188895f09c Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Fri, 11 Jun 2021 17:45:47 +1000 Subject: [PATCH 205/447] chore: add secure websockets example (#930) * Add Secure WebSockets example * Make dial accept self-signed cert --- examples/package.json | 1 + examples/transports/4.js | 89 +++++++++++++++++++++++++ examples/transports/test-4.js | 33 +++++++++ examples/transports/test.js | 2 + examples/transports/test_certs/cert.pem | 32 +++++++++ examples/transports/test_certs/key.pem | 52 +++++++++++++++ package.json | 4 +- 7 files changed, 211 insertions(+), 2 deletions(-) create mode 100644 examples/transports/4.js create mode 100644 examples/transports/test-4.js create mode 100644 examples/transports/test_certs/cert.pem create mode 100644 examples/transports/test_certs/key.pem diff --git a/examples/package.json b/examples/package.json index 4bd37311b7..7b877b9777 100644 --- a/examples/package.json +++ b/examples/package.json @@ -16,6 +16,7 @@ "which": "^2.0.1" }, "devDependencies": { + "https": "^1.0.0", "playwright": "^1.7.1" } } diff --git a/examples/transports/4.js b/examples/transports/4.js new file mode 100644 index 0000000000..487315ec68 --- /dev/null +++ b/examples/transports/4.js @@ -0,0 +1,89 @@ +/* eslint-disable no-console */ +'use strict' + +const Libp2p = require('../..') +const TCP = require('libp2p-tcp') +const WebSockets = require('libp2p-websockets') +const { NOISE } = require('libp2p-noise') +const MPLEX = require('libp2p-mplex') + +const fs = require('fs'); +const https = require('https'); +const pipe = require('it-pipe') + +const transportKey = WebSockets.prototype[Symbol.toStringTag]; + +const httpServer = https.createServer({ + cert: fs.readFileSync('./test_certs/cert.pem'), + key: fs.readFileSync('./test_certs/key.pem'), +}); + +const createNode = async (addresses = []) => { + if (!Array.isArray(addresses)) { + addresses = [addresses] + } + + const node = await Libp2p.create({ + addresses: { + listen: addresses + }, + modules: { + transport: [WebSockets], + connEncryption: [NOISE], + streamMuxer: [MPLEX] + }, + config: { + peerDiscovery: { + // Disable autoDial as it would fail because we are using a self-signed cert. + // `dialProtocol` does not fail because we pass `rejectUnauthorized: false`. + autoDial: false + }, + transport: { + [transportKey]: { + listenerOptions: { server: httpServer }, + }, + }, + } + }) + + await node.start() + return node +} + +function printAddrs(node, number) { + console.log('node %s is listening on:', number) + node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`)) +} + +function print ({ stream }) { + pipe( + stream, + async function (source) { + for await (const msg of source) { + console.log(msg.toString()) + } + } + ) +} + +;(async () => { + const [node1, node2] = await Promise.all([ + createNode('/ip4/127.0.0.1/tcp/10000/wss'), + createNode([]) + ]) + + printAddrs(node1, '1') + printAddrs(node2, '2') + + node1.handle('/print', print) + node2.handle('/print', print) + + const targetAddr = `${node1.multiaddrs[0]}/p2p/${node1.peerId.toB58String()}`; + + // node 2 (Secure WebSockets) dials to node 1 (Secure Websockets) + const { stream } = await node2.dialProtocol(targetAddr, '/print', { websocket: { rejectUnauthorized: false } }) + await pipe( + ['node 2 dialed to node 1 successfully'], + stream + ) +})(); diff --git a/examples/transports/test-4.js b/examples/transports/test-4.js new file mode 100644 index 0000000000..8ee6d8e3c7 --- /dev/null +++ b/examples/transports/test-4.js @@ -0,0 +1,33 @@ +'use strict' + +const path = require('path') +const execa = require('execa') +const pDefer = require('p-defer') +const uint8ArrayToString = require('uint8arrays/to-string') + +async function test () { + const deferNode1 = pDefer() + + process.stdout.write('4.js\n') + + const proc = execa('node', [path.join(__dirname, '4.js')], { + cwd: path.resolve(__dirname), + all: true + }) + + proc.all.on('data', async (data) => { + process.stdout.write(data) + const line = uint8ArrayToString(data) + + if (line.includes('node 2 dialed to node 1 successfully')) { + deferNode1.resolve() + } + }) + + await Promise.all([ + deferNode1.promise, + ]) + proc.kill() +} + +module.exports = test diff --git a/examples/transports/test.js b/examples/transports/test.js index 72fa27ee7c..8ef5d0b56c 100644 --- a/examples/transports/test.js +++ b/examples/transports/test.js @@ -3,11 +3,13 @@ const test1 = require('./test-1') const test2 = require('./test-2') const test3 = require('./test-3') +const test4 = require('./test-4') async function test() { await test1() await test2() await test3() + await test4() } module.exports = test diff --git a/examples/transports/test_certs/cert.pem b/examples/transports/test_certs/cert.pem new file mode 100644 index 0000000000..0574192b2c --- /dev/null +++ b/examples/transports/test_certs/cert.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFlzCCA3+gAwIBAgIUMYedwb9L/BtvZ7Lhu71iSKrXsa4wDQYJKoZIhvcNAQEL +BQAwajELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlZBMREwDwYDVQQHDAhTb21lQ2l0 +eTESMBAGA1UECgwJTXlDb21wYW55MRMwEQYDVQQLDApNeURpdmlzaW9uMRIwEAYD +VQQDDAkxMjcuMC4wLjEwHhcNMjEwNDI4MDIzMjA5WhcNMjIwNDI4MDIzMjA5WjBq +MQswCQYDVQQGEwJVUzELMAkGA1UECAwCVkExETAPBgNVBAcMCFNvbWVDaXR5MRIw +EAYDVQQKDAlNeUNvbXBhbnkxEzARBgNVBAsMCk15RGl2aXNpb24xEjAQBgNVBAMM +CTEyNy4wLjAuMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANNhXBu0 +GH1Kzl9iaQxCxEnyyAShS5FYScdKxqpYsgJT4poLWLQBZQEFLEqbdillIlTZqMss +jWqkFL2xmjqdcnOKFEZUarntVE2hxFYYQex2Fi8MYwFj+Pvt74d02xPyfzFNFgyX +a1EakoGBwClaf3I7jW7raPudjcf4HnwQ7r/NwiO8FqHFZgLcTnwI8bk+cxDoDAqu +mhqMB5nnerqvKEyR9Fb2PoL+8PwOPJOOKTDVwLMeMJu2WLR8AU2FzOj5SVI2qsu9 +Ps5azysD8KQAMcw4y9s6do36SaMQS85fbvXBV7XBqMD34HPBUbFiCoFoaCzK9Zfb +pCXyVJMUNmw5hyq9nbjUt4Kvr/58bU2gjUKSdPf6KhBxFnDZwl+2qqPdVIb/qtwz +HExtJWq3upklXNOg3HoR6vcr1O9ReJHrzLRMEb51WP1aN/qJ2/lRskcZ4A806qwr +W67BvnOg6s3ZtxHN9v3bsyfsvC66w8PEfCnCVxugC7cUW0gtW54AU75T3ukg7X+m +vECr/+qIzNEBIxxCPgefCG/JAdJhQ5SCvoARAVPStUIWDmigDeOt7go5nKbdVIJ4 +7bbBFUhHT2mTHu30fHhRqSDcHzwE7Zz6YJIJmKq29UmzUazFnKlLU67MjLJwiDPm +fC3GyOdAWkkZE5hjtkiy+3yWoEHhaJYRI1u3AgMBAAGjNTAzMAsGA1UdDwQEAwIE +MDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3 +DQEBCwUAA4ICAQCx/ynu4iCQAK8VId/QQe7GqgOpFgx+6Mce9GQC6ZVEjAPgapsS +Pl+l6+11cFjHKv0+Z/iN2JgkFmNXfwJcfYI0tHbMK+0U9hgKb1eFgiIwCqb4cPOz +wMwusZ95BjIbtcEbL/+pMUpNhmjPz1fOILJZtDVq++lqJCv7t8+SoAmMVYtlcLNg +muuV/UYR3uqvnAJmjgJVWs4otDGrxCYJE48M+9L2Gm05Htpi9WL1bZaQ+fJ85m85 +daedLc6R1/ZRTIH6i73sD4rYs0bx1fCJvkbcgXtKMHEkiHuG/MzR7Pa4cJAVKCx9 +lRTgrO7Gkllt2+jp4qg0YhdNq89e0DNA5cyB9H4udRgHQOcrlVRiX9OD/Kz+F5m/ +fQwMdbnqdg3ar5DSa8Q5g3bdLbNSCcI9sjCLTkNxUC/XTWGdG03RCVIt1qvBvZHk +JaG6xGpbRZ5CN0T9eindd38JBrkPAPfgl6qhwvcqh6uVFYua+7KmF9K+mKarlmMw +6RWaw2j4sMgUyRIS6fR9vDc20SrtoNvKQM1U6+0VYs1nizfkmsqqqRODmERKbKwc +ahKJFubXfr8gz+PipAKFZbxr2EPAyoiNkx+0eM6Eedo55oP2BoGHEfXEoAonyMFM +F/xTbpFtdRYE2hwsZCk86fpbcPTmdCY8txeZ7+4Bme2d9XXsTAxF64usqQ== +-----END CERTIFICATE----- diff --git a/examples/transports/test_certs/key.pem b/examples/transports/test_certs/key.pem new file mode 100644 index 0000000000..dfee30176f --- /dev/null +++ b/examples/transports/test_certs/key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQDTYVwbtBh9Ss5f +YmkMQsRJ8sgEoUuRWEnHSsaqWLICU+KaC1i0AWUBBSxKm3YpZSJU2ajLLI1qpBS9 +sZo6nXJzihRGVGq57VRNocRWGEHsdhYvDGMBY/j77e+HdNsT8n8xTRYMl2tRGpKB +gcApWn9yO41u62j7nY3H+B58EO6/zcIjvBahxWYC3E58CPG5PnMQ6AwKrpoajAeZ +53q6ryhMkfRW9j6C/vD8DjyTjikw1cCzHjCbtli0fAFNhczo+UlSNqrLvT7OWs8r +A/CkADHMOMvbOnaN+kmjEEvOX271wVe1wajA9+BzwVGxYgqBaGgsyvWX26Ql8lST +FDZsOYcqvZ241LeCr6/+fG1NoI1CknT3+ioQcRZw2cJftqqj3VSG/6rcMxxMbSVq +t7qZJVzToNx6Eer3K9TvUXiR68y0TBG+dVj9Wjf6idv5UbJHGeAPNOqsK1uuwb5z +oOrN2bcRzfb927Mn7LwuusPDxHwpwlcboAu3FFtILVueAFO+U97pIO1/prxAq//q +iMzRASMcQj4HnwhvyQHSYUOUgr6AEQFT0rVCFg5ooA3jre4KOZym3VSCeO22wRVI +R09pkx7t9Hx4Uakg3B88BO2c+mCSCZiqtvVJs1GsxZypS1OuzIyycIgz5nwtxsjn +QFpJGROYY7ZIsvt8lqBB4WiWESNbtwIDAQABAoICAQCpGV3iG7Trpohp7gQzdsYo +kjxI1+/oGkULVVqQs9vT2N+SdDlF50eyBT1lgfCJNQq97lIGF2IaSaD+D7Jd6c7B +d1i42pd2ndGvORYj+cvjKqSchsA9QIjSoYnZRzZrQrdV7WESOZ/0hdlmGTJs4qTJ +8bI3ZcPaZjQiIO/iOHmGn0gL5lAEojH1X+C5gT4+/yJ2B+x6LyvAyPzbtj6MUctf +VfOuDdf8W47VVV5IfJWfJ6C8qg4gw0M7P2ibZ8qBJcvuJSWFT6OK2UKaGtDLogw0 +X8tVWfO1qOB3vnWmZtoRZ9aO5JnnpWS9tY1w5gmZdLjB/Kt0DJXIdZALCURwV6U0 +q5XR0SETEgdRrNX92PA2lmxO9fAgXRSjP/OoeDjAVhnRfYyShDbEIb8GHk7nE+is +6ak5ufxKE53S8wB9L7MTPqTvxusBHi8saLevdnPBMQPvtEVkg2Iw/iPBsegUuUjD +uzXlq4WUMCUBJEMVPuYEsaQizxpp2oM6AZj/ecuTKFX5CirFFWKOQ4cp+O8lrfI5 +ruwHrMkfjowDYcQaOLHq13anvt8+8LBlngVw+jiAGB/bGwrAwEZWUc8i1HbH/G8e +sm0kMuCqV1GbRyMCUO3pWjzrsz8LEy74Jr0z7KZn52vLWrTkiD4NRXahxTBhHpXb +AVclJ+a4BKk2rRJVRFRRQQKCAQEA7+uTl2ZHp1v7A8/I2zPIxoVz0fiwxwAjuv34 +cV+uxG0n5Tko4PKMxavddRFKNeGvrz0aO/GNX8NIW7pDqZ2CwHyskgUX/bFAqGKF +Z/z2DmiZ2rdSUH89O3ysq+OF3RjX/FBNJ0SVdwtrpz3kCSWpa4PnmN7+IevL6zxY +8gLrs07Ge+ci94FZaDHBNrkGQ00krbOmwIvnc90hyRPCKfMS+u2/ejKZ5QDyRG+H +jbQ008ZV2OqUdS6h1twfoJ1Q4QhHijB6PegRLGdZGuUXIQfFP8dIUsQluKSUFyOy +bL9W2yBwtbn3EwYDHLJQnLICxfcTBWg/2vOIucsSjxG7KNY0yQKCAQEA4YwcVpi3 +D+8OcnbpRBRlHo84DRZorp0RO8vhxevvB1CcBnkLRIYXlS2JIfrnhZAI/5jBk1ei +FmgRFyAjZ8gDdkDCiDMQMDUwUhLGSVurI9sk16B4TQKCM+iE0LDrXIy9ezJRJkj0 +rOt8sqo2/TOttm2KEXY8Cco59tU4bMZg5Tr9l7SMTTj4skTO6Jn6/6hX3XuFkJw7 +B0DsSzIqXyRHAzOidagIEoIr7k4cEGXsrSWoSiHg/eky1ihCyUw3vDDOmoViBR7s +h5nLjQNNAzOtyoKLqST7B7uXkdUo5nV2IUHSGD5LNxlTaNp0XL9Ph3EBtcuwNuB6 +zyKXc+O5iNfMfwKCAQEA5/RJKCnRgsORxpif5xWEujIRzOHz/yFqagHarbnFHNEv +rhT6Kak2YnIL1H/X0IoWsYSQlX2uofQKQ+ysOBM5c2HV8gKMtFAnY+SEeAn/1eRZ +QzTTl1G84INj6Xc6V40KXD1CqoFLQ+G9vd4/Vnyb9H99bLXC2wa+ivo4QBqEyEGT +8fyAOOxMhUj9NSvjGzQ9DtbOk/9u0PztChtZL/d61TEAW2MKmHW2xGVTl7OvE0QA +gYwh5b0k6La+uSj/JeE8USUXOjzgRZ7RbggouV1q3YOMr8BFe+NZ7Zksiqjej1Io +xfk6H6FDZv4ao7QSrFR4hlTIz6V9/aqQkdOhsBSQyQKCAQEAzHwz4Qr5xVduGLbY +S6HV/7vHDI6Jf+3lBvqUidWa013w5yls3sZXsSckkgshRoVMszayIbystnXJMNcx +YlEDWn3iIItzHNHMKkzdOvsCETMIlvnkt6UTmK4xY+dSq4jp7Ty0N+qi8fdaCb2q +tyrYTnHHYId6bUHMBY5QZsYAaTNvYNAO96A0UaNyl42q84iTiLkJYg9SsQPad15W +7gU84Jk6rEMYdndQDvEAHpnZ1y0yA2vtySZYsbK0wj34tgTl+0/8izn7JgF4ezNH +6iQ7Z0OuDT763IrmIxBH0ZEi9YnwSYyIsr6iUYjlQIUuPFRnQYQXEdm5Xfw1pZsL +xhYoTwKCAQB9edDe4LX+0z9i4qr0iHV8H/WoyI5UD/Pc217PKkYM3+ewR9SL9D9z +TS78Sl7HgRgEmIu+MR/u5B2ePf7jkvB/oxyPwqAzJeJ72mV3Mevm27G/Ndd8lt5W +FBCGOx7ZeP4/Cv4mvPD979ix2IalDoWMSWJnpQPN+B1jGeCrUYAXQc1k/vU99gLa +8Tuu3WfBpVAsO7hAC9mu6tuLyfKVqiMOVs2aky9xLqiqW/6uIcGu+owrr+gkDDY/ +JfBSUfxYKcjtJiHOEbFGrrRe93XsngmaTz/Hv9A/QLVCuJgWEHlt4WHSc+BtAtaV +9avp6VlyVNfe4KEKW7IekrI0cmfMdXkl +-----END PRIVATE KEY----- diff --git a/package.json b/package.json index 4f8d112caa..7e6cf38d69 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ }, "dependencies": { "@motrix/nat-api": "^0.3.1", + "@vascosantos/moving-average": "^1.1.0", "abort-controller": "^3.0.0", "aggregate-error": "^3.1.0", "any-signal": "^2.1.1", @@ -108,7 +109,6 @@ "libp2p-utils": "^0.3.1", "mafmt": "^9.0.0", "merge-options": "^3.0.4", - "@vascosantos/moving-average": "^1.1.0", "multiaddr": "^9.0.1", "multicodec": "^3.0.1", "multihashing-async": "^2.1.2", @@ -157,7 +157,7 @@ "libp2p-noise": "^3.0.0", "libp2p-tcp": "^0.15.4", "libp2p-webrtc-star": "^0.22.2", - "libp2p-websockets": "^0.15.6", + "libp2p-websockets": "^0.15.8", "multihashes": "^4.0.2", "nock": "^13.0.3", "p-defer": "^3.0.0", From 2a6a635f13309e861b4654d570f1a4765f729cea Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 11 Jun 2021 09:01:40 +0100 Subject: [PATCH 206/447] chore: remove ipfs-utils dep (#953) * chore: remove ipfs-utils dep We only use it for the env detection, so use [wherearewe](https://www.npmjs.com/package/wherearewe) instead which is that, but pulled out into a tiny module. The `TextDecoder` class is global everywhere we support so we don't need to pull it in from `ipfs-utils` and it's been removed from v8 anyway. * chore: update ipfs-http-client --- doc/CONFIGURATION.md | 4 ++-- package.json | 4 ++-- src/circuit/utils.js | 2 -- src/nat-manager.js | 2 +- test/content-routing/content-routing.node.js | 4 ++-- test/peer-routing/peer-routing.node.js | 4 ++-- test/relay/auto-relay.node.js | 2 +- test/ts-use/src/main.ts | 4 ++-- 8 files changed, 12 insertions(+), 14 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index dd11814451..dcf8575618 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -399,13 +399,13 @@ const PeerId = require('peer-id') // create a peerId const peerId = await PeerId.create() -const delegatedPeerRouting = new DelegatedPeerRouter(ipfsHttpClient({ +const delegatedPeerRouting = new DelegatedPeerRouter(ipfsHttpClient.create({ host: 'node0.delegate.ipfs.io', // In production you should setup your own delegates protocol: 'https', port: 443 })) -const delegatedContentRouting = new DelegatedContentRouter(peerId, ipfsHttpClient({ +const delegatedContentRouting = new DelegatedContentRouter(peerId, ipfsHttpClient.create({ host: 'node0.delegate.ipfs.io', // In production you should setup your own delegates protocol: 'https', port: 443 diff --git a/package.json b/package.json index 7e6cf38d69..498a9d12a1 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,6 @@ "events": "^3.3.0", "hashlru": "^2.3.0", "interface-datastore": "^4.0.0", - "ipfs-utils": "^7.0.0", "it-all": "^1.0.4", "it-buffer": "^0.1.2", "it-drain": "^1.0.3", @@ -128,6 +127,7 @@ "streaming-iterables": "^5.0.2", "timeout-abort-controller": "^1.1.1", "varint": "^6.0.0", + "wherearewe": "^1.0.0", "xsalsa20": "^1.1.0" }, "devDependencies": { @@ -141,7 +141,7 @@ "delay": "^5.0.0", "interop-libp2p": "^0.4.0", "into-stream": "^6.0.0", - "ipfs-http-client": "^49.0.4", + "ipfs-http-client": "^50.1.1", "it-concat": "^1.0.0", "it-pair": "^1.0.0", "it-pushable": "^1.4.0", diff --git a/src/circuit/utils.js b/src/circuit/utils.js index c1a3ff10eb..3e4ae4c540 100644 --- a/src/circuit/utils.js +++ b/src/circuit/utils.js @@ -3,8 +3,6 @@ const CID = require('cids') const multihashing = require('multihashing-async') -const TextEncoder = require('ipfs-utils/src/text-encoder') - /** * Convert a namespace string into a cid. * diff --git a/src/nat-manager.js b/src/nat-manager.js index 478db81104..a53e152d15 100644 --- a/src/nat-manager.js +++ b/src/nat-manager.js @@ -8,7 +8,7 @@ const { Multiaddr } = require('multiaddr') const log = Object.assign(debug('libp2p:nat'), { error: debug('libp2p:nat:err') }) -const { isBrowser } = require('ipfs-utils/src/env') +const { isBrowser } = require('wherearewe') const retry = require('p-retry') // @ts-ignore private-api does not export types const isPrivateIp = require('private-ip') diff --git a/test/content-routing/content-routing.node.js b/test/content-routing/content-routing.node.js index ff2079a505..6861230952 100644 --- a/test/content-routing/content-routing.node.js +++ b/test/content-routing/content-routing.node.js @@ -105,7 +105,7 @@ describe('content-routing', () => { beforeEach(async () => { const [peerId] = await peerUtils.createPeerId({ fixture: true }) - delegate = new DelegatedContentRouter(peerId, ipfsHttpClient({ + delegate = new DelegatedContentRouter(peerId, ipfsHttpClient.create({ host: '0.0.0.0', protocol: 'http', port: 60197 @@ -253,7 +253,7 @@ describe('content-routing', () => { beforeEach(async () => { const [peerId] = await peerUtils.createPeerId({ fixture: true }) - delegate = new DelegatedContentRouter(peerId, ipfsHttpClient({ + delegate = new DelegatedContentRouter(peerId, ipfsHttpClient.create({ host: '0.0.0.0', protocol: 'http', port: 60197 diff --git a/test/peer-routing/peer-routing.node.js b/test/peer-routing/peer-routing.node.js index 5149c7d45a..9cb9ca0fb1 100644 --- a/test/peer-routing/peer-routing.node.js +++ b/test/peer-routing/peer-routing.node.js @@ -113,7 +113,7 @@ describe('peer-routing', () => { let delegate beforeEach(async () => { - delegate = new DelegatedPeerRouter(ipfsHttpClient({ + delegate = new DelegatedPeerRouter(ipfsHttpClient.create({ host: '0.0.0.0', protocol: 'http', port: 60197 @@ -288,7 +288,7 @@ describe('peer-routing', () => { let delegate beforeEach(async () => { - delegate = new DelegatedPeerRouter(ipfsHttpClient({ + delegate = new DelegatedPeerRouter(ipfsHttpClient.create({ host: '0.0.0.0', protocol: 'http', port: 60197 diff --git a/test/relay/auto-relay.node.js b/test/relay/auto-relay.node.js index 3b0481a31d..aaddd21491 100644 --- a/test/relay/auto-relay.node.js +++ b/test/relay/auto-relay.node.js @@ -515,7 +515,7 @@ describe('auto-relay', () => { // Create 2 nodes, and turn HOP on for the relay ;[local, remote, relayLibp2p] = peerIds.map((peerId, index) => { - const delegate = new DelegatedContentRouter(peerId, ipfsHttpClient({ + const delegate = new DelegatedContentRouter(peerId, ipfsHttpClient.create({ host: '0.0.0.0', protocol: 'http', port: 60197 diff --git a/test/ts-use/src/main.ts b/test/ts-use/src/main.ts index a1d2ac61a4..0efe3c64ab 100644 --- a/test/ts-use/src/main.ts +++ b/test/ts-use/src/main.ts @@ -35,13 +35,13 @@ async function main() { // create a peerId const peerId = await PeerId.create() - const delegatedPeerRouting = new DelegatedPeerRouter(ipfsHttpClient({ + const delegatedPeerRouting = new DelegatedPeerRouter(ipfsHttpClient.create({ host: 'node0.delegate.ipfs.io', // In production you should setup your own delegates protocol: 'https', port: 443 })) - const delegatedContentRouting = new DelegatedContentRouter(peerId, ipfsHttpClient({ + const delegatedContentRouting = new DelegatedContentRouter(peerId, ipfsHttpClient.create({ host: 'node0.delegate.ipfs.io', // In production you should setup your own delegates protocol: 'https', port: 443 From 2c4b567b00a0226aaace1169745c2bd4c834bfcb Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 20 Jan 2021 10:34:47 +0100 Subject: [PATCH 207/447] chore: restructure pubsub tests --- examples/package.json | 1 + examples/pubsub/README.md | 4 + .../pubsub.spec.js} | 66 +++- test/configuration/utils.js | 52 +++ test/pubsub/implementations.node.js | 95 ----- test/pubsub/operation.node.js | 326 ------------------ test/pubsub/utils.js | 29 -- 7 files changed, 105 insertions(+), 468 deletions(-) rename test/{pubsub/configuration.node.js => configuration/pubsub.spec.js} (58%) create mode 100644 test/configuration/utils.js delete mode 100644 test/pubsub/implementations.node.js delete mode 100644 test/pubsub/operation.node.js delete mode 100644 test/pubsub/utils.js diff --git a/examples/package.json b/examples/package.json index 7b877b9777..23b635b816 100644 --- a/examples/package.json +++ b/examples/package.json @@ -12,6 +12,7 @@ "fs-extra": "^8.1.0", "libp2p-pubsub-peer-discovery": "^4.0.0", "libp2p-relay-server": "^0.2.0", + "libp2p-gossipsub": "^0.8.0", "p-defer": "^3.0.0", "which": "^2.0.1" }, diff --git a/examples/pubsub/README.md b/examples/pubsub/README.md index 2016b2c813..9ea93dc0e4 100644 --- a/examples/pubsub/README.md +++ b/examples/pubsub/README.md @@ -8,6 +8,10 @@ We've seen many interesting use cases appear with this, here are some highlights - [IPFS PubSub (using libp2p-floodsub) for IoT](https://www.youtube.com/watch?v=qLpM5pBDGiE). - [Real Time distributed Applications](https://www.youtube.com/watch?v=vQrbxyDPSXg) +## 0. Set up the example + +Before moving into the examples, you should run `npm install` on the top level `js-libp2p` folder, in order to install all the dependencies needed for this example. In addition, you will need to install the example related dependencies by doing `cd examples && npm install`. Once the install finishes, you should move into the example folder with `cd pubsub`. + ## 1. Setting up a simple PubSub network on top of libp2p For this example, we will use MulticastDNS for automatic Peer Discovery. This example is based the previous examples found in [Discovery Mechanisms](../discovery-mechanisms). You can find the complete version at [1.js](./1.js). diff --git a/test/pubsub/configuration.node.js b/test/configuration/pubsub.spec.js similarity index 58% rename from test/pubsub/configuration.node.js rename to test/configuration/pubsub.spec.js index 998b886ee5..6f2393d659 100644 --- a/test/pubsub/configuration.node.js +++ b/test/configuration/pubsub.spec.js @@ -3,14 +3,13 @@ const { expect } = require('aegir/utils/chai') const mergeOptions = require('merge-options') -const { Multiaddr } = require('multiaddr') +const pDefer = require('p-defer') +const delay = require('delay') const { create } = require('../../src') -const { baseOptions, subsystemOptions } = require('./utils') +const { baseOptions, pubsubSubsystemOptions } = require('./utils') const peerUtils = require('../utils/creators/peer') -const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') - describe('Pubsub subsystem is configurable', () => { let libp2p @@ -24,18 +23,15 @@ describe('Pubsub subsystem is configurable', () => { }) it('should exist if the module is provided', async () => { - libp2p = await create(subsystemOptions) + libp2p = await create(pubsubSubsystemOptions) expect(libp2p.pubsub).to.exist() }) it('should start and stop by default once libp2p starts', async () => { const [peerId] = await peerUtils.createPeerId() - const customOptions = mergeOptions(subsystemOptions, { - peerId, - addresses: { - listen: [listenAddr] - } + const customOptions = mergeOptions(pubsubSubsystemOptions, { + peerId }) libp2p = await create(customOptions) @@ -51,11 +47,8 @@ describe('Pubsub subsystem is configurable', () => { it('should not start if disabled once libp2p starts', async () => { const [peerId] = await peerUtils.createPeerId() - const customOptions = mergeOptions(subsystemOptions, { + const customOptions = mergeOptions(pubsubSubsystemOptions, { peerId, - addresses: { - listen: [listenAddr] - }, config: { pubsub: { enabled: false @@ -73,11 +66,8 @@ describe('Pubsub subsystem is configurable', () => { it('should allow a manual start', async () => { const [peerId] = await peerUtils.createPeerId() - const customOptions = mergeOptions(subsystemOptions, { + const customOptions = mergeOptions(pubsubSubsystemOptions, { peerId, - addresses: { - listen: [listenAddr] - }, config: { pubsub: { enabled: false @@ -93,3 +83,43 @@ describe('Pubsub subsystem is configurable', () => { expect(libp2p.pubsub.started).to.equal(true) }) }) + +describe('Pubsub subscription handlers adapter', () => { + let libp2p + + beforeEach(async () => { + const [peerId] = await peerUtils.createPeerId() + + libp2p = await create(mergeOptions(pubsubSubsystemOptions, { + peerId + })) + + await libp2p.start() + }) + + it('extends pubsub with subscribe handler', async () => { + let countMessages = 0 + const topic = 'topic' + const defer = pDefer() + + const handler = () => { + countMessages++ + if (countMessages > 1) { + throw new Error('only one message should be received') + } + + defer.resolve() + } + + await libp2p.pubsub.subscribe(topic, handler) + + libp2p.pubsub.emit(topic, 'useless-data') + await defer.promise + + await libp2p.pubsub.unsubscribe(topic, handler) + libp2p.pubsub.emit(topic, 'useless-data') + + // wait to guarantee that the handler is not called twice + await delay(100) + }) +}) diff --git a/test/configuration/utils.js b/test/configuration/utils.js new file mode 100644 index 0000000000..c1a7aa5c6a --- /dev/null +++ b/test/configuration/utils.js @@ -0,0 +1,52 @@ +'use strict' + +const Pubsub = require('libp2p-interfaces/src/pubsub') +const { NOISE: Crypto } = require('libp2p-noise') +const Muxer = require('libp2p-mplex') +const Transport = require('libp2p-websockets') +const filters = require('libp2p-websockets/src/filters') +const transportKey = Transport.prototype[Symbol.toStringTag] + +const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser') +const relayAddr = MULTIADDRS_WEBSOCKETS[0] + +const mergeOptions = require('merge-options') + +const baseOptions = { + modules: { + transport: [Transport], + streamMuxer: [Muxer], + connEncryption: [Crypto] + } +} + +module.exports.baseOptions = baseOptions + +class MockPubsub extends Pubsub { + constructor (libp2p, options = {}) { + super({ + debugName: 'mock-pubsub', + multicodecs: '/mock-pubsub', + libp2p, + ...options + }) + } +} + +const pubsubSubsystemOptions = mergeOptions(baseOptions, { + modules: { + pubsub: MockPubsub + }, + addresses: { + listen: [`${relayAddr}/p2p-circuit`] + }, + config: { + transport: { + [transportKey]: { + filter: filters.all + } + } + } +}) + +module.exports.pubsubSubsystemOptions = pubsubSubsystemOptions diff --git a/test/pubsub/implementations.node.js b/test/pubsub/implementations.node.js deleted file mode 100644 index 165df61bc1..0000000000 --- a/test/pubsub/implementations.node.js +++ /dev/null @@ -1,95 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const pWaitFor = require('p-wait-for') -const pDefer = require('p-defer') -const mergeOptions = require('merge-options') - -const Floodsub = require('libp2p-floodsub') -const Gossipsub = require('libp2p-gossipsub') -const { multicodec: floodsubMulticodec } = require('libp2p-floodsub') -const { multicodec: gossipsubMulticodec } = require('libp2p-gossipsub') -const uint8ArrayToString = require('uint8arrays/to-string') - -const { Multiaddr } = require('multiaddr') - -const { create } = require('../../src') -const { baseOptions } = require('./utils') -const peerUtils = require('../utils/creators/peer') - -const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') -const remoteListenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') - -describe('Pubsub subsystem is able to use different implementations', () => { - let peerId, remotePeerId - let libp2p, remoteLibp2p - - beforeEach(async () => { - [peerId, remotePeerId] = await peerUtils.createPeerId({ number: 2 }) - }) - - afterEach(() => Promise.all([ - libp2p && libp2p.stop(), - remoteLibp2p && remoteLibp2p.stop() - ])) - - it('Floodsub nodes', () => { - return pubsubTest(floodsubMulticodec, Floodsub) - }) - - it('Gossipsub nodes', () => { - return pubsubTest(gossipsubMulticodec, Gossipsub) - }) - - const pubsubTest = async (multicodec, pubsub) => { - const defer = pDefer() - const topic = 'test-topic' - const data = 'hey!' - - libp2p = await create(mergeOptions(baseOptions, { - peerId, - addresses: { - listen: [listenAddr] - }, - modules: { - pubsub: pubsub - } - })) - - remoteLibp2p = await create(mergeOptions(baseOptions, { - peerId: remotePeerId, - addresses: { - listen: [remoteListenAddr] - }, - modules: { - pubsub: pubsub - } - })) - - await Promise.all([ - libp2p.start(), - remoteLibp2p.start() - ]) - - const libp2pId = libp2p.peerId.toB58String() - libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) - - const connection = await libp2p.dialProtocol(remotePeerId, multicodec) - expect(connection).to.exist() - - libp2p.pubsub.subscribe(topic, (msg) => { - expect(uint8ArrayToString(msg.data)).to.equal(data) - defer.resolve() - }) - - // 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 defer.promise - } -}) diff --git a/test/pubsub/operation.node.js b/test/pubsub/operation.node.js deleted file mode 100644 index 2103438373..0000000000 --- a/test/pubsub/operation.node.js +++ /dev/null @@ -1,326 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') - -const pWaitFor = require('p-wait-for') -const pDefer = require('p-defer') -const mergeOptions = require('merge-options') -const { Multiaddr } = require('multiaddr') -const uint8ArrayToString = require('uint8arrays/to-string') - -const { create } = require('../../src') -const { subsystemOptions, subsystemMulticodecs } = require('./utils') -const peerUtils = require('../utils/creators/peer') - -const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') -const remoteListenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') - -describe('Pubsub subsystem operates correctly', () => { - let peerId, remotePeerId - let libp2p, remoteLibp2p - - beforeEach(async () => { - [peerId, remotePeerId] = await peerUtils.createPeerId({ number: 2 }) - }) - - describe('pubsub started before connect', () => { - beforeEach(async () => { - libp2p = await create(mergeOptions(subsystemOptions, { - peerId, - addresses: { - listen: [listenAddr] - } - })) - - remoteLibp2p = await create(mergeOptions(subsystemOptions, { - peerId: remotePeerId, - addresses: { - listen: [remoteListenAddr] - } - })) - - await Promise.all([ - libp2p.start(), - remoteLibp2p.start() - ]) - - libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) - }) - - afterEach(() => Promise.all([ - libp2p && libp2p.stop(), - remoteLibp2p && remoteLibp2p.stop() - ])) - - afterEach(() => { - sinon.restore() - }) - - it('should get notified of connected peers on dial', async () => { - const connection = await libp2p.dialProtocol(remotePeerId, subsystemMulticodecs) - - expect(connection).to.exist() - - return Promise.all([ - pWaitFor(() => libp2p.pubsub.peers.size === 1), - pWaitFor(() => remoteLibp2p.pubsub.peers.size === 1) - ]) - }) - - it('should receive pubsub messages', async () => { - const defer = pDefer() - const topic = 'test-topic' - const data = 'hey!' - const libp2pId = libp2p.peerId.toB58String() - - await libp2p.dialProtocol(remotePeerId, subsystemMulticodecs) - - let subscribedTopics = libp2p.pubsub.getTopics() - expect(subscribedTopics).to.not.include(topic) - - libp2p.pubsub.subscribe(topic, (msg) => { - expect(uint8ArrayToString(msg.data)).to.equal(data) - defer.resolve() - }) - - 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 defer.promise - }) - }) - - describe('pubsub started after connect', () => { - beforeEach(async () => { - libp2p = await create(mergeOptions(subsystemOptions, { - peerId, - addresses: { - listen: [listenAddr] - } - })) - - remoteLibp2p = await create(mergeOptions(subsystemOptions, { - peerId: remotePeerId, - addresses: { - listen: [remoteListenAddr] - }, - config: { - pubsub: { - enabled: 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 get notified of connected peers after starting', async () => { - const connection = await libp2p.dial(remotePeerId) - - expect(connection).to.exist() - expect(libp2p.pubsub.peers.size).to.be.eql(0) - expect(remoteLibp2p.pubsub.peers.size).to.be.eql(0) - - remoteLibp2p.pubsub.start() - - return Promise.all([ - pWaitFor(() => libp2p.pubsub.peers.size === 1), - pWaitFor(() => remoteLibp2p.pubsub.peers.size === 1) - ]) - }) - - it('should receive pubsub messages', async function () { - this.timeout(10e3) - const defer = pDefer() - const libp2pId = libp2p.peerId.toB58String() - const topic = 'test-topic' - const data = 'hey!' - - await libp2p.dial(remotePeerId) - - remoteLibp2p.pubsub.start() - - await Promise.all([ - pWaitFor(() => libp2p.pubsub.peers.size === 1), - pWaitFor(() => remoteLibp2p.pubsub.peers.size === 1) - ]) - - let subscribedTopics = libp2p.pubsub.getTopics() - expect(subscribedTopics).to.not.include(topic) - - libp2p.pubsub.subscribe(topic, (msg) => { - expect(uint8ArrayToString(msg.data)).to.equal(data) - defer.resolve() - }) - - 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 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(uint8ArrayToString(msg.data)).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]) => uint8ArrayToString(message.data))).to.include.members(['message1', 'message2']) - - // Disconnect the first connection (this acts as a delayed reconnect) - const libp2pConnUpdateSpy = sinon.spy(libp2p.connectionManager.connections, 'set') - const remoteLibp2pConnUpdateSpy = sinon.spy(remoteLibp2p.connectionManager.connections, 'set') - - await originalConnection.close() - await pWaitFor(() => libp2pConnUpdateSpy.callCount === 1 && remoteLibp2pConnUpdateSpy.callCount === 1) - - // 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]) => uint8ArrayToString(message.data))).to.include.members(['message3', 'message4']) - }) - }) -}) diff --git a/test/pubsub/utils.js b/test/pubsub/utils.js deleted file mode 100644 index 11495c5df8..0000000000 --- a/test/pubsub/utils.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict' - -const Gossipsub = require('libp2p-gossipsub') -const { multicodec } = require('libp2p-gossipsub') -const Crypto = require('../../src/insecure/plaintext') -const Muxer = require('libp2p-mplex') -const Transport = require('libp2p-tcp') - -const mergeOptions = require('merge-options') - -const baseOptions = { - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - } -} - -module.exports.baseOptions = baseOptions - -const subsystemOptions = mergeOptions(baseOptions, { - modules: { - pubsub: Gossipsub - } -}) - -module.exports.subsystemOptions = subsystemOptions - -module.exports.subsystemMulticodecs = [multicodec] From 052aad4e06d08aa570697e43d1d9a9a916d6096f Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 20 Jan 2021 15:48:54 +0100 Subject: [PATCH 208/447] chore: use node 15 in ci --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 50b74aad04..1cc6673b43 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,7 +27,7 @@ jobs: strategy: matrix: os: [windows-latest, ubuntu-latest, macos-latest] - node: [14] + node: [14, 15] fail-fast: true steps: - uses: actions/checkout@v2 From 50f7f32e538c221a66c769d39797b94d4e73b5b2 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 16 Apr 2021 22:45:34 +0200 Subject: [PATCH 209/447] chore: update branch --- test/configuration/pubsub.spec.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/configuration/pubsub.spec.js b/test/configuration/pubsub.spec.js index 6f2393d659..46e6c8fbd5 100644 --- a/test/configuration/pubsub.spec.js +++ b/test/configuration/pubsub.spec.js @@ -97,6 +97,10 @@ describe('Pubsub subscription handlers adapter', () => { await libp2p.start() }) + afterEach(async () => { + libp2p && await libp2p.stop() + }) + it('extends pubsub with subscribe handler', async () => { let countMessages = 0 const topic = 'topic' From afe0f854e897b02eaca983b5095aa408768bfffb Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 25 May 2021 11:21:40 +0200 Subject: [PATCH 210/447] chore: use node 16 --- .github/workflows/main.yml | 2 +- examples/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1cc6673b43..4b3d6ad7f2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,7 +27,7 @@ jobs: strategy: matrix: os: [windows-latest, ubuntu-latest, macos-latest] - node: [14, 15] + node: [14, 16] fail-fast: true steps: - uses: actions/checkout@v2 diff --git a/examples/package.json b/examples/package.json index 23b635b816..2e71844d43 100644 --- a/examples/package.json +++ b/examples/package.json @@ -12,7 +12,7 @@ "fs-extra": "^8.1.0", "libp2p-pubsub-peer-discovery": "^4.0.0", "libp2p-relay-server": "^0.2.0", - "libp2p-gossipsub": "^0.8.0", + "libp2p-gossipsub": "^0.9.0", "p-defer": "^3.0.0", "which": "^2.0.1" }, From 755eb909f21857db5c0660374e1246ad9cf4ba9d Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 28 May 2021 14:10:27 +0200 Subject: [PATCH 211/447] chore: update gossipsub dep for example --- examples/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/package.json b/examples/package.json index 2e71844d43..a4adc2ad42 100644 --- a/examples/package.json +++ b/examples/package.json @@ -12,7 +12,7 @@ "fs-extra": "^8.1.0", "libp2p-pubsub-peer-discovery": "^4.0.0", "libp2p-relay-server": "^0.2.0", - "libp2p-gossipsub": "^0.9.0", + "libp2p-gossipsub": "^0.9.1", "p-defer": "^3.0.0", "which": "^2.0.1" }, From b291bc06ec13feeb6e010730edfad754a3b2dc1b Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 14 Jun 2021 09:19:23 +0200 Subject: [PATCH 212/447] fix: dialer leaking resources after stopping (#947) * fix: dialer leaking resources after stopping * chore: add error code to test --- src/connection-manager/index.js | 14 +++-- src/connection-manager/latency-monitor.js | 36 +++++++------ src/dialer/index.js | 34 +++++++++++- test/dialing/direct.spec.js | 64 +++++++++++++++++++++++ 4 files changed, 127 insertions(+), 21 deletions(-) diff --git a/src/connection-manager/index.js b/src/connection-manager/index.js index 7a865127b0..d63ebf7581 100644 --- a/src/connection-manager/index.js +++ b/src/connection-manager/index.js @@ -97,6 +97,11 @@ class ConnectionManager extends EventEmitter { this._autoDialTimeout = null this._checkMetrics = this._checkMetrics.bind(this) this._autoDial = this._autoDial.bind(this) + + this._latencyMonitor = new LatencyMonitor({ + latencyCheckIntervalMs: this._options.pollInterval, + dataEmitIntervalMs: this._options.pollInterval + }) } /** @@ -117,10 +122,7 @@ class ConnectionManager extends EventEmitter { } // latency monitor - this._latencyMonitor = new LatencyMonitor({ - latencyCheckIntervalMs: this._options.pollInterval, - dataEmitIntervalMs: this._options.pollInterval - }) + this._latencyMonitor.start() this._onLatencyMeasure = this._onLatencyMeasure.bind(this) this._latencyMonitor.on('data', this._onLatencyMeasure) @@ -138,7 +140,9 @@ class ConnectionManager extends EventEmitter { async stop () { this._autoDialTimeout && this._autoDialTimeout.clear() this._timer && this._timer.clear() - this._latencyMonitor && this._latencyMonitor.removeListener('data', this._onLatencyMeasure) + + this._latencyMonitor.removeListener('data', this._onLatencyMeasure) + this._latencyMonitor.stop() this._started = false await this._close() diff --git a/src/connection-manager/latency-monitor.js b/src/connection-manager/latency-monitor.js index 6c3061b91b..374794c85e 100644 --- a/src/connection-manager/latency-monitor.js +++ b/src/connection-manager/latency-monitor.js @@ -69,49 +69,55 @@ class LatencyMonitor extends EventEmitter { } that.asyncTestFn = asyncTestFn // If there is no asyncFn, we measure latency + } + start () { // If process: use high resolution timer if (globalThis.process && globalThis.process.hrtime) { // eslint-disable-line no-undef debug('Using process.hrtime for timing') - that.now = globalThis.process.hrtime // eslint-disable-line no-undef - that.getDeltaMS = (startTime) => { - const hrtime = that.now(startTime) + this.now = globalThis.process.hrtime // eslint-disable-line no-undef + this.getDeltaMS = (startTime) => { + const hrtime = this.now(startTime) return (hrtime[0] * 1000) + (hrtime[1] / 1000000) } // Let's try for a timer that only monotonically increases } else if (typeof window !== 'undefined' && window.performance && window.performance.now) { debug('Using performance.now for timing') - that.now = window.performance.now.bind(window.performance) - that.getDeltaMS = (startTime) => Math.round(that.now() - startTime) + this.now = window.performance.now.bind(window.performance) + this.getDeltaMS = (startTime) => Math.round(this.now() - startTime) } else { debug('Using Date.now for timing') - that.now = Date.now - that.getDeltaMS = (startTime) => that.now() - startTime + this.now = Date.now + this.getDeltaMS = (startTime) => this.now() - startTime } - that._latencyData = that._initLatencyData() + this._latencyData = this._initLatencyData() // We check for isBrowser because of browsers set max rates of timeouts when a page is hidden, // so we fall back to another library // See: http://stackoverflow.com/questions/6032429/chrome-timeouts-interval-suspended-in-background-tabs if (isBrowser()) { - that._visibilityChangeEmitter = new VisibilityChangeEmitter() + this._visibilityChangeEmitter = new VisibilityChangeEmitter() - that._visibilityChangeEmitter.on('visibilityChange', (pageInFocus) => { + this._visibilityChangeEmitter.on('visibilityChange', (pageInFocus) => { if (pageInFocus) { - that._startTimers() + this._startTimers() } else { - that._emitSummary() - that._stopTimers() + this._emitSummary() + this._stopTimers() } }) } - if (!that._visibilityChangeEmitter || that._visibilityChangeEmitter.isVisible()) { - that._startTimers() + if (!this._visibilityChangeEmitter || this._visibilityChangeEmitter.isVisible()) { + this._startTimers() } } + stop () { + this._stopTimers() + } + /** * Start internal timers * diff --git a/src/dialer/index.js b/src/dialer/index.js index 31919a1f44..3be5c36bc0 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -8,6 +8,7 @@ const errCode = require('err-code') const { Multiaddr } = require('multiaddr') // @ts-ignore timeout-abourt-controles does not export types const TimeoutController = require('timeout-abort-controller') +const { AbortError } = require('abortable-iterator') const { anySignal } = require('any-signal') const DialRequest = require('./dial-request') @@ -76,6 +77,7 @@ class Dialer { this.maxDialsPerPeer = maxDialsPerPeer this.tokens = [...new Array(maxParallelDials)].map((_, index) => index) this._pendingDials = new Map() + this._pendingDialTargets = new Map() for (const [key, value] of Object.entries(resolvers)) { Multiaddr.resolvers.set(key, value) @@ -94,6 +96,11 @@ class Dialer { } } this._pendingDials.clear() + + for (const pendingTarget of this._pendingDialTargets.values()) { + pendingTarget.reject(new AbortError('Dialer was destroyed')) + } + this._pendingDialTargets.clear() } /** @@ -107,7 +114,7 @@ class Dialer { * @returns {Promise} */ async connectToPeer (peer, options = {}) { - const dialTarget = await this._createDialTarget(peer) + const dialTarget = await this._createCancellableDialTarget(peer) if (!dialTarget.addrs.length) { throw errCode(new Error('The dial request has no valid addresses'), codes.ERR_NO_VALID_ADDRESSES) @@ -130,6 +137,31 @@ class Dialer { } } + /** + * Connects to a given `peer` by dialing all of its known addresses. + * The dial to the first address that is successfully able to upgrade a connection + * will be used. + * + * @param {PeerId|Multiaddr|string} peer - The peer to dial + * @returns {Promise} + */ + async _createCancellableDialTarget (peer) { + // Make dial target promise cancellable + const id = `${(parseInt(String(Math.random() * 1e9), 10)).toString() + Date.now()}` + const cancellablePromise = new Promise((resolve, reject) => { + this._pendingDialTargets.set(id, { resolve, reject }) + }) + + const dialTarget = await Promise.race([ + this._createDialTarget(peer), + cancellablePromise + ]) + + this._pendingDialTargets.delete(id) + + return dialTarget + } + /** * Creates a DialTarget. The DialTarget is used to create and track * the DialRequest to a given peer. diff --git a/test/dialing/direct.spec.js b/test/dialing/direct.spec.js index a074e02f97..1dc2954479 100644 --- a/test/dialing/direct.spec.js +++ b/test/dialing/direct.spec.js @@ -290,6 +290,34 @@ describe('Dialing (direct, WebSockets)', () => { } }) + it('should cancel pending dial targets before proceeding', async () => { + const dialer = new Dialer({ + transportManager: localTM, + peerStore: { + addressBook: { + set: () => { } + } + } + }) + + sinon.stub(dialer, '_createDialTarget').callsFake(() => { + const deferredDial = pDefer() + return deferredDial.promise + }) + + // Perform dial + const dialPromise = dialer.connectToPeer(peerId) + + // Let the call stack run + await delay(0) + + dialer.destroy() + + await expect(dialPromise) + .to.eventually.be.rejected() + .and.to.have.property('code', 'ABORT_ERR') + }) + describe('libp2p.dialer', () => { const transportKey = Transport.prototype[Symbol.toStringTag] let libp2p @@ -462,6 +490,42 @@ describe('Dialing (direct, WebSockets)', () => { await libp2p.hangUp(remoteAddr) }) + it('should cancel pending dial targets and stop', async () => { + const [, remotePeerId] = await createPeerId({ number: 2 }) + + libp2p = new Libp2p({ + peerId, + modules: { + transport: [Transport], + streamMuxer: [Muxer], + connEncryption: [Crypto] + }, + config: { + transport: { + [transportKey]: { + filter: filters.all + } + } + } + }) + + sinon.stub(libp2p.dialer, '_createDialTarget').callsFake(() => { + const deferredDial = pDefer() + return deferredDial.promise + }) + + // Perform dial + const dialPromise = libp2p.dial(remotePeerId) + + // Let the call stack run + await delay(0) + + await libp2p.stop() + await expect(dialPromise) + .to.eventually.be.rejected() + .and.to.have.property('code', 'ABORT_ERR') + }) + it('should abort pending dials on stop', async () => { libp2p = new Libp2p({ peerId, From b9988adce966af3288d6c5e2ebaba8f1fdb9855e Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 14 Jun 2021 09:56:53 +0200 Subject: [PATCH 213/447] chore: update contributors --- package.json | 57 ++++++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 498a9d12a1..6f4177c1e6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.31.6", + "version": "0.31.7", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", @@ -179,50 +179,51 @@ "Friedel Ziegelmayer ", "Maciej Krüger ", "Hugo Dias ", - "Volker Mische ", "dirkmc ", + "Volker Mische ", "Richard Littauer ", + "Ryan Bell ", "a1300 ", "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", - "Andrew Nesbitt ", - "Elven ", - "Giovanni T. Parra ", - "Ryan Bell ", "Samlior ", + "Andrew Nesbitt ", "Thomas Eizinger ", - "Didrik Nordström ", - "Felipe Martins ", - "Fei Liu ", - "Joel Gustafson ", - "Julien Bouquillon ", - "Kevin Kwok ", - "Kevin Lacker ", - "Ethan Lam ", - "Miguel Mota ", - "Nuno Nogueira ", - "Dmitriy Ryajov ", - "Philipp Muens ", + "Franck Royer ", + "Giovanni T. Parra ", + "Elven ", + "Didrik Nordström ", "RasmusErik Voel Jensen ", - "Diogo Silva ", - "robertkiel ", "Smite Chow ", "Soeren ", "Sönke Hahn ", "TJKoury ", "Tiago Alves ", - "Daijiro Wachi ", "Yusef Napora ", "Zane Starr ", - "Aleksei ", - "Cindy Wu ", - "Chris Bratlien ", "ebinks ", + "isan_rivkin ", + "mcclure ", + "robertkiel ", + "Aleksei ", "Bernd Strehl ", - "Francis Gulotta ", - "Franck Royer ", + "Chris Bratlien ", + "Cindy Wu ", + "Daijiro Wachi ", + "Diogo Silva ", + "Dmitriy Ryajov ", + "Ethan Lam ", + "Fei Liu ", + "Felipe Martins ", "Florian-Merle ", - "isan_rivkin ", + "Francis Gulotta ", "Henrique Dias ", - "Irakli Gozalishvili " + "Irakli Gozalishvili ", + "Joel Gustafson ", + "Julien Bouquillon ", + "Kevin Kwok ", + "Kevin Lacker ", + "Miguel Mota ", + "Nuno Nogueira ", + "Philipp Muens " ] } From f7183e8afd49deb40d03fca708a3070c1f16610f Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 14 Jun 2021 09:56:53 +0200 Subject: [PATCH 214/447] chore: release version v0.31.7 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84b64ce09f..afea42d705 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## [0.31.7](https://github.com/libp2p/js-libp2p/compare/v0.31.6...v0.31.7) (2021-06-14) + + +### Bug Fixes + +* chat example with new multiaddr ([#946](https://github.com/libp2p/js-libp2p/issues/946)) ([d8ba284](https://github.com/libp2p/js-libp2p/commit/d8ba2848833d9fb8a963d1b7c8d27062c6f829da)) +* dialer leaking resources after stopping ([#947](https://github.com/libp2p/js-libp2p/issues/947)) ([b291bc0](https://github.com/libp2p/js-libp2p/commit/b291bc06ec13feeb6e010730edfad754a3b2dc1b)) + + + ## [0.31.6](https://github.com/libp2p/js-libp2p/compare/v0.31.5...v0.31.6) (2021-05-27) From 39b03586e84935f9bfe3038a489997dd84d4aa34 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 16 Jun 2021 09:09:26 +0200 Subject: [PATCH 215/447] chore: use libp2p-tcp with types (#952) --- package.json | 2 +- test/ts-use/package.json | 4 ++-- test/ts-use/src/main.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 6f4177c1e6..74c158c1f4 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,7 @@ "libp2p-mdns": "^0.16.0", "libp2p-mplex": "^0.10.1", "libp2p-noise": "^3.0.0", - "libp2p-tcp": "^0.15.4", + "libp2p-tcp": "^0.16.0", "libp2p-webrtc-star": "^0.22.2", "libp2p-websockets": "^0.15.8", "multihashes": "^4.0.2", diff --git a/test/ts-use/package.json b/test/ts-use/package.json index 0d02583bb8..6caaeca324 100644 --- a/test/ts-use/package.json +++ b/test/ts-use/package.json @@ -8,13 +8,13 @@ "libp2p-bootstrap": "^0.12.2", "libp2p-delegated-content-routing": "^0.9.0", "libp2p-delegated-peer-routing": "^0.8.2", - "libp2p-gossipsub": "^0.8.0", + "libp2p-gossipsub": "^0.9.0", "libp2p-interfaces": "^0.10.1", "libp2p-kad-dht": "^0.21.0", "libp2p-mplex": "^0.10.2", "libp2p-noise": "^2.0.5", "libp2p-record": "^0.10.2", - "libp2p-tcp": "^0.15.3", + "libp2p-tcp": "^0.16.0", "libp2p-websockets": "^0.15.3", "peer-id": "^0.14.3" }, diff --git a/test/ts-use/src/main.ts b/test/ts-use/src/main.ts index 0efe3c64ab..3d18cd3a3e 100644 --- a/test/ts-use/src/main.ts +++ b/test/ts-use/src/main.ts @@ -1,7 +1,7 @@ import Libp2p = require('libp2p') import Libp2pRecord = require('libp2p-record') +import TCP = require('libp2p-tcp') -const TCP = require('libp2p-tcp') const WEBSOCKETS = require('libp2p-websockets') const NOISE = require('libp2p-noise') const MPLEX = require('libp2p-mplex') From 13cf4761489d59b22924bb8ec2ec6dbe207b280c Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 9 Jul 2021 07:43:34 +0100 Subject: [PATCH 216/447] chore: update to new multiformats (#948) BREAKING CHANGE: uses the CID class from the new multiformats module Co-authored-by: Vasco Santos --- .github/workflows/main.yml | 4 ++ examples/libp2p-in-the-browser/package.json | 10 ++-- examples/peer-and-content-routing/2.js | 6 +-- examples/peer-and-content-routing/README.md | 2 +- examples/webrtc-direct/package.json | 10 ++-- package.json | 52 ++++++++++---------- src/circuit/auto-relay.js | 2 +- src/circuit/transport.js | 4 +- src/circuit/utils.js | 8 +-- src/content-routing/index.js | 2 +- src/peer-store/index.js | 2 +- src/peer-store/persistent/index.js | 5 +- src/record/peer-record/consts.js | 4 +- test/content-routing/content-routing.node.js | 10 ++-- test/insecure/compliance.spec.js | 2 +- test/keychain/peerid.spec.js | 6 +-- test/record/peer-record.spec.js | 2 +- test/relay/relay.node.js | 2 +- test/ts-use/package.json | 26 +++++----- 19 files changed, 81 insertions(+), 78 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4b3d6ad7f2..ff8e94d70f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,6 +12,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - name: Use Node.js 14 + uses: actions/setup-node@v1 + with: + node-version: 14 - run: npm install - run: npx aegir lint - uses: gozala/typescript-error-reporter-action@v1.0.8 diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json index 9b928e8f7b..bb997142bb 100644 --- a/examples/libp2p-in-the-browser/package.json +++ b/examples/libp2p-in-the-browser/package.json @@ -17,11 +17,11 @@ "dependencies": { "@babel/preset-env": "^7.13.0", "libp2p": "../../", - "libp2p-bootstrap": "^0.12.1", - "libp2p-mplex": "^0.10.0", - "libp2p-noise": "^2.0.0", - "libp2p-webrtc-star": "^0.22.0", - "libp2p-websockets": "^0.15.0" + "libp2p-bootstrap": "^0.13.0", + "libp2p-mplex": "^0.10.4", + "libp2p-noise": "^4.0.0", + "libp2p-webrtc-star": "^0.23.0", + "libp2p-websockets": "^0.16.1" }, "devDependencies": { "@babel/cli": "^7.13.10", diff --git a/examples/peer-and-content-routing/2.js b/examples/peer-and-content-routing/2.js index 14f513ecfd..928234b2fa 100644 --- a/examples/peer-and-content-routing/2.js +++ b/examples/peer-and-content-routing/2.js @@ -5,7 +5,7 @@ const Libp2p = require('../../') const TCP = require('libp2p-tcp') const Mplex = require('libp2p-mplex') const { NOISE } = require('libp2p-noise') -const CID = require('cids') +const { CID } = require('multiformats/cid') const KadDHT = require('libp2p-kad-dht') const all = require('it-all') @@ -51,10 +51,10 @@ const createNode = async () => { // Wait for onConnect handlers in the DHT await delay(100) - const cid = new CID('QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL') + const cid = CID.parse('QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL') await node1.contentRouting.provide(cid) - console.log('Node %s is providing %s', node1.peerId.toB58String(), cid.toBaseEncodedString()) + console.log('Node %s is providing %s', node1.peerId.toB58String(), cid.toString()) // wait for propagation await delay(300) diff --git a/examples/peer-and-content-routing/README.md b/examples/peer-and-content-routing/README.md index 9832003e22..197ecf32fc 100644 --- a/examples/peer-and-content-routing/README.md +++ b/examples/peer-and-content-routing/README.md @@ -81,7 +81,7 @@ Instead of calling `peerRouting.findPeer`, we will use `contentRouting.provide` ```JavaScript await node1.contentRouting.provide(cid) -console.log('Node %s is providing %s', node1.peerId.toB58String(), cid.toBaseEncodedString()) +console.log('Node %s is providing %s', node1.peerId.toB58String(), cid.toString()) const provs = await all(node3.contentRouting.findProviders(cid, { timeout: 5000 })) diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index d96c2aed5e..c04ad84f67 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -21,11 +21,11 @@ }, "dependencies": { "libp2p": "../../", - "libp2p-bootstrap": "^0.12.1", - "libp2p-mplex": "^0.10.1", - "libp2p-noise": "^2.0.1", - "libp2p-webrtc-direct": "^0.6.0", - "peer-id": "^0.14.3" + "libp2p-bootstrap": "^0.13.0", + "libp2p-mplex": "^0.10.4", + "libp2p-noise": "^4.0.0", + "libp2p-webrtc-direct": "^0.7.0", + "peer-id": "^0.15.0" }, "browser": { "ipfs": "ipfs/dist/index.min.js" diff --git a/package.json b/package.json index 74c158c1f4..28ec8e6630 100644 --- a/package.json +++ b/package.json @@ -84,14 +84,13 @@ "aggregate-error": "^3.1.0", "any-signal": "^2.1.1", "bignumber.js": "^9.0.1", - "cids": "^1.1.5", "class-is": "^1.1.0", "debug": "^4.3.1", "err-code": "^3.0.0", "es6-promisify": "^6.1.1", "events": "^3.3.0", "hashlru": "^2.3.0", - "interface-datastore": "^4.0.0", + "interface-datastore": "^5.1.1", "it-all": "^1.0.4", "it-buffer": "^0.1.2", "it-drain": "^1.0.3", @@ -100,17 +99,17 @@ "it-handshake": "^2.0.0", "it-length-prefixed": "^5.0.2", "it-map": "^1.0.4", - "it-merge": "1.0.0", + "it-merge": "^1.0.0", "it-pipe": "^1.1.0", - "it-take": "1.0.0", + "it-take": "^1.0.0", "libp2p-crypto": "^0.19.4", - "libp2p-interfaces": "^0.10.4", - "libp2p-utils": "^0.3.1", - "mafmt": "^9.0.0", + "libp2p-interfaces": "^1.0.0", + "libp2p-interfaces-compliance-tests": "^1.0.0", + "libp2p-utils": "^0.4.0", + "mafmt": "^10.0.0", "merge-options": "^3.0.4", - "multiaddr": "^9.0.1", - "multicodec": "^3.0.1", - "multihashing-async": "^2.1.2", + "multiaddr": "^10.0.0", + "multiformats": "^9.0.0", "multistream-select": "^2.0.0", "mutable-proxy": "^1.0.0", "node-forge": "^0.10.0", @@ -118,13 +117,13 @@ "p-fifo": "^1.0.0", "p-retry": "^4.4.0", "p-settle": "^4.1.1", - "peer-id": "^0.14.2", + "peer-id": "^0.15.0", "private-ip": "^2.1.0", "protobufjs": "^6.10.2", "retimer": "^3.0.0", "sanitize-filename": "^1.6.3", "set-delayed-interval": "^1.0.0", - "streaming-iterables": "^5.0.2", + "streaming-iterables": "^6.0.0", "timeout-abort-controller": "^1.1.1", "varint": "^6.0.0", "wherearewe": "^1.0.0", @@ -133,7 +132,8 @@ "devDependencies": { "@nodeutils/defaults-deep": "^1.1.0", "@types/es6-promisify": "^6.0.0", - "@types/node-forge": "^0.9.7", + "@types/node": "^16.0.1", + "@types/node-forge": "^0.10.1", "@types/varint": "^6.0.0", "abortable-iterator": "^3.0.0", "aegir": "^33.1.1", @@ -142,29 +142,29 @@ "interop-libp2p": "^0.4.0", "into-stream": "^6.0.0", "ipfs-http-client": "^50.1.1", - "it-concat": "^1.0.0", + "it-concat": "^2.0.0", "it-pair": "^1.0.0", "it-pushable": "^1.4.0", "libp2p": ".", - "libp2p-bootstrap": "^0.12.3", - "libp2p-delegated-content-routing": "^0.10.0", - "libp2p-delegated-peer-routing": "^0.9.0", - "libp2p-floodsub": "^0.25.0", - "libp2p-gossipsub": "^0.9.0", - "libp2p-kad-dht": "^0.22.0", - "libp2p-mdns": "^0.16.0", + "libp2p-bootstrap": "^0.13.0", + "libp2p-delegated-content-routing": "^0.11.0", + "libp2p-delegated-peer-routing": "^0.10.0", + "libp2p-floodsub": "^0.26.0", + "libp2p-gossipsub": "^0.10.0", + "libp2p-kad-dht": "^0.23.0", + "libp2p-mdns": "^0.17.0", "libp2p-mplex": "^0.10.1", - "libp2p-noise": "^3.0.0", - "libp2p-tcp": "^0.16.0", - "libp2p-webrtc-star": "^0.22.2", - "libp2p-websockets": "^0.15.8", + "libp2p-noise": "^4.0.0", + "libp2p-tcp": "^0.17.0", + "libp2p-webrtc-star": "^0.23.0", + "libp2p-websockets": "^0.16.0", "multihashes": "^4.0.2", "nock": "^13.0.3", "p-defer": "^3.0.0", "p-times": "^3.0.0", "p-wait-for": "^3.2.0", "rimraf": "^3.0.2", - "sinon": "^10.0.0", + "sinon": "^11.1.1", "uint8arrays": "^2.1.3", "util": "^0.12.3" }, diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js index 20174e9921..69fc0f76ff 100644 --- a/src/circuit/auto-relay.js +++ b/src/circuit/auto-relay.js @@ -222,7 +222,7 @@ class AutoRelay { continue } - const peerId = PeerId.createFromCID(id) + const peerId = PeerId.createFromB58String(id) const connection = this._connectionManager.get(peerId) // If not connected, store for possible later use. diff --git a/src/circuit/transport.js b/src/circuit/transport.js index b220620398..1dc284cab4 100644 --- a/src/circuit/transport.js +++ b/src/circuit/transport.js @@ -136,8 +136,8 @@ class Circuit { throw errCode(new Error(errMsg), codes.ERR_RELAYED_DIAL) } - const relayPeer = PeerId.createFromCID(relayId) - const destinationPeer = PeerId.createFromCID(destinationId) + const relayPeer = PeerId.createFromB58String(relayId) + const destinationPeer = PeerId.createFromB58String(destinationId) let disconnectOnFailure = false let relayConnection = this._connectionManager.get(relayPeer) diff --git a/src/circuit/utils.js b/src/circuit/utils.js index 3e4ae4c540..7f681a54a9 100644 --- a/src/circuit/utils.js +++ b/src/circuit/utils.js @@ -1,7 +1,7 @@ 'use strict' -const CID = require('cids') -const multihashing = require('multihashing-async') +const { CID } = require('multiformats/cid') +const { sha256 } = require('multiformats/hashes/sha2') /** * Convert a namespace string into a cid. @@ -11,7 +11,7 @@ const multihashing = require('multihashing-async') */ module.exports.namespaceToCid = async (namespace) => { const bytes = new TextEncoder().encode(namespace) - const hash = await multihashing(bytes, 'sha2-256') + const hash = await sha256.digest(bytes) - return new CID(hash) + return CID.createV0(hash) } diff --git a/src/content-routing/index.js b/src/content-routing/index.js index 00211f71ff..7fc4b4fb5c 100644 --- a/src/content-routing/index.js +++ b/src/content-routing/index.js @@ -15,7 +15,7 @@ const { pipe } = require('it-pipe') /** * @typedef {import('peer-id')} PeerId * @typedef {import('multiaddr').Multiaddr} Multiaddr - * @typedef {import('cids')} CID + * @typedef {import('multiformats/cid').CID} CID * @typedef {import('libp2p-interfaces/src/content-routing/types').ContentRouting} ContentRoutingModule */ diff --git a/src/peer-store/index.js b/src/peer-store/index.js index b3df1bbb94..72fd9043aa 100644 --- a/src/peer-store/index.js +++ b/src/peer-store/index.js @@ -99,7 +99,7 @@ class PeerStore extends EventEmitter { const peersData = new Map() storedPeers.forEach((idStr) => { - peersData.set(idStr, this.get(PeerId.createFromCID(idStr))) + peersData.set(idStr, this.get(PeerId.createFromB58String(idStr))) }) return peersData diff --git a/src/peer-store/persistent/index.js b/src/peer-store/persistent/index.js index 8d7d50ea9a..d0bd3dcc30 100644 --- a/src/peer-store/persistent/index.js +++ b/src/peer-store/persistent/index.js @@ -7,6 +7,7 @@ const log = Object.assign(debug('libp2p:persistent-peer-store'), { const { Key } = require('interface-datastore') const { Multiaddr } = require('multiaddr') const PeerId = require('peer-id') +const { base32 } = require('multiformats/bases/base32') const PeerStore = require('..') @@ -195,7 +196,7 @@ class PersistentPeerStore extends PeerStore { const batch = this._datastore.batch() for (const peerIdStr of commitPeers) { // PeerId - const peerId = this.keyBook.data.get(peerIdStr) || PeerId.createFromCID(peerIdStr) + const peerId = this.keyBook.data.get(peerIdStr) || PeerId.createFromB58String(peerIdStr) // Address Book this._batchAddressBook(peerId, batch) @@ -346,7 +347,7 @@ class PersistentPeerStore extends PeerStore { async _processDatastoreEntry ({ key, value }) { try { const keyParts = key.toString().split('/') - const peerId = PeerId.createFromCID(keyParts[3]) + const peerId = PeerId.createFromBytes(base32.decode(keyParts[3])) let decoded switch (keyParts[2]) { diff --git a/src/record/peer-record/consts.js b/src/record/peer-record/consts.js index 9a83e03067..9b35427ec7 100644 --- a/src/record/peer-record/consts.js +++ b/src/record/peer-record/consts.js @@ -1,9 +1,7 @@ 'use strict' -const multicodec = require('multicodec') - // The domain string used for peer records contained in a Envelope. -const domain = multicodec.getName(multicodec.LIBP2P_PEER_RECORD) || 'libp2p-peer-record' +const domain = 'libp2p-peer-record' // The type hint used to identify peer records in a Envelope. // Defined in https://github.com/multiformats/multicodec/blob/master/table.csv diff --git a/test/content-routing/content-routing.node.js b/test/content-routing/content-routing.node.js index 6861230952..0a8db15b64 100644 --- a/test/content-routing/content-routing.node.js +++ b/test/content-routing/content-routing.node.js @@ -8,7 +8,7 @@ const sinon = require('sinon') const pDefer = require('p-defer') const mergeOptions = require('merge-options') -const CID = require('cids') +const { CID } = require('multiformats/cid') const ipfsHttpClient = require('ipfs-http-client') const DelegatedContentRouter = require('libp2p-delegated-content-routing') const { Multiaddr } = require('multiaddr') @@ -164,7 +164,7 @@ describe('content-routing', () => { }) it('should be able to register as a provider', async () => { - const cid = new CID('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB') + const cid = CID.parse('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB') const provider = 'QmZNgCqZCvTsi3B4Vt7gsSqpkqDpE7M2Y9TDmEhbDb4ceF' const mockBlockApi = nock('http://0.0.0.0:60197') @@ -191,7 +191,7 @@ describe('content-routing', () => { }) it('should handle errors when registering as a provider', async () => { - const cid = new CID('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB') + const cid = CID.parse('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB') const mockApi = nock('http://0.0.0.0:60197') // mock the block/stat call .post('/api/v0/block/stat') @@ -205,7 +205,7 @@ describe('content-routing', () => { }) it('should be able to find providers', async () => { - const cid = new CID('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB') + const cid = CID.parse('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB') const provider = 'QmZNgCqZCvTsi3B4Vt7gsSqpkqDpE7M2Y9TDmEhbDb4ceF' const mockApi = nock('http://0.0.0.0:60197') @@ -227,7 +227,7 @@ describe('content-routing', () => { }) it('should handle errors when finding providers', async () => { - const cid = new CID('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB') + const cid = CID.parse('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB') const mockApi = nock('http://0.0.0.0:60197') .post('/api/v0/dht/findprovs') .query(true) diff --git a/test/insecure/compliance.spec.js b/test/insecure/compliance.spec.js index 74fdf2071b..f124dde9fa 100644 --- a/test/insecure/compliance.spec.js +++ b/test/insecure/compliance.spec.js @@ -1,7 +1,7 @@ 'use strict' /* eslint-env mocha */ -const tests = require('libp2p-interfaces/src/crypto/tests') +const tests = require('libp2p-interfaces-compliance-tests/src/crypto') const plaintext = require('../../src/insecure/plaintext') describe('plaintext compliance', () => { diff --git a/test/keychain/peerid.spec.js b/test/keychain/peerid.spec.js index fd393d6a5c..50d42e13f5 100644 --- a/test/keychain/peerid.spec.js +++ b/test/keychain/peerid.spec.js @@ -3,7 +3,7 @@ const { expect } = require('aegir/utils/chai') const PeerId = require('peer-id') -const multihash = require('multihashes') +const { base58btc } = require('multiformats/bases/base58') const crypto = require('libp2p-crypto') const rsaUtils = require('libp2p-crypto/src/keys/rsa-utils') const rsaClass = require('libp2p-crypto/src/keys/rsa-class') @@ -40,7 +40,7 @@ describe('peer ID', () => { const jwk = rsaUtils.pkixToJwk(publicKeyDer) const rsa = new rsaClass.RsaPublicKey(jwk) const keyId = await rsa.hash() - const kids = multihash.toB58String(keyId) + const kids = base58btc.encode(keyId).substring(1) expect(kids).to.equal(peer.toB58String()) }) @@ -54,7 +54,7 @@ describe('peer ID', () => { } const rsa = new rsaClass.RsaPublicKey(jwk) const keyId = await rsa.hash() - const kids = multihash.toB58String(keyId) + const kids = base58btc.encode(keyId).substring(1) expect(kids).to.equal(peer.toB58String()) }) diff --git a/test/record/peer-record.spec.js b/test/record/peer-record.spec.js index 7865e2dfad..532c79a5fb 100644 --- a/test/record/peer-record.spec.js +++ b/test/record/peer-record.spec.js @@ -3,7 +3,7 @@ const { expect } = require('aegir/utils/chai') -const tests = require('libp2p-interfaces/src/record/tests') +const tests = require('libp2p-interfaces-compliance-tests/src/record') const { Multiaddr } = require('multiaddr') const PeerId = require('peer-id') diff --git a/test/relay/relay.node.js b/test/relay/relay.node.js index 033f21a3fc..b75fb1cd31 100644 --- a/test/relay/relay.node.js +++ b/test/relay/relay.node.js @@ -52,7 +52,7 @@ describe('Dialing (via relay, TCP)', () => { await libp2p.stop() // Clear the peer stores for (const peerIdStr of libp2p.peerStore.peers.keys()) { - const peerId = PeerId.createFromCID(peerIdStr) + const peerId = PeerId.createFromB58String(peerIdStr) libp2p.peerStore.delete(peerId) } })) diff --git a/test/ts-use/package.json b/test/ts-use/package.json index 6caaeca324..8a0670c65a 100644 --- a/test/ts-use/package.json +++ b/test/ts-use/package.json @@ -2,21 +2,21 @@ "name": "ts-use", "private": true, "dependencies": { - "datastore-level": "^4.0.0", - "ipfs-http-client": "^49.0.4", + "datastore-level": "^6.0.0", + "ipfs-http-client": "^50.1.2", "libp2p": "file:../..", - "libp2p-bootstrap": "^0.12.2", - "libp2p-delegated-content-routing": "^0.9.0", - "libp2p-delegated-peer-routing": "^0.8.2", + "libp2p-bootstrap": "^0.13.0", + "libp2p-delegated-content-routing": "^0.11.0", + "libp2p-delegated-peer-routing": "^0.10.0", "libp2p-gossipsub": "^0.9.0", - "libp2p-interfaces": "^0.10.1", - "libp2p-kad-dht": "^0.21.0", - "libp2p-mplex": "^0.10.2", - "libp2p-noise": "^2.0.5", - "libp2p-record": "^0.10.2", - "libp2p-tcp": "^0.16.0", - "libp2p-websockets": "^0.15.3", - "peer-id": "^0.14.3" + "libp2p-interfaces": "^1.0.1", + "libp2p-kad-dht": "^0.23.1", + "libp2p-mplex": "^0.10.4", + "libp2p-noise": "^4.0.0", + "libp2p-record": "^0.10.4", + "libp2p-tcp": "^0.17.1", + "libp2p-websockets": "^0.16.1", + "peer-id": "^0.15.0" }, "scripts": { "build": "npx tsc", From af723b355e1ddf4aecf439f81c3aa67613d45fa4 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 9 Jul 2021 08:46:24 +0200 Subject: [PATCH 217/447] fix: do not allow dial to large number of multiaddrs (#954) --- doc/CONFIGURATION.md | 2 ++ src/constants.js | 1 + src/dialer/index.js | 11 ++++++++++- src/errors.js | 1 + test/dialing/direct.spec.js | 20 ++++++++++++++++++++ 5 files changed, 34 insertions(+), 1 deletion(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index dcf8575618..a1a5149ec7 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -525,6 +525,7 @@ Dialing in libp2p can be configured to limit the rate of dialing, and how long d | Name | Type | Description | |------|------|-------------| | maxParallelDials | `number` | How many multiaddrs we can dial in parallel. | +| maxAddrsToDial | `number` | How many multiaddrs is the dial allowed to dial for a single peer. | | 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 | @@ -549,6 +550,7 @@ const node = await Libp2p.create({ }, dialer: { maxParallelDials: 100, + maxAddrsToDial: 25, maxDialsPerPeer: 4, dialTimeout: 30e3, resolvers: { diff --git a/src/constants.js b/src/constants.js index 08ad156971..76a2abd863 100644 --- a/src/constants.js +++ b/src/constants.js @@ -4,6 +4,7 @@ module.exports = { DIAL_TIMEOUT: 30e3, // How long in ms a dial attempt is allowed to take MAX_PARALLEL_DIALS: 100, // Maximum allowed concurrent dials MAX_PER_PEER_DIALS: 4, // Allowed parallel dials per DialRequest + MAX_ADDRS_TO_DIAL: 25, // Maximum number of allowed addresses to attempt to dial METRICS: { computeThrottleMaxQueueSize: 1000, computeThrottleTimeout: 2000, diff --git a/src/dialer/index.js b/src/dialer/index.js index 3be5c36bc0..65afe266e7 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -19,7 +19,8 @@ const { codes } = require('../errors') const { DIAL_TIMEOUT, MAX_PARALLEL_DIALS, - MAX_PER_PEER_DIALS + MAX_PER_PEER_DIALS, + MAX_ADDRS_TO_DIAL } = require('../constants') /** @@ -40,6 +41,7 @@ const { * @typedef {Object} DialerOptions * @property {(addresses: Address[]) => Address[]} [options.addressSorter = publicAddressesFirst] - Sort the known addresses of a peer before trying to dial. * @property {number} [maxParallelDials = MAX_PARALLEL_DIALS] - Number of max concurrent dials. + * @property {number} [maxAddrsToDial = MAX_ADDRS_TO_DIAL] - Number of max addresses to dial for a given peer. * @property {number} [maxDialsPerPeer = MAX_PER_PEER_DIALS] - Number of max concurrent dials per peer. * @property {number} [dialTimeout = DIAL_TIMEOUT] - How long a dial attempt is allowed to take. * @property {Record} [resolvers = {}] - multiaddr resolvers to use when dialing @@ -65,6 +67,7 @@ class Dialer { peerStore, addressSorter = publicAddressesFirst, maxParallelDials = MAX_PARALLEL_DIALS, + maxAddrsToDial = MAX_ADDRS_TO_DIAL, dialTimeout = DIAL_TIMEOUT, maxDialsPerPeer = MAX_PER_PEER_DIALS, resolvers = {} @@ -73,6 +76,7 @@ class Dialer { this.peerStore = peerStore this.addressSorter = addressSorter this.maxParallelDials = maxParallelDials + this.maxAddrsToDial = maxAddrsToDial this.timeout = dialTimeout this.maxDialsPerPeer = maxDialsPerPeer this.tokens = [...new Array(maxParallelDials)].map((_, index) => index) @@ -198,6 +202,11 @@ class Dialer { // Multiaddrs not supported by the available transports will be filtered out. const supportedAddrs = addrs.filter(a => this.transportManager.transportForMultiaddr(a)) + if (supportedAddrs.length > this.maxAddrsToDial) { + this.peerStore.delete(id) + throw errCode(new Error('dial with more addresses than allowed'), codes.ERR_TOO_MANY_ADDRESSES) + } + return { id: id.toB58String(), addrs: supportedAddrs diff --git a/src/errors.js b/src/errors.js index 0b73b983db..5b4d070fb2 100644 --- a/src/errors.js +++ b/src/errors.js @@ -16,6 +16,7 @@ exports.codes = { ERR_CONNECTION_FAILED: 'ERR_CONNECTION_FAILED', ERR_NODE_NOT_STARTED: 'ERR_NODE_NOT_STARTED', ERR_ALREADY_ABORTED: 'ERR_ALREADY_ABORTED', + ERR_TOO_MANY_ADDRESSES: 'ERR_TOO_MANY_ADDRESSES', ERR_NO_VALID_ADDRESSES: 'ERR_NO_VALID_ADDRESSES', ERR_RELAYED_DIAL: 'ERR_RELAYED_DIAL', ERR_DIALED_SELF: 'ERR_DIALED_SELF', diff --git a/test/dialing/direct.spec.js b/test/dialing/direct.spec.js index 1dc2954479..a24a1ca2f4 100644 --- a/test/dialing/direct.spec.js +++ b/test/dialing/direct.spec.js @@ -177,6 +177,26 @@ describe('Dialing (direct, WebSockets)', () => { .and.to.have.property('code', ErrorCodes.ERR_TIMEOUT) }) + it('should throw when a peer advertises more than the allowed number of peers', async () => { + const spy = sinon.spy() + const dialer = new Dialer({ + transportManager: localTM, + maxAddrsToDial: 10, + peerStore: { + delete: spy, + addressBook: { + add: () => { }, + getMultiaddrsForPeer: () => Array.from({ length: 11 }, (_, i) => new Multiaddr(`/ip4/127.0.0.1/tcp/1500${i}/ws/p2p/12D3KooWHFKTMzwerBtsVmtz4ZZEQy2heafxzWw6wNn5PPYkBxJ5`)) + } + } + }) + + await expect(dialer.connectToPeer(remoteAddr)) + .to.eventually.be.rejected() + .and.to.have.property('code', ErrorCodes.ERR_TOO_MANY_ADDRESSES) + expect(spy.calledOnce).to.be.true() + }) + it('should sort addresses on dial', async () => { const peerMultiaddrs = [ new Multiaddr('/ip4/127.0.0.1/tcp/15001/ws'), From 608564b0339fe2429d1cf0d1e9b9b84fff447849 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 9 Jul 2021 09:01:24 +0200 Subject: [PATCH 218/447] chore: update contributors --- package.json | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 28ec8e6630..071f4be0c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.31.7", + "version": "0.32.0-rc.0", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", @@ -169,8 +169,8 @@ "util": "^0.12.3" }, "contributors": [ - "David Dias ", "Vasco Santos ", + "David Dias ", "Jacob Heun ", "Alex Potsides ", "Alan Shaw ", @@ -180,7 +180,9 @@ "Maciej Krüger ", "Hugo Dias ", "dirkmc ", + "Chris Dostert ", "Volker Mische ", + "zeim839 <50573884+zeim839@users.noreply.github.com>", "Richard Littauer ", "Ryan Bell ", "a1300 ", @@ -190,6 +192,7 @@ "Thomas Eizinger ", "Franck Royer ", "Giovanni T. Parra ", + "acolytec3 <17355484+acolytec3@users.noreply.github.com>", "Elven ", "Didrik Nordström ", "RasmusErik Voel Jensen ", @@ -202,8 +205,13 @@ "Zane Starr ", "ebinks ", "isan_rivkin ", + "mayerwin ", "mcclure ", + "phillmac ", "robertkiel ", + "shresthagrawal <34920931+shresthagrawal@users.noreply.github.com>", + "swedneck <40505480+swedneck@users.noreply.github.com>", + "Marcin Tojek ", "Aleksei ", "Bernd Strehl ", "Chris Bratlien ", @@ -216,12 +224,18 @@ "Felipe Martins ", "Florian-Merle ", "Francis Gulotta ", + "Guy Sviry <32539816+guysv@users.noreply.github.com>", "Henrique Dias ", "Irakli Gozalishvili ", "Joel Gustafson ", + "John Rees ", + "João Santos ", "Julien Bouquillon ", "Kevin Kwok ", "Kevin Lacker ", + "Lars Gierth ", + "Aditya Bose <13054902+adbose@users.noreply.github.com>", + "Michael Burns <5170+mburns@users.noreply.github.com>", "Miguel Mota ", "Nuno Nogueira ", "Philipp Muens " From 664ba2d1e7b8ca7dba3daeed63b144e206419e22 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 9 Jul 2021 09:01:25 +0200 Subject: [PATCH 219/447] chore: release version v0.32.0-rc.0 --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index afea42d705..230ef6734e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +# [0.32.0-rc.0](https://github.com/libp2p/js-libp2p/compare/v0.31.7...v0.32.0-rc.0) (2021-07-09) + + +### Bug Fixes + +* do not allow dial to large number of multiaddrs ([#954](https://github.com/libp2p/js-libp2p/issues/954)) ([af723b3](https://github.com/libp2p/js-libp2p/commit/af723b355e1ddf4aecf439f81c3aa67613d45fa4)) + + +### chore + +* update to new multiformats ([#948](https://github.com/libp2p/js-libp2p/issues/948)) ([13cf476](https://github.com/libp2p/js-libp2p/commit/13cf4761489d59b22924bb8ec2ec6dbe207b280c)) + + +### BREAKING CHANGES + +* uses the CID class from the new multiformats module + +Co-authored-by: Vasco Santos + + + ## [0.31.7](https://github.com/libp2p/js-libp2p/compare/v0.31.6...v0.31.7) (2021-06-14) From 67b97e32daf683498e74710fd87c6c32e78ada85 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 15 Jul 2021 12:34:15 +0200 Subject: [PATCH 220/447] chore: add migration guide to 0.32 (#957) --- doc/migrations/v0.31-v0.32.md | 36 +++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 doc/migrations/v0.31-v0.32.md diff --git a/doc/migrations/v0.31-v0.32.md b/doc/migrations/v0.31-v0.32.md new file mode 100644 index 0000000000..296394a3e3 --- /dev/null +++ b/doc/migrations/v0.31-v0.32.md @@ -0,0 +1,36 @@ + +# Migrating to libp2p@32 + +A migration guide for refactoring your application code from libp2p v0.31.x to v0.32.0. + +## Table of Contents + +- [Module Updates](#module-updates) + +## Module Updates + +With this release you should update the following libp2p modules if you are relying on them: + + + +```json +"libp2p-bootstrap": "^0.13.0", +"libp2p-crypto": "^0.19.4", +"libp2p-interfaces": "^1.0.0", +"libp2p-delegated-content-routing": "^0.11.0", +"libp2p-delegated-peer-routing": "^0.10.0", +"libp2p-floodsub": "^0.27.0", +"libp2p-gossipsub": "^0.11.0", +"libp2p-kad-dht": "^0.23.0", +"libp2p-mdns": "^0.17.0", +"libp2p-noise": "^4.0.0", +"libp2p-tcp": "^0.17.0", +"libp2p-webrtc-direct": "^0.7.0", +"libp2p-webrtc-star": "^0.23.0", +"libp2p-websockets": "^0.16.0" +``` + +One of the main changes in this new release is the update to `multiaddr@10.0.0`. This should also be updated in upstream projects to avoid several multiaddr versions in the bundle and to avoid potential problems when libp2p interacts with provided outdated multiaddr instances. diff --git a/package.json b/package.json index 071f4be0c0..d5327c1b42 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,7 @@ "libp2p-bootstrap": "^0.13.0", "libp2p-delegated-content-routing": "^0.11.0", "libp2p-delegated-peer-routing": "^0.10.0", - "libp2p-floodsub": "^0.26.0", + "libp2p-floodsub": "^0.27.0", "libp2p-gossipsub": "^0.10.0", "libp2p-kad-dht": "^0.23.0", "libp2p-mdns": "^0.17.0", From d48005b8b7f219a1f5f31374eb493a646ec33eb1 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 15 Jul 2021 13:14:36 +0200 Subject: [PATCH 221/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d5327c1b42..851928a6d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.32.0-rc.0", + "version": "0.32.0", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From d6bb967243975da7c8e9c962f21b434baa33d82e Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 15 Jul 2021 13:14:37 +0200 Subject: [PATCH 222/447] chore: release version v0.32.0 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 230ef6734e..bd71623a54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# [0.32.0](https://github.com/libp2p/js-libp2p/compare/v0.32.0-rc.0...v0.32.0) (2021-07-15) + + + # [0.32.0-rc.0](https://github.com/libp2p/js-libp2p/compare/v0.31.7...v0.32.0-rc.0) (2021-07-09) From 0701de40b1ebdf319959846d8c4fdd30b3cf34a4 Mon Sep 17 00:00:00 2001 From: Robert Kiel Date: Thu, 22 Jul 2021 11:42:04 +0200 Subject: [PATCH 223/447] fix: turn compliance tests into devDependency (#960) Co-authored-by: Robert Kiel --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 851928a6d2..df45203d0c 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,6 @@ "it-take": "^1.0.0", "libp2p-crypto": "^0.19.4", "libp2p-interfaces": "^1.0.0", - "libp2p-interfaces-compliance-tests": "^1.0.0", "libp2p-utils": "^0.4.0", "mafmt": "^10.0.0", "merge-options": "^3.0.4", @@ -151,6 +150,7 @@ "libp2p-delegated-peer-routing": "^0.10.0", "libp2p-floodsub": "^0.27.0", "libp2p-gossipsub": "^0.10.0", + "libp2p-interfaces-compliance-tests": "^1.0.0", "libp2p-kad-dht": "^0.23.0", "libp2p-mdns": "^0.17.0", "libp2p-mplex": "^0.10.1", From df53ab4e655c04f50c5d00aedd7f1a7992a1af52 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 22 Jul 2021 11:51:39 +0200 Subject: [PATCH 224/447] chore: update contributors --- package.json | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index df45203d0c..abfac0734d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.32.0", + "version": "0.32.1", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", @@ -179,23 +179,25 @@ "Friedel Ziegelmayer ", "Maciej Krüger ", "Hugo Dias ", - "dirkmc ", - "Chris Dostert ", "Volker Mische ", + "Chris Dostert ", + "dirkmc ", "zeim839 <50573884+zeim839@users.noreply.github.com>", "Richard Littauer ", "Ryan Bell ", "a1300 ", "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", "Samlior ", - "Andrew Nesbitt ", - "Thomas Eizinger ", "Franck Royer ", + "Thomas Eizinger ", "Giovanni T. Parra ", "acolytec3 <17355484+acolytec3@users.noreply.github.com>", "Elven ", + "Andrew Nesbitt ", "Didrik Nordström ", "RasmusErik Voel Jensen ", + "Robert Kiel ", + "Aditya Bose <13054902+adbose@users.noreply.github.com>", "Smite Chow ", "Soeren ", "Sönke Hahn ", @@ -211,7 +213,6 @@ "robertkiel ", "shresthagrawal <34920931+shresthagrawal@users.noreply.github.com>", "swedneck <40505480+swedneck@users.noreply.github.com>", - "Marcin Tojek ", "Aleksei ", "Bernd Strehl ", "Chris Bratlien ", @@ -234,7 +235,7 @@ "Kevin Kwok ", "Kevin Lacker ", "Lars Gierth ", - "Aditya Bose <13054902+adbose@users.noreply.github.com>", + "Marcin Tojek ", "Michael Burns <5170+mburns@users.noreply.github.com>", "Miguel Mota ", "Nuno Nogueira ", From b11126ca89693e0e17755042639c3e69e792cb60 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 22 Jul 2021 11:51:39 +0200 Subject: [PATCH 225/447] chore: release version v0.32.1 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd71623a54..b546c5ef3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.32.1](https://github.com/libp2p/js-libp2p/compare/v0.32.0...v0.32.1) (2021-07-22) + + +### Bug Fixes + +* turn compliance tests into devDependency ([#960](https://github.com/libp2p/js-libp2p/issues/960)) ([0701de4](https://github.com/libp2p/js-libp2p/commit/0701de40b1ebdf319959846d8c4fdd30b3cf34a4)) + + + # [0.32.0](https://github.com/libp2p/js-libp2p/compare/v0.32.0-rc.0...v0.32.0) (2021-07-15) From a07fb7960bc0b8f1547ba00eb2aa62db401aa582 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 12 Aug 2021 14:17:50 +0200 Subject: [PATCH 226/447] chore: update action setup node --- .github/workflows/main.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ff8e94d70f..6e76c05f51 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,10 +12,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Use Node.js 14 - uses: actions/setup-node@v1 - with: - node-version: 14 + - uses: actions/setup-node@v2 + with: + node-version: 14 - run: npm install - run: npx aegir lint - uses: gozala/typescript-error-reporter-action@v1.0.8 @@ -35,7 +34,7 @@ jobs: fail-fast: true steps: - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v2 with: node-version: ${{ matrix.node }} - run: npm install From 3b33fb4b73ba8065e432fb59f758fe138fd23d9e Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 12 Aug 2021 14:29:11 +0200 Subject: [PATCH 227/447] fix: browser example ci --- examples/libp2p-in-the-browser/index.html | 2 +- examples/libp2p-in-the-browser/package.json | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/libp2p-in-the-browser/index.html b/examples/libp2p-in-the-browser/index.html index 1c8f462b5b..1b1f48da60 100644 --- a/examples/libp2p-in-the-browser/index.html +++ b/examples/libp2p-in-the-browser/index.html @@ -16,7 +16,7 @@

Starting libp2p...


     
 
-    
+    
 
   
 
diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json
index bb997142bb..3aa948d5c3 100644
--- a/examples/libp2p-in-the-browser/package.json
+++ b/examples/libp2p-in-the-browser/package.json
@@ -2,7 +2,6 @@
   "name": "libp2p-in-browser",
   "version": "1.0.0",
   "description": "A libp2p node running in the browser",
-  "main": "dist/index.html",
   "browserslist": [
     "last 2 Chrome versions"
   ],

From ef24fabf0269fd079888e92eedb458e23ef1c733 Mon Sep 17 00:00:00 2001
From: greenSnot 
Date: Fri, 13 Aug 2021 10:21:50 -0400
Subject: [PATCH 228/447] feat: custom protocol name (#962)

Co-authored-by: mzdws <8580712+mzdws@user.noreply.gitee.com>
---
 doc/CONFIGURATION.md                       | 20 +++++++++
 src/config.js                              |  1 +
 src/identify/consts.js                     | 12 ++++--
 src/identify/index.js                      | 32 +++++++++++----
 src/index.js                               |  3 +-
 src/ping/constants.js                      |  6 ++-
 src/ping/index.js                          | 11 ++---
 test/configuration/protocol-prefix.node.js | 47 ++++++++++++++++++++++
 test/identify/index.spec.js                | 31 +++++++++-----
 9 files changed, 134 insertions(+), 29 deletions(-)
 create mode 100644 test/configuration/protocol-prefix.node.js

diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md
index a1a5149ec7..135390eb1e 100644
--- a/doc/CONFIGURATION.md
+++ b/doc/CONFIGURATION.md
@@ -781,6 +781,26 @@ By default under nodejs libp2p will attempt to use [UPnP](https://en.wikipedia.o
 
 [NAT-PMP](http://miniupnp.free.fr/nat-pmp.html) is a feature of some modern routers which performs a similar job to UPnP. NAT-PMP is disabled by default, if enabled libp2p will try to use NAT-PMP and will fall back to UPnP if it fails.
 
+#### Configuring protocol name
+
+Changing the protocol name prefix can isolate default public network (IPFS) for custom purposes. 
+
+```js
+const node = await Libp2p.create({
+  config: {
+    protocolPrefix: 'ipfs' // default
+  }
+})
+/*
+protocols: [
+  "/ipfs/id/1.0.0", // identify service protocol (if we have multiplexers)
+  "/ipfs/id/push/1.0.0", // identify service push protocol (if we have multiplexers)
+  "/ipfs/ping/1.0.0", // built-in ping protocol
+]
+*/
+```
+
+
 ## Configuration examples
 
 As libp2p is designed to be a modular networking library, its usage will vary based on individual project needs. We've included links to some existing project configurations for your reference, in case you wish to replicate their configuration:
diff --git a/src/config.js b/src/config.js
index bbcc6dab47..9542c70036 100644
--- a/src/config.js
+++ b/src/config.js
@@ -57,6 +57,7 @@ const DefaultConfig = {
     }
   },
   config: {
+    protocolPrefix: 'ipfs',
     dht: {
       enabled: false,
       kBucketSize: 20,
diff --git a/src/identify/consts.js b/src/identify/consts.js
index 1f697e5dc5..7c2484aad9 100644
--- a/src/identify/consts.js
+++ b/src/identify/consts.js
@@ -3,7 +3,13 @@
 // @ts-ignore file not listed within the file list of projects
 const libp2pVersion = require('../../package.json').version
 
-module.exports.PROTOCOL_VERSION = 'ipfs/0.1.0'
+module.exports.PROTOCOL_VERSION = 'ipfs/0.1.0' // deprecated
 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'
+module.exports.MULTICODEC_IDENTIFY = '/ipfs/id/1.0.0' // deprecated
+module.exports.MULTICODEC_IDENTIFY_PUSH = '/ipfs/id/push/1.0.0' // deprecated
+
+module.exports.IDENTIFY_PROTOCOL_VERSION = '0.1.0'
+module.exports.MULTICODEC_IDENTIFY_PROTOCOL_NAME = 'id'
+module.exports.MULTICODEC_IDENTIFY_PUSH_PROTOCOL_NAME = 'id/push'
+module.exports.MULTICODEC_IDENTIFY_PROTOCOL_VERSION = '1.0.0'
+module.exports.MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION = '1.0.0'
diff --git a/src/identify/index.js b/src/identify/index.js
index 5b85bcf61d..5a3b4ff3ba 100644
--- a/src/identify/index.js
+++ b/src/identify/index.js
@@ -23,7 +23,11 @@ const PeerRecord = require('../record/peer-record')
 const {
   MULTICODEC_IDENTIFY,
   MULTICODEC_IDENTIFY_PUSH,
-  PROTOCOL_VERSION
+  IDENTIFY_PROTOCOL_VERSION,
+  MULTICODEC_IDENTIFY_PROTOCOL_NAME,
+  MULTICODEC_IDENTIFY_PUSH_PROTOCOL_NAME,
+  MULTICODEC_IDENTIFY_PROTOCOL_VERSION,
+  MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION
 } = require('./consts')
 
 const { codes } = require('../errors')
@@ -39,6 +43,16 @@ const { codes } = require('../errors')
  */
 
 class IdentifyService {
+  /**
+   * @param {import('../')} libp2p
+   */
+  static getProtocolStr (libp2p) {
+    return {
+      identifyProtocolStr: `/${libp2p._config.protocolPrefix}/${MULTICODEC_IDENTIFY_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PROTOCOL_VERSION}`,
+      identifyPushProtocolStr: `/${libp2p._config.protocolPrefix}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION}`
+    }
+  }
+
   /**
    * @class
    * @param {Object} options
@@ -53,9 +67,13 @@ class IdentifyService {
 
     this.handleMessage = this.handleMessage.bind(this)
 
+    const protocolStr = IdentifyService.getProtocolStr(libp2p)
+    this.identifyProtocolStr = protocolStr.identifyProtocolStr
+    this.identifyPushProtocolStr = protocolStr.identifyPushProtocolStr
+
     // Store self host metadata
     this._host = {
-      protocolVersion: PROTOCOL_VERSION,
+      protocolVersion: `${libp2p._config.protocolPrefix}/${IDENTIFY_PROTOCOL_VERSION}`,
       ...libp2p._options.host
     }
 
@@ -94,7 +112,7 @@ class IdentifyService {
 
     const pushes = connections.map(async connection => {
       try {
-        const { stream } = await connection.newStream(MULTICODEC_IDENTIFY_PUSH)
+        const { stream } = await connection.newStream(this.identifyPushProtocolStr)
 
         await pipe(
           [Message.Identify.encode({
@@ -129,7 +147,7 @@ class IdentifyService {
     const connections = []
     let connection
     for (const peer of this.peerStore.peers.values()) {
-      if (peer.protocols.includes(MULTICODEC_IDENTIFY_PUSH) && (connection = this.connectionManager.get(peer.id))) {
+      if (peer.protocols.includes(this.identifyPushProtocolStr) && (connection = this.connectionManager.get(peer.id))) {
         connections.push(connection)
       }
     }
@@ -147,7 +165,7 @@ class IdentifyService {
    * @returns {Promise}
    */
   async identify (connection) {
-    const { stream } = await connection.newStream(MULTICODEC_IDENTIFY)
+    const { stream } = await connection.newStream(this.identifyProtocolStr)
     const [data] = await pipe(
       [],
       stream,
@@ -224,9 +242,9 @@ class IdentifyService {
    */
   handleMessage ({ connection, stream, protocol }) {
     switch (protocol) {
-      case MULTICODEC_IDENTIFY:
+      case this.identifyProtocolStr:
         return this._handleIdentify({ connection, stream })
-      case MULTICODEC_IDENTIFY_PUSH:
+      case this.identifyPushProtocolStr:
         return this._handlePush({ connection, stream })
       default:
         log.error('cannot handle unknown protocol %s', protocol)
diff --git a/src/index.js b/src/index.js
index 999bcb2685..3bd1fbfe77 100644
--- a/src/index.js
+++ b/src/index.js
@@ -31,7 +31,6 @@ const PersistentPeerStore = require('./peer-store/persistent')
 const Registrar = require('./registrar')
 const ping = require('./ping')
 const IdentifyService = require('./identify')
-const IDENTIFY_PROTOCOLS = IdentifyService.multicodecs
 const NatManager = require('./nat-manager')
 const { updateSelfPeerRecord } = require('./record/utils')
 
@@ -289,7 +288,7 @@ class Libp2p extends EventEmitter {
 
       // Add the identify service since we can multiplex
       this.identifyService = new IdentifyService({ libp2p: this })
-      this.handle(Object.values(IDENTIFY_PROTOCOLS), this.identifyService.handleMessage)
+      this.handle(Object.values(IdentifyService.getProtocolStr(this)), this.identifyService.handleMessage)
     }
 
     // Attach private network protector
diff --git a/src/ping/constants.js b/src/ping/constants.js
index d4c3bd08b3..8ddd596d1e 100644
--- a/src/ping/constants.js
+++ b/src/ping/constants.js
@@ -1,6 +1,8 @@
 'use strict'
 
 module.exports = {
-  PROTOCOL: '/ipfs/ping/1.0.0',
-  PING_LENGTH: 32
+  PROTOCOL: '/ipfs/ping/1.0.0', // deprecated
+  PING_LENGTH: 32,
+  PROTOCOL_VERSION: '1.0.0',
+  PROTOCOL_NAME: 'ping'
 }
diff --git a/src/ping/index.js b/src/ping/index.js
index 6ad988526f..2ffb139c52 100644
--- a/src/ping/index.js
+++ b/src/ping/index.js
@@ -13,7 +13,7 @@ const { toBuffer } = require('it-buffer')
 const { collect, take } = require('streaming-iterables')
 const equals = require('uint8arrays/equals')
 
-const { PROTOCOL, PING_LENGTH } = require('./constants')
+const { PROTOCOL_NAME, PING_LENGTH, PROTOCOL_VERSION } = require('./constants')
 
 /**
  * @typedef {import('../')} Libp2p
@@ -30,11 +30,12 @@ const { PROTOCOL, PING_LENGTH } = require('./constants')
  * @returns {Promise}
  */
 async function ping (node, peer) {
+  const protocol = `/${node._config.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}`
   // @ts-ignore multiaddr might not have toB58String
-  log('dialing %s to %s', PROTOCOL, peer.toB58String ? peer.toB58String() : peer)
+  log('dialing %s to %s', protocol, peer.toB58String ? peer.toB58String() : peer)
 
   const connection = await node.dial(peer)
-  const { stream } = await connection.newStream(PROTOCOL)
+  const { stream } = await connection.newStream(protocol)
 
   const start = Date.now()
   const data = crypto.randomBytes(PING_LENGTH)
@@ -61,7 +62,7 @@ async function ping (node, peer) {
  * @param {Libp2p} node
  */
 function mount (node) {
-  node.handle(PROTOCOL, ({ stream }) => pipe(stream, stream))
+  node.handle(`/${node._config.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}`, ({ stream }) => pipe(stream, stream))
 }
 
 /**
@@ -70,7 +71,7 @@ function mount (node) {
  * @param {Libp2p} node
  */
 function unmount (node) {
-  node.unhandle(PROTOCOL)
+  node.unhandle(`/${node._config.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}`)
 }
 
 exports = module.exports = ping
diff --git a/test/configuration/protocol-prefix.node.js b/test/configuration/protocol-prefix.node.js
new file mode 100644
index 0000000000..6a718e6579
--- /dev/null
+++ b/test/configuration/protocol-prefix.node.js
@@ -0,0 +1,47 @@
+'use strict'
+/* eslint-env mocha */
+
+const { expect } = require('aegir/utils/chai')
+const mergeOptions = require('merge-options')
+
+const { create } = require('../../src')
+const { baseOptions } = require('./utils')
+
+describe('Protocol prefix is configurable', () => {
+  let libp2p
+
+  it('protocolPrefix is provided', async () => {
+    const testProtocol = 'test-protocol'
+    libp2p = await create(mergeOptions(baseOptions, {
+      config: {
+        protocolPrefix: testProtocol
+      }
+    }))
+
+    const protocols = libp2p.peerStore.protoBook.get(libp2p.peerId);
+    [
+      '/libp2p/circuit/relay/0.1.0',
+      `/${testProtocol}/id/1.0.0`,
+      `/${testProtocol}/id/push/1.0.0`,
+      `/${testProtocol}/ping/1.0.0`
+    ].forEach((i, idx) => {
+      expect(protocols[idx]).equals(i)
+    })
+    await libp2p.stop()
+  })
+
+  it('protocolPrefix is not provided', async () => {
+    libp2p = await create(baseOptions)
+
+    const protocols = libp2p.peerStore.protoBook.get(libp2p.peerId);
+    [
+      '/libp2p/circuit/relay/0.1.0',
+      '/ipfs/id/1.0.0',
+      '/ipfs/id/push/1.0.0',
+      '/ipfs/ping/1.0.0'
+    ].forEach((i, idx) => {
+      expect(protocols[idx]).equals(i)
+    })
+    await libp2p.stop()
+  })
+})
diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js
index dbf2d73605..2b7ccc6bf7 100644
--- a/test/identify/index.spec.js
+++ b/test/identify/index.spec.js
@@ -60,7 +60,8 @@ describe('Identify', () => {
         peerStore: localPeerStore,
         multiaddrs: listenMaddrs,
         isStarted: () => true,
-        _options: { host: {} }
+        _options: { host: {} },
+        _config: { protocolPrefix: 'ipfs' }
       }
     })
     const remoteIdentify = new IdentifyService({
@@ -70,7 +71,8 @@ describe('Identify', () => {
         peerStore: remotePeerStore,
         multiaddrs: listenMaddrs,
         isStarted: () => true,
-        _options: { host: {} }
+        _options: { host: {} },
+        _config: { protocolPrefix: 'ipfs' }
       }
     })
 
@@ -119,7 +121,8 @@ describe('Identify', () => {
         peerStore: localPeerStore,
         multiaddrs: listenMaddrs,
         isStarted: () => true,
-        _options: { host: { agentVersion } }
+        _options: { host: { agentVersion } },
+        _config: { protocolPrefix: 'ipfs' }
       }
     })
 
@@ -131,7 +134,8 @@ describe('Identify', () => {
         peerStore: remotePeerStore,
         multiaddrs: listenMaddrs,
         isStarted: () => true,
-        _options: { host: { agentVersion } }
+        _options: { host: { agentVersion } },
+        _config: { protocolPrefix: 'ipfs' }
       }
     })
 
@@ -180,7 +184,8 @@ describe('Identify', () => {
         connectionManager: new EventEmitter(),
         peerStore: localPeerStore,
         multiaddrs: [],
-        _options: { host: {} }
+        _options: { host: {} },
+        _config: { protocolPrefix: 'ipfs' }
       }
     })
     const remoteIdentify = new IdentifyService({
@@ -189,7 +194,8 @@ describe('Identify', () => {
         connectionManager: new EventEmitter(),
         peerStore: remotePeerStore,
         multiaddrs: [],
-        _options: { host: {} }
+        _options: { host: {} },
+        _config: { protocolPrefix: 'ipfs' }
       }
     })
 
@@ -231,7 +237,8 @@ describe('Identify', () => {
           host: {
             agentVersion
           }
-        }
+        },
+        _config: { protocolPrefix: 'ipfs' }
       },
       protocols
     })
@@ -261,7 +268,8 @@ describe('Identify', () => {
           peerStore: localPeerStore,
           multiaddrs: listenMaddrs,
           isStarted: () => true,
-          _options: { host: {} }
+          _options: { host: {} },
+          _config: { protocolPrefix: 'ipfs' }
         }
       })
 
@@ -275,7 +283,8 @@ describe('Identify', () => {
           peerStore: remotePeerStore,
           multiaddrs: [],
           isStarted: () => true,
-          _options: { host: {} }
+          _options: { host: {} },
+          _config: { protocolPrefix: 'ipfs' }
         }
       })
 
@@ -333,7 +342,8 @@ describe('Identify', () => {
           peerStore: localPeerStore,
           multiaddrs: listenMaddrs,
           isStarted: () => true,
-          _options: { host: {} }
+          _options: { host: {} },
+          _config: { protocolPrefix: 'ipfs' }
         }
       })
 
@@ -347,6 +357,7 @@ describe('Identify', () => {
           peerStore: new PeerStore({ peerId: remotePeer }),
           multiaddrs: [],
           _options: { host: {} },
+          _config: { protocolPrefix: 'ipfs' },
           isStarted: () => true
         }
       })

From 833f789714e20c2c5a05e3b8c288b92037dc88d8 Mon Sep 17 00:00:00 2001
From: Vasco Santos 
Date: Fri, 13 Aug 2021 17:02:51 +0200
Subject: [PATCH 229/447] chore: update contributors

---
 package.json | 75 ++++++++++++++++++++++++++--------------------------
 1 file changed, 38 insertions(+), 37 deletions(-)

diff --git a/package.json b/package.json
index abfac0734d..27f3ea3fc7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "libp2p",
-  "version": "0.32.1",
+  "version": "0.32.2",
   "description": "JavaScript implementation of libp2p, a modular peer to peer network stack",
   "leadMaintainer": "Jacob Heun ",
   "main": "src/index.js",
@@ -182,63 +182,64 @@
     "Volker Mische ",
     "Chris Dostert ",
     "dirkmc ",
-    "zeim839 <50573884+zeim839@users.noreply.github.com>",
     "Richard Littauer ",
+    "zeim839 <50573884+zeim839@users.noreply.github.com>",
     "Ryan Bell ",
     "a1300 ",
     "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ",
-    "Samlior ",
+    "Andrew Nesbitt ",
+    "Elven ",
     "Franck Royer ",
-    "Thomas Eizinger ",
     "Giovanni T. Parra ",
+    "Samlior ",
+    "Thomas Eizinger ",
     "acolytec3 <17355484+acolytec3@users.noreply.github.com>",
-    "Elven ",
-    "Andrew Nesbitt ",
-    "Didrik Nordström ",
+    "Didrik Nordström ",
+    "Irakli Gozalishvili ",
+    "Joel Gustafson ",
+    "John Rees ",
+    "João Santos ",
+    "Julien Bouquillon ",
+    "Kevin Kwok ",
+    "Kevin Lacker ",
+    "Lars Gierth ",
+    "Ethan Lam ",
+    "Marcin Tojek ",
+    "Michael Burns <5170+mburns@users.noreply.github.com>",
+    "Miguel Mota ",
+    "Nuno Nogueira ",
+    "Dmitriy Ryajov ",
+    "Philipp Muens ",
     "RasmusErik Voel Jensen ",
+    "Diogo Silva ",
     "Robert Kiel ",
-    "Aditya Bose <13054902+adbose@users.noreply.github.com>",
+    "phillmac ",
+    "robertkiel ",
     "Smite Chow ",
     "Soeren ",
     "Sönke Hahn ",
     "TJKoury ",
+    "shresthagrawal <34920931+shresthagrawal@users.noreply.github.com>",
     "Tiago Alves ",
+    "Daijiro Wachi ",
     "Yusef Napora ",
     "Zane Starr ",
-    "ebinks ",
-    "isan_rivkin ",
-    "mayerwin ",
-    "mcclure ",
-    "phillmac ",
-    "robertkiel ",
-    "shresthagrawal <34920931+shresthagrawal@users.noreply.github.com>",
     "swedneck <40505480+swedneck@users.noreply.github.com>",
     "Aleksei ",
-    "Bernd Strehl ",
-    "Chris Bratlien ",
     "Cindy Wu ",
-    "Daijiro Wachi ",
-    "Diogo Silva ",
-    "Dmitriy Ryajov ",
-    "Ethan Lam ",
-    "Fei Liu ",
-    "Felipe Martins ",
-    "Florian-Merle ",
+    "Aditya Bose <13054902+adbose@users.noreply.github.com>",
+    "Chris Bratlien ",
+    "ebinks ",
+    "greenSnot ",
+    "Bernd Strehl ",
     "Francis Gulotta ",
+    "isan_rivkin ",
+    "Florian-Merle ",
+    "mayerwin ",
     "Guy Sviry <32539816+guysv@users.noreply.github.com>",
     "Henrique Dias ",
-    "Irakli Gozalishvili ",
-    "Joel Gustafson ",
-    "John Rees ",
-    "João Santos ",
-    "Julien Bouquillon ",
-    "Kevin Kwok ",
-    "Kevin Lacker ",
-    "Lars Gierth ",
-    "Marcin Tojek ",
-    "Michael Burns <5170+mburns@users.noreply.github.com>",
-    "Miguel Mota ",
-    "Nuno Nogueira ",
-    "Philipp Muens "
+    "mcclure ",
+    "Felipe Martins ",
+    "Fei Liu "
   ]
 }

From 06c3a6d407222116acfbb403901a50e149543ad7 Mon Sep 17 00:00:00 2001
From: Vasco Santos 
Date: Fri, 13 Aug 2021 17:02:52 +0200
Subject: [PATCH 230/447] chore: release version v0.32.2

---
 CHANGELOG.md | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index b546c5ef3b..bd22c00860 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,17 @@
+## [0.32.2](https://github.com/libp2p/js-libp2p/compare/v0.32.1...v0.32.2) (2021-08-13)
+
+
+### Bug Fixes
+
+* browser example ci ([3b33fb4](https://github.com/libp2p/js-libp2p/commit/3b33fb4b73ba8065e432fb59f758fe138fd23d9e))
+
+
+### Features
+
+* custom protocol name ([#962](https://github.com/libp2p/js-libp2p/issues/962)) ([ef24fab](https://github.com/libp2p/js-libp2p/commit/ef24fabf0269fd079888e92eedb458e23ef1c733))
+
+
+
 ## [0.32.1](https://github.com/libp2p/js-libp2p/compare/v0.32.0...v0.32.1) (2021-07-22)
 
 

From ba2b4d4b28f1d9940b457de344aed44537f9eabd Mon Sep 17 00:00:00 2001
From: Vasco Santos 
Date: Mon, 16 Aug 2021 17:54:24 +0200
Subject: [PATCH 231/447] fix: uint8arrays is a dep (#964)

---
 .github/workflows/main.yml | 2 +-
 package.json               | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 6e76c05f51..83f8a68892 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -20,7 +20,7 @@ jobs:
     - uses: gozala/typescript-error-reporter-action@v1.0.8
     - run: npx aegir build
     - run: npx aegir dep-check
-    - uses: ipfs/aegir/actions/bundle-size@master
+    - uses: ipfs/aegir/actions/bundle-size@v32.1.0
       name: size
       with:
         github_token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/package.json b/package.json
index 27f3ea3fc7..426d1741d1 100644
--- a/package.json
+++ b/package.json
@@ -124,6 +124,7 @@
     "set-delayed-interval": "^1.0.0",
     "streaming-iterables": "^6.0.0",
     "timeout-abort-controller": "^1.1.1",
+    "uint8arrays": "^2.1.3",
     "varint": "^6.0.0",
     "wherearewe": "^1.0.0",
     "xsalsa20": "^1.1.0"
@@ -165,7 +166,6 @@
     "p-wait-for": "^3.2.0",
     "rimraf": "^3.0.2",
     "sinon": "^11.1.1",
-    "uint8arrays": "^2.1.3",
     "util": "^0.12.3"
   },
   "contributors": [

From 7783edb059a488c56b277656114220f3312f950e Mon Sep 17 00:00:00 2001
From: Vasco Santos 
Date: Mon, 16 Aug 2021 18:20:40 +0200
Subject: [PATCH 232/447] chore: update contributors

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 426d1741d1..2e19b777b7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "libp2p",
-  "version": "0.32.2",
+  "version": "0.32.3",
   "description": "JavaScript implementation of libp2p, a modular peer to peer network stack",
   "leadMaintainer": "Jacob Heun ",
   "main": "src/index.js",

From 266f2c3c8683f1d1d1c6673c94cb4e14ed868da2 Mon Sep 17 00:00:00 2001
From: Vasco Santos 
Date: Mon, 16 Aug 2021 18:20:41 +0200
Subject: [PATCH 233/447] chore: release version v0.32.3

---
 CHANGELOG.md | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index bd22c00860..781a0360ee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,12 @@
+## [0.32.3](https://github.com/libp2p/js-libp2p/compare/v0.32.2...v0.32.3) (2021-08-16)
+
+
+### Bug Fixes
+
+* uint8arrays is a dep ([#964](https://github.com/libp2p/js-libp2p/issues/964)) ([ba2b4d4](https://github.com/libp2p/js-libp2p/commit/ba2b4d4b28f1d9940b457de344aed44537f9eabd))
+
+
+
 ## [0.32.2](https://github.com/libp2p/js-libp2p/compare/v0.32.1...v0.32.2) (2021-08-13)
 
 

From 9f0582f372be9c61460bad591fdb400982716be9 Mon Sep 17 00:00:00 2001
From: Vasco Santos 
Date: Thu, 19 Aug 2021 09:38:39 +0200
Subject: [PATCH 234/447] chore: remove unused dev dep (#966)

---
 package.json | 1 -
 1 file changed, 1 deletion(-)

diff --git a/package.json b/package.json
index 2e19b777b7..950d790a2d 100644
--- a/package.json
+++ b/package.json
@@ -159,7 +159,6 @@
     "libp2p-tcp": "^0.17.0",
     "libp2p-webrtc-star": "^0.23.0",
     "libp2p-websockets": "^0.16.0",
-    "multihashes": "^4.0.2",
     "nock": "^13.0.3",
     "p-defer": "^3.0.0",
     "p-times": "^3.0.0",

From 0f389a7828e7ecdbedc43cfe2d5b397407e27327 Mon Sep 17 00:00:00 2001
From: Vasco Santos 
Date: Mon, 16 Aug 2021 21:29:06 +0200
Subject: [PATCH 235/447] chore: update uint8arrays

---
 examples/auto-relay/test.js                   | 2 +-
 examples/chat/test.js                         | 2 +-
 examples/connection-encryption/test.js        | 2 +-
 examples/discovery-mechanisms/test-1.js       | 2 +-
 examples/discovery-mechanisms/test-2.js       | 2 +-
 examples/discovery-mechanisms/test-3.js       | 2 +-
 examples/echo/test.js                         | 2 +-
 examples/package.json                         | 6 ++++--
 examples/peer-and-content-routing/test-1.js   | 2 +-
 examples/peer-and-content-routing/test-2.js   | 2 +-
 examples/pnet/test.js                         | 2 +-
 examples/protocol-and-stream-muxing/test-1.js | 2 +-
 examples/protocol-and-stream-muxing/test-2.js | 2 +-
 examples/protocol-and-stream-muxing/test-3.js | 2 +-
 examples/pubsub/1.js                          | 4 ++--
 examples/pubsub/message-filtering/1.js        | 4 ++--
 examples/pubsub/message-filtering/test.js     | 2 +-
 examples/pubsub/test-1.js                     | 2 +-
 examples/transports/test-1.js                 | 2 +-
 examples/transports/test-2.js                 | 2 +-
 examples/transports/test-3.js                 | 2 +-
 examples/transports/test-4.js                 | 2 +-
 examples/webrtc-direct/test.js                | 2 +-
 package.json                                  | 2 +-
 src/circuit/auto-relay.js                     | 4 ++--
 src/identify/index.js                         | 2 +-
 src/keychain/cms.js                           | 4 ++--
 src/keychain/index.js                         | 4 ++--
 src/peer-store/metadata-book.js               | 2 +-
 src/ping/index.js                             | 2 +-
 src/pnet/crypto.js                            | 4 ++--
 src/pnet/key-generator.js                     | 4 ++--
 src/record/README.md                          | 2 +-
 src/record/envelope/index.js                  | 6 +++---
 test/content-routing/dht/operation.node.js    | 2 +-
 test/dialing/direct.node.js                   | 2 +-
 test/identify/index.spec.js                   | 2 +-
 test/keychain/cms-interop.spec.js             | 4 ++--
 test/keychain/keychain.spec.js                | 4 ++--
 test/keychain/peerid.spec.js                  | 2 +-
 test/peer-discovery/index.node.js             | 2 +-
 test/peer-store/metadata-book.spec.js         | 2 +-
 test/peer-store/peer-store.spec.js            | 2 +-
 test/peer-store/persisted-peer-store.spec.js  | 2 +-
 test/pnet/index.spec.js                       | 2 +-
 test/record/envelope.spec.js                  | 4 ++--
 test/relay/relay.node.js                      | 2 +-
 test/upgrading/upgrader.spec.js               | 2 +-
 48 files changed, 63 insertions(+), 61 deletions(-)

diff --git a/examples/auto-relay/test.js b/examples/auto-relay/test.js
index 0eaf8cc3ae..ac5945ab4e 100644
--- a/examples/auto-relay/test.js
+++ b/examples/auto-relay/test.js
@@ -3,7 +3,7 @@
 const path = require('path')
 const execa = require('execa')
 const pDefer = require('p-defer')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 function startProcess (name, args = []) {
   return execa('node', [path.join(__dirname, name), ...args], {
diff --git a/examples/chat/test.js b/examples/chat/test.js
index 63e67f0c4a..0d9b40d489 100644
--- a/examples/chat/test.js
+++ b/examples/chat/test.js
@@ -3,7 +3,7 @@
 const path = require('path')
 const execa = require('execa')
 const pDefer = require('p-defer')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 function startProcess(name) {
   return execa('node', [path.join(__dirname, name)], {
diff --git a/examples/connection-encryption/test.js b/examples/connection-encryption/test.js
index 9be0ab219c..4574dd9acf 100644
--- a/examples/connection-encryption/test.js
+++ b/examples/connection-encryption/test.js
@@ -3,7 +3,7 @@
 const path = require('path')
 const execa = require('execa')
 const pDefer = require('p-defer')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 async function test () {
   const messageReceived = pDefer()
diff --git a/examples/discovery-mechanisms/test-1.js b/examples/discovery-mechanisms/test-1.js
index d187953ca5..0f76fa2f1b 100644
--- a/examples/discovery-mechanisms/test-1.js
+++ b/examples/discovery-mechanisms/test-1.js
@@ -3,7 +3,7 @@
 const path = require('path')
 const execa = require('execa')
 const pWaitFor = require('p-wait-for')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 const bootstrapers = require('./bootstrapers')
 
 const discoveredCopy = 'Discovered:'
diff --git a/examples/discovery-mechanisms/test-2.js b/examples/discovery-mechanisms/test-2.js
index 0aa5c84857..86183c81d5 100644
--- a/examples/discovery-mechanisms/test-2.js
+++ b/examples/discovery-mechanisms/test-2.js
@@ -3,7 +3,7 @@
 const path = require('path')
 const execa = require('execa')
 const pWaitFor = require('p-wait-for')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 const discoveredCopy = 'Discovered:'
 
diff --git a/examples/discovery-mechanisms/test-3.js b/examples/discovery-mechanisms/test-3.js
index cbb4b74a60..f73744cd7e 100644
--- a/examples/discovery-mechanisms/test-3.js
+++ b/examples/discovery-mechanisms/test-3.js
@@ -3,7 +3,7 @@
 const path = require('path')
 const execa = require('execa')
 const pWaitFor = require('p-wait-for')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 const discoveredCopy = 'discovered:'
 
diff --git a/examples/echo/test.js b/examples/echo/test.js
index 168f00445a..579d609131 100644
--- a/examples/echo/test.js
+++ b/examples/echo/test.js
@@ -3,7 +3,7 @@
 const path = require('path')
 const execa = require('execa')
 const pDefer = require('p-defer')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 function startProcess(name) {
   return execa('node', [path.join(__dirname, name)], {
diff --git a/examples/package.json b/examples/package.json
index a4adc2ad42..b69c9ed6e8 100644
--- a/examples/package.json
+++ b/examples/package.json
@@ -10,10 +10,12 @@
   "dependencies": {
     "execa": "^2.1.0",
     "fs-extra": "^8.1.0",
+    "libp2p": "../src",
     "libp2p-pubsub-peer-discovery": "^4.0.0",
-    "libp2p-relay-server": "^0.2.0",
-    "libp2p-gossipsub": "^0.9.1",
+    "libp2p-relay-server": "^0.3.0",
+    "libp2p-gossipsub": "^0.11.0",
     "p-defer": "^3.0.0",
+    "uint8arrays": "^3.0.0",
     "which": "^2.0.1"
   },
   "devDependencies": {
diff --git a/examples/peer-and-content-routing/test-1.js b/examples/peer-and-content-routing/test-1.js
index 72f3ba70f9..2a19b10dbc 100644
--- a/examples/peer-and-content-routing/test-1.js
+++ b/examples/peer-and-content-routing/test-1.js
@@ -3,7 +3,7 @@
 const path = require('path')
 const execa = require('execa')
 const pWaitFor = require('p-wait-for')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 async function test() {
   process.stdout.write('1.js\n')
diff --git a/examples/peer-and-content-routing/test-2.js b/examples/peer-and-content-routing/test-2.js
index 546c3cddc6..82dbcf925f 100644
--- a/examples/peer-and-content-routing/test-2.js
+++ b/examples/peer-and-content-routing/test-2.js
@@ -3,7 +3,7 @@
 const path = require('path')
 const execa = require('execa')
 const pDefer = require('p-defer')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 const providedCopy = 'is providing'
 const foundCopy = 'Found provider:'
diff --git a/examples/pnet/test.js b/examples/pnet/test.js
index 129c883ad7..8a295dbb8e 100644
--- a/examples/pnet/test.js
+++ b/examples/pnet/test.js
@@ -3,7 +3,7 @@
 const path = require('path')
 const execa = require('execa')
 const pDefer = require('p-defer')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 async function test () {
   const messageReceived = pDefer()
diff --git a/examples/protocol-and-stream-muxing/test-1.js b/examples/protocol-and-stream-muxing/test-1.js
index 35314ab9c6..c851b879e8 100644
--- a/examples/protocol-and-stream-muxing/test-1.js
+++ b/examples/protocol-and-stream-muxing/test-1.js
@@ -3,7 +3,7 @@
 const path = require('path')
 const execa = require('execa')
 const pDefer = require('p-defer')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 async function test() {
   const messageDefer = pDefer()
diff --git a/examples/protocol-and-stream-muxing/test-2.js b/examples/protocol-and-stream-muxing/test-2.js
index e8d934cb71..3f04c574f2 100644
--- a/examples/protocol-and-stream-muxing/test-2.js
+++ b/examples/protocol-and-stream-muxing/test-2.js
@@ -3,7 +3,7 @@
 const path = require('path')
 const execa = require('execa')
 const pWaitFor = require('p-wait-for')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 const messages = [
   'protocol (a)',
diff --git a/examples/protocol-and-stream-muxing/test-3.js b/examples/protocol-and-stream-muxing/test-3.js
index e6dff441f2..223e1d124b 100644
--- a/examples/protocol-and-stream-muxing/test-3.js
+++ b/examples/protocol-and-stream-muxing/test-3.js
@@ -3,7 +3,7 @@
 const path = require('path')
 const execa = require('execa')
 const pWaitFor = require('p-wait-for')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 const messages = [
   'from 1 to 2',
diff --git a/examples/pubsub/1.js b/examples/pubsub/1.js
index 419838960b..6d9b0fa7c7 100644
--- a/examples/pubsub/1.js
+++ b/examples/pubsub/1.js
@@ -6,8 +6,8 @@ const TCP = require('libp2p-tcp')
 const Mplex = require('libp2p-mplex')
 const { NOISE } = require('libp2p-noise')
 const Gossipsub = require('libp2p-gossipsub')
-const uint8ArrayFromString = require('uint8arrays/from-string')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 const createNode = async () => {
   const node = await Libp2p.create({
diff --git a/examples/pubsub/message-filtering/1.js b/examples/pubsub/message-filtering/1.js
index 4d8a2c1803..dc1fe5811e 100644
--- a/examples/pubsub/message-filtering/1.js
+++ b/examples/pubsub/message-filtering/1.js
@@ -6,8 +6,8 @@ const TCP = require('libp2p-tcp')
 const Mplex = require('libp2p-mplex')
 const { NOISE } = require('libp2p-noise')
 const Gossipsub = require('libp2p-gossipsub')
-const uint8ArrayFromString = require('uint8arrays/from-string')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 const createNode = async () => {
   const node = await Libp2p.create({
diff --git a/examples/pubsub/message-filtering/test.js b/examples/pubsub/message-filtering/test.js
index 229e226909..fddcc5c840 100644
--- a/examples/pubsub/message-filtering/test.js
+++ b/examples/pubsub/message-filtering/test.js
@@ -3,7 +3,7 @@
 const path = require('path')
 const execa = require('execa')
 const pDefer = require('p-defer')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 const stdout = [
   {
diff --git a/examples/pubsub/test-1.js b/examples/pubsub/test-1.js
index 708b203906..ea968897e5 100644
--- a/examples/pubsub/test-1.js
+++ b/examples/pubsub/test-1.js
@@ -3,7 +3,7 @@
 const path = require('path')
 const execa = require('execa')
 const pDefer = require('p-defer')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 async function test () {
   const defer = pDefer()
diff --git a/examples/transports/test-1.js b/examples/transports/test-1.js
index ce4a37a2f2..63e320329d 100644
--- a/examples/transports/test-1.js
+++ b/examples/transports/test-1.js
@@ -3,7 +3,7 @@
 const path = require('path')
 const execa = require('execa')
 const pDefer = require('p-defer')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 async function test () {
   const deferStarted = pDefer()
diff --git a/examples/transports/test-2.js b/examples/transports/test-2.js
index fcef26e590..48c37fff70 100644
--- a/examples/transports/test-2.js
+++ b/examples/transports/test-2.js
@@ -3,7 +3,7 @@
 const path = require('path')
 const execa = require('execa')
 const pDefer = require('p-defer')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 async function test () {
   const defer = pDefer()
diff --git a/examples/transports/test-3.js b/examples/transports/test-3.js
index d52fb951e5..cf75b44e45 100644
--- a/examples/transports/test-3.js
+++ b/examples/transports/test-3.js
@@ -3,7 +3,7 @@
 const path = require('path')
 const execa = require('execa')
 const pDefer = require('p-defer')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 async function test () {
   const deferNode1 = pDefer()
diff --git a/examples/transports/test-4.js b/examples/transports/test-4.js
index 8ee6d8e3c7..7b2b6e5d79 100644
--- a/examples/transports/test-4.js
+++ b/examples/transports/test-4.js
@@ -3,7 +3,7 @@
 const path = require('path')
 const execa = require('execa')
 const pDefer = require('p-defer')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 async function test () {
   const deferNode1 = pDefer()
diff --git a/examples/webrtc-direct/test.js b/examples/webrtc-direct/test.js
index a6a976a2df..6f990073aa 100644
--- a/examples/webrtc-direct/test.js
+++ b/examples/webrtc-direct/test.js
@@ -3,7 +3,7 @@
 const path = require('path')
 const execa = require('execa')
 const pDefer = require('p-defer')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 const { chromium } = require('playwright');
 
 function startNode (name, args = []) {
diff --git a/package.json b/package.json
index 950d790a2d..b0d646620f 100644
--- a/package.json
+++ b/package.json
@@ -124,7 +124,7 @@
     "set-delayed-interval": "^1.0.0",
     "streaming-iterables": "^6.0.0",
     "timeout-abort-controller": "^1.1.1",
-    "uint8arrays": "^2.1.3",
+    "uint8arrays": "^3.0.0",
     "varint": "^6.0.0",
     "wherearewe": "^1.0.0",
     "xsalsa20": "^1.1.0"
diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js
index 69fc0f76ff..c27bfc5631 100644
--- a/src/circuit/auto-relay.js
+++ b/src/circuit/auto-relay.js
@@ -5,8 +5,8 @@ const log = Object.assign(debug('libp2p:auto-relay'), {
   error: debug('libp2p:auto-relay:err')
 })
 
-const uint8ArrayFromString = require('uint8arrays/from-string')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 const { Multiaddr } = require('multiaddr')
 const PeerId = require('peer-id')
 
diff --git a/src/identify/index.js b/src/identify/index.js
index 5a3b4ff3ba..b198b45041 100644
--- a/src/identify/index.js
+++ b/src/identify/index.js
@@ -8,7 +8,7 @@ const errCode = require('err-code')
 const lp = require('it-length-prefixed')
 const { pipe } = require('it-pipe')
 const { collect, take, consume } = require('streaming-iterables')
-const uint8ArrayFromString = require('uint8arrays/from-string')
+const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
 
 const PeerId = require('peer-id')
 const { Multiaddr } = require('multiaddr')
diff --git a/src/keychain/cms.js b/src/keychain/cms.js
index 92a1932a09..286e75a4ae 100644
--- a/src/keychain/cms.js
+++ b/src/keychain/cms.js
@@ -8,8 +8,8 @@ require('node-forge/lib/pbe')
 const forge = require('node-forge/lib/forge')
 const { certificateForKey, findAsync } = require('./util')
 const errcode = require('err-code')
-const uint8ArrayFromString = require('uint8arrays/from-string')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 const privates = new WeakMap()
 
diff --git a/src/keychain/index.js b/src/keychain/index.js
index 97d1a0d682..4bf63de382 100644
--- a/src/keychain/index.js
+++ b/src/keychain/index.js
@@ -10,8 +10,8 @@ const crypto = require('libp2p-crypto')
 const { Key } = require('interface-datastore')
 const CMS = require('./cms')
 const errcode = require('err-code')
-const uint8ArrayToString = require('uint8arrays/to-string')
-const uint8ArrayFromString = require('uint8arrays/from-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
+const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
 
 // @ts-ignore node-forge sha512 types not exported
 require('node-forge/lib/sha512')
diff --git a/src/peer-store/metadata-book.js b/src/peer-store/metadata-book.js
index 50b227da91..c2769a0aec 100644
--- a/src/peer-store/metadata-book.js
+++ b/src/peer-store/metadata-book.js
@@ -5,7 +5,7 @@ const log = Object.assign(debug('libp2p:peer-store:proto-book'), {
   error: debug('libp2p:peer-store:proto-book:err')
 })
 const errcode = require('err-code')
-const uint8ArrayEquals = require('uint8arrays/equals')
+const { equals: uint8ArrayEquals } = require('uint8arrays/equals')
 
 const PeerId = require('peer-id')
 
diff --git a/src/ping/index.js b/src/ping/index.js
index 2ffb139c52..758c0ccabd 100644
--- a/src/ping/index.js
+++ b/src/ping/index.js
@@ -11,7 +11,7 @@ const { pipe } = require('it-pipe')
 // @ts-ignore it-buffer has no types exported
 const { toBuffer } = require('it-buffer')
 const { collect, take } = require('streaming-iterables')
-const equals = require('uint8arrays/equals')
+const { equals } = require('uint8arrays/equals')
 
 const { PROTOCOL_NAME, PING_LENGTH, PROTOCOL_VERSION } = require('./constants')
 
diff --git a/src/pnet/crypto.js b/src/pnet/crypto.js
index da1f829710..6ef946b6b3 100644
--- a/src/pnet/crypto.js
+++ b/src/pnet/crypto.js
@@ -10,8 +10,8 @@ const Errors = require('./errors')
 // @ts-ignore xsalsa20 has no types exported
 const xsalsa20 = require('xsalsa20')
 const KEY_LENGTH = require('./key-generator').KEY_LENGTH
-const uint8ArrayFromString = require('uint8arrays/from-string')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 /**
  * Creates a stream iterable to encrypt messages in a private network
diff --git a/src/pnet/key-generator.js b/src/pnet/key-generator.js
index e973f7787c..4bd3bab902 100644
--- a/src/pnet/key-generator.js
+++ b/src/pnet/key-generator.js
@@ -2,8 +2,8 @@
 
 const crypto = require('libp2p-crypto')
 const KEY_LENGTH = 32
-const uint8ArrayToString = require('uint8arrays/to-string')
-const uint8ArrayFromString = require('uint8arrays/from-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
+const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
 
 /**
  * Generates a PSK that can be used in a libp2p-pnet private network
diff --git a/src/record/README.md b/src/record/README.md
index 47a5600bd9..761cd23477 100644
--- a/src/record/README.md
+++ b/src/record/README.md
@@ -17,7 +17,7 @@ You can read further about the envelope in [libp2p/specs#217](https://github.com
 ```js
 // interface-record implementation example with the "libp2p-example" namespace
 const Record = require('libp2p-interfaces/src/record')
-const fromString = require('uint8arrays/from-string')
+const { fromString } = require('uint8arrays/from-string')
 
 class ExampleRecord extends Record {
   constructor () {
diff --git a/src/record/envelope/index.js b/src/record/envelope/index.js
index 02be96e06f..4fadb4f0ba 100644
--- a/src/record/envelope/index.js
+++ b/src/record/envelope/index.js
@@ -1,13 +1,13 @@
 'use strict'
 
 const errCode = require('err-code')
-const uint8arraysConcat = require('uint8arrays/concat')
-const uint8arraysFromString = require('uint8arrays/from-string')
+const { concat: uint8arraysConcat } = require('uint8arrays/concat')
+const { fromString: uint8arraysFromString } = require('uint8arrays/from-string')
 // @ts-ignore libp2p-crypto does not support types
 const cryptoKeys = require('libp2p-crypto/src/keys')
 const PeerId = require('peer-id')
 const varint = require('varint')
-const uint8arraysEquals = require('uint8arrays/equals')
+const { equals: uint8arraysEquals } = require('uint8arrays/equals')
 
 const { codes } = require('../../errors')
 const { Envelope: Protobuf } = require('./envelope')
diff --git a/test/content-routing/dht/operation.node.js b/test/content-routing/dht/operation.node.js
index 946d92748f..c1cfbb13f4 100644
--- a/test/content-routing/dht/operation.node.js
+++ b/test/content-routing/dht/operation.node.js
@@ -6,7 +6,7 @@ const { expect } = require('aegir/utils/chai')
 const { Multiaddr } = require('multiaddr')
 const pWaitFor = require('p-wait-for')
 const mergeOptions = require('merge-options')
-const uint8ArrayFromString = require('uint8arrays/from-string')
+const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
 
 const { create } = require('../../../src')
 const { subsystemOptions, subsystemMulticodecs } = require('./utils')
diff --git a/test/dialing/direct.node.js b/test/dialing/direct.node.js
index 7750a35773..74b3989c6f 100644
--- a/test/dialing/direct.node.js
+++ b/test/dialing/direct.node.js
@@ -17,7 +17,7 @@ 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')
-const uint8ArrayFromString = require('uint8arrays/from-string')
+const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
 
 const Libp2p = require('../../src')
 const Dialer = require('../../src/dialer')
diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js
index 2b7ccc6bf7..a0c9ba631e 100644
--- a/test/identify/index.spec.js
+++ b/test/identify/index.spec.js
@@ -9,7 +9,7 @@ const PeerId = require('peer-id')
 const duplexPair = require('it-pair/duplex')
 const { Multiaddr } = require('multiaddr')
 const pWaitFor = require('p-wait-for')
-const unit8ArrayToString = require('uint8arrays/to-string')
+const { toString: unit8ArrayToString } = require('uint8arrays/to-string')
 
 const { codes: Errors } = require('../../src/errors')
 const IdentifyService = require('../../src/identify')
diff --git a/test/keychain/cms-interop.spec.js b/test/keychain/cms-interop.spec.js
index c21b6305e0..79930b08a1 100644
--- a/test/keychain/cms-interop.spec.js
+++ b/test/keychain/cms-interop.spec.js
@@ -3,8 +3,8 @@
 'use strict'
 
 const { expect } = require('aegir/utils/chai')
-const uint8ArrayFromString = require('uint8arrays/from-string')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 const { MemoryDatastore } = require('interface-datastore')
 const Keychain = require('../../src/keychain')
 
diff --git a/test/keychain/keychain.spec.js b/test/keychain/keychain.spec.js
index 9c5047a964..26a1d60892 100644
--- a/test/keychain/keychain.spec.js
+++ b/test/keychain/keychain.spec.js
@@ -4,8 +4,8 @@
 
 const { expect } = require('aegir/utils/chai')
 const fail = expect.fail
-const uint8ArrayFromString = require('uint8arrays/from-string')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 const peerUtils = require('../utils/creators/peer')
 
diff --git a/test/keychain/peerid.spec.js b/test/keychain/peerid.spec.js
index 50d42e13f5..9d9592f396 100644
--- a/test/keychain/peerid.spec.js
+++ b/test/keychain/peerid.spec.js
@@ -7,7 +7,7 @@ const { base58btc } = require('multiformats/bases/base58')
 const crypto = require('libp2p-crypto')
 const rsaUtils = require('libp2p-crypto/src/keys/rsa-utils')
 const rsaClass = require('libp2p-crypto/src/keys/rsa-class')
-const uint8ArrayFromString = require('uint8arrays/from-string')
+const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
 
 const sample = {
   id: '122019318b6e5e0cf93a2314bf01269a2cc23cd3dcd452d742cdb9379d8646f6e4a9',
diff --git a/test/peer-discovery/index.node.js b/test/peer-discovery/index.node.js
index 3f6b3f33ea..c7db964603 100644
--- a/test/peer-discovery/index.node.js
+++ b/test/peer-discovery/index.node.js
@@ -11,7 +11,7 @@ const crypto = require('libp2p-crypto')
 const KadDht = require('libp2p-kad-dht')
 const MulticastDNS = require('libp2p-mdns')
 const { Multiaddr } = require('multiaddr')
-const uint8ArrayToString = require('uint8arrays/to-string')
+const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 const Libp2p = require('../../src')
 const baseOptions = require('../utils/base-options')
diff --git a/test/peer-store/metadata-book.spec.js b/test/peer-store/metadata-book.spec.js
index e75ae419bd..a1b0d2a105 100644
--- a/test/peer-store/metadata-book.spec.js
+++ b/test/peer-store/metadata-book.spec.js
@@ -2,7 +2,7 @@
 /* eslint-env mocha */
 
 const { expect } = require('aegir/utils/chai')
-const uint8ArrayFromString = require('uint8arrays/from-string')
+const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
 
 const pDefer = require('p-defer')
 const PeerStore = require('../../src/peer-store')
diff --git a/test/peer-store/peer-store.spec.js b/test/peer-store/peer-store.spec.js
index c316240bdd..4cfaecfe18 100644
--- a/test/peer-store/peer-store.spec.js
+++ b/test/peer-store/peer-store.spec.js
@@ -5,7 +5,7 @@ const { expect } = require('aegir/utils/chai')
 
 const PeerStore = require('../../src/peer-store')
 const { Multiaddr } = require('multiaddr')
-const uint8ArrayFromString = require('uint8arrays/from-string')
+const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
 
 const peerUtils = require('../utils/creators/peer')
 
diff --git a/test/peer-store/persisted-peer-store.spec.js b/test/peer-store/persisted-peer-store.spec.js
index 9c669efd52..985092600d 100644
--- a/test/peer-store/persisted-peer-store.spec.js
+++ b/test/peer-store/persisted-peer-store.spec.js
@@ -10,7 +10,7 @@ const PeerStore = require('../../src/peer-store/persistent')
 
 const { Multiaddr } = require('multiaddr')
 const { MemoryDatastore } = require('interface-datastore')
-const uint8ArrayFromString = require('uint8arrays/from-string')
+const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
 
 const peerUtils = require('../utils/creators/peer')
 
diff --git a/test/pnet/index.spec.js b/test/pnet/index.spec.js
index 98327146d8..76278e3829 100644
--- a/test/pnet/index.spec.js
+++ b/test/pnet/index.spec.js
@@ -5,7 +5,7 @@ const { expect } = require('aegir/utils/chai')
 const duplexPair = require('it-pair/duplex')
 const pipe = require('it-pipe')
 const { collect } = require('streaming-iterables')
-const uint8ArrayFromString = require('uint8arrays/from-string')
+const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
 
 const Protector = require('../../src/pnet')
 const Errors = Protector.errors
diff --git a/test/record/envelope.spec.js b/test/record/envelope.spec.js
index a99ad834c5..c932528f21 100644
--- a/test/record/envelope.spec.js
+++ b/test/record/envelope.spec.js
@@ -2,8 +2,8 @@
 /* eslint-env mocha */
 
 const { expect } = require('aegir/utils/chai')
-const uint8arrayFromString = require('uint8arrays/from-string')
-const uint8arrayEquals = require('uint8arrays/equals')
+const { fromString: uint8arrayFromString } = require('uint8arrays/from-string')
+const { equals: uint8arrayEquals } = require('uint8arrays/equals')
 const Envelope = require('../../src/record/envelope')
 const { codes: ErrorCodes } = require('../../src/errors')
 
diff --git a/test/relay/relay.node.js b/test/relay/relay.node.js
index b75fb1cd31..f2290a74e4 100644
--- a/test/relay/relay.node.js
+++ b/test/relay/relay.node.js
@@ -9,7 +9,7 @@ const { collect } = require('streaming-iterables')
 const pipe = require('it-pipe')
 const AggregateError = require('aggregate-error')
 const PeerId = require('peer-id')
-const uint8ArrayFromString = require('uint8arrays/from-string')
+const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
 
 const { createPeerId } = require('../utils/creators/peer')
 const baseOptions = require('../utils/base-options')
diff --git a/test/upgrading/upgrader.spec.js b/test/upgrading/upgrader.spec.js
index b9f276a7ed..36ed56c261 100644
--- a/test/upgrading/upgrader.spec.js
+++ b/test/upgrading/upgrader.spec.js
@@ -12,7 +12,7 @@ const pSettle = require('p-settle')
 const Transport = require('libp2p-websockets')
 const { NOISE: Crypto } = require('libp2p-noise')
 const Protector = require('../../src/pnet')
-const uint8ArrayFromString = require('uint8arrays/from-string')
+const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
 const swarmKeyBuffer = uint8ArrayFromString(require('../fixtures/swarm.key'))
 
 const Libp2p = require('../../src')

From 3d25ff7fd03bdabaf0f16e51de4020e8b571f6f9 Mon Sep 17 00:00:00 2001
From: Vasco Santos 
Date: Thu, 19 Aug 2021 13:18:50 +0200
Subject: [PATCH 236/447] chore: use new libp2p-noise

---
 .aegir.js                                   | 2 +-
 examples/auto-relay/README.md               | 6 +++---
 examples/auto-relay/dialer.js               | 2 +-
 examples/auto-relay/listener.js             | 2 +-
 examples/auto-relay/relay.js                | 2 +-
 examples/chat/src/libp2p.js                 | 2 +-
 examples/connection-encryption/1.js         | 2 +-
 examples/connection-encryption/README.md    | 4 ++--
 examples/discovery-mechanisms/1.js          | 2 +-
 examples/discovery-mechanisms/2.js          | 2 +-
 examples/discovery-mechanisms/3.js          | 2 +-
 examples/discovery-mechanisms/README.md     | 2 +-
 examples/echo/src/libp2p.js                 | 2 +-
 examples/libp2p-in-the-browser/index.js     | 2 +-
 examples/libp2p-in-the-browser/package.json | 2 +-
 examples/peer-and-content-routing/1.js      | 2 +-
 examples/peer-and-content-routing/2.js      | 2 +-
 examples/pnet/libp2p-node.js                | 2 +-
 examples/protocol-and-stream-muxing/1.js    | 2 +-
 examples/protocol-and-stream-muxing/2.js    | 2 +-
 examples/protocol-and-stream-muxing/3.js    | 2 +-
 examples/pubsub/1.js                        | 2 +-
 examples/pubsub/message-filtering/1.js      | 2 +-
 examples/transports/1.js                    | 2 +-
 examples/transports/2.js                    | 2 +-
 examples/transports/3.js                    | 2 +-
 examples/transports/4.js                    | 2 +-
 examples/transports/README.md               | 4 ++--
 examples/webrtc-direct/dialer.js            | 2 +-
 examples/webrtc-direct/listener.js          | 2 +-
 examples/webrtc-direct/package.json         | 2 +-
 package.json                                | 4 ++--
 test/configuration/utils.js                 | 2 +-
 test/core/consume-peer-record.spec.js       | 2 +-
 test/core/encryption.spec.js                | 2 +-
 test/core/listening.node.js                 | 2 +-
 test/dialing/direct.node.js                 | 2 +-
 test/dialing/direct.spec.js                 | 2 +-
 test/transports/transport-manager.spec.js   | 2 +-
 test/ts-use/package.json                    | 2 +-
 test/ts-use/src/main.ts                     | 2 +-
 test/upgrading/upgrader.spec.js             | 2 +-
 test/utils/base-options.browser.js          | 2 +-
 test/utils/base-options.js                  | 2 +-
 44 files changed, 49 insertions(+), 49 deletions(-)

diff --git a/.aegir.js b/.aegir.js
index a787ac4fc7..92fe923d7c 100644
--- a/.aegir.js
+++ b/.aegir.js
@@ -7,7 +7,7 @@ const Peers = require('./test/fixtures/peers')
 const PeerId = require('peer-id')
 const WebSockets = require('libp2p-websockets')
 const Muxer = require('libp2p-mplex')
-const { NOISE: Crypto } = require('libp2p-noise')
+const { NOISE: Crypto } = require('@chainsafe/libp2p-noise')
 const pipe = require('it-pipe')
 let libp2p
 
diff --git a/examples/auto-relay/README.md b/examples/auto-relay/README.md
index fbb3b7a046..bad4f2b619 100644
--- a/examples/auto-relay/README.md
+++ b/examples/auto-relay/README.md
@@ -18,7 +18,7 @@ The relay node will need to have its relay subsystem enabled, as well as its HOP
 ```js
 const Libp2p = require('libp2p')
 const Websockets = require('libp2p-websockets')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 const MPLEX = require('libp2p-mplex')
 
 const node = await Libp2p.create({
@@ -76,7 +76,7 @@ One of the typical use cases for Auto Relay is nodes behind a NAT or browser nod
 ```js
 const Libp2p = require('libp2p')
 const Websockets = require('libp2p-websockets')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 const MPLEX = require('libp2p-mplex')
 
 const relayAddr = process.argv[2]
@@ -147,7 +147,7 @@ Now that you have a relay node and a node bound to that relay, you can test conn
 ```js
 const Libp2p = require('libp2p')
 const Websockets = require('libp2p-websockets')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 const MPLEX = require('libp2p-mplex')
 
 const autoRelayNodeAddr = process.argv[2]
diff --git a/examples/auto-relay/dialer.js b/examples/auto-relay/dialer.js
index ebf5fd1249..5f6423ede5 100644
--- a/examples/auto-relay/dialer.js
+++ b/examples/auto-relay/dialer.js
@@ -2,7 +2,7 @@
 
 const Libp2p = require('libp2p')
 const Websockets = require('libp2p-websockets')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 const MPLEX = require('libp2p-mplex')
 
 async function main () {
diff --git a/examples/auto-relay/listener.js b/examples/auto-relay/listener.js
index 0f94374067..4f762c675d 100644
--- a/examples/auto-relay/listener.js
+++ b/examples/auto-relay/listener.js
@@ -2,7 +2,7 @@
 
 const Libp2p = require('libp2p')
 const Websockets = require('libp2p-websockets')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 const MPLEX = require('libp2p-mplex')
 
 async function main () {
diff --git a/examples/auto-relay/relay.js b/examples/auto-relay/relay.js
index 2a18c3a769..18a1df51cf 100644
--- a/examples/auto-relay/relay.js
+++ b/examples/auto-relay/relay.js
@@ -2,7 +2,7 @@
 
 const Libp2p = require('libp2p')
 const Websockets = require('libp2p-websockets')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 const MPLEX = require('libp2p-mplex')
 
 async function main () {
diff --git a/examples/chat/src/libp2p.js b/examples/chat/src/libp2p.js
index c4bfb88d65..fbce60e93e 100644
--- a/examples/chat/src/libp2p.js
+++ b/examples/chat/src/libp2p.js
@@ -3,7 +3,7 @@
 const TCP = require('libp2p-tcp')
 const WS = require('libp2p-websockets')
 const mplex = require('libp2p-mplex')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 const defaultsDeep = require('@nodeutils/defaults-deep')
 const libp2p = require('../../..')
 
diff --git a/examples/connection-encryption/1.js b/examples/connection-encryption/1.js
index 1d3f0bcb01..0b8699152d 100644
--- a/examples/connection-encryption/1.js
+++ b/examples/connection-encryption/1.js
@@ -3,7 +3,7 @@
 const Libp2p = require('../..')
 const TCP = require('libp2p-tcp')
 const Mplex = require('libp2p-mplex')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 
 const pipe = require('it-pipe')
 
diff --git a/examples/connection-encryption/README.md b/examples/connection-encryption/README.md
index 2d1c0614a6..ac824bf93c 100644
--- a/examples/connection-encryption/README.md
+++ b/examples/connection-encryption/README.md
@@ -8,13 +8,13 @@ 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 `libp2p-noise` module to complete it, go ahead and `npm install libp2p-noise`.
+We will build this example on top of example for [Protocol and Stream Multiplexing](../protocol-and-stream-multiplexing). You will need the `@chainsafe/libp2p-noise` module to complete it, go ahead and `npm install @chainsafe/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 { NOISE } = require('@chainsafe/libp2p-noise')
 
 const createNode = () => {
   return Libp2p.create({
diff --git a/examples/discovery-mechanisms/1.js b/examples/discovery-mechanisms/1.js
index d5187fcd73..8ac3cc489c 100644
--- a/examples/discovery-mechanisms/1.js
+++ b/examples/discovery-mechanisms/1.js
@@ -4,7 +4,7 @@
 const Libp2p = require('../../')
 const TCP = require('libp2p-tcp')
 const Mplex = require('libp2p-mplex')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 const Bootstrap = require('libp2p-bootstrap')
 
 const bootstrapers = require('./bootstrapers')
diff --git a/examples/discovery-mechanisms/2.js b/examples/discovery-mechanisms/2.js
index 15c801cdec..bd5f8ddc2b 100644
--- a/examples/discovery-mechanisms/2.js
+++ b/examples/discovery-mechanisms/2.js
@@ -4,7 +4,7 @@
 const Libp2p = require('../../')
 const TCP = require('libp2p-tcp')
 const Mplex = require('libp2p-mplex')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 const MulticastDNS = require('libp2p-mdns')
 
 const createNode = async () => {
diff --git a/examples/discovery-mechanisms/3.js b/examples/discovery-mechanisms/3.js
index 6eaa96a91f..4fb1be1264 100644
--- a/examples/discovery-mechanisms/3.js
+++ b/examples/discovery-mechanisms/3.js
@@ -4,7 +4,7 @@
 const Libp2p = require('../../')
 const TCP = require('libp2p-tcp')
 const Mplex = require('libp2p-mplex')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 const Gossipsub = require('libp2p-gossipsub')
 const Bootstrap = require('libp2p-bootstrap')
 const PubsubPeerDiscovery = require('libp2p-pubsub-peer-discovery')
diff --git a/examples/discovery-mechanisms/README.md b/examples/discovery-mechanisms/README.md
index e2c9e0ff2a..2db83431dd 100644
--- a/examples/discovery-mechanisms/README.md
+++ b/examples/discovery-mechanisms/README.md
@@ -168,7 +168,7 @@ You can create your libp2p nodes as follows:
 const Libp2p = require('libp2p')
 const TCP = require('libp2p-tcp')
 const Mplex = require('libp2p-mplex')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 const Gossipsub = require('libp2p-gossipsub')
 const Bootstrap = require('libp2p-bootstrap')
 const PubsubPeerDiscovery = require('libp2p-pubsub-peer-discovery')
diff --git a/examples/echo/src/libp2p.js b/examples/echo/src/libp2p.js
index 4f14a6e14f..6bc1d7293e 100644
--- a/examples/echo/src/libp2p.js
+++ b/examples/echo/src/libp2p.js
@@ -3,7 +3,7 @@
 const TCP = require('libp2p-tcp')
 const WS = require('libp2p-websockets')
 const mplex = require('libp2p-mplex')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 
 const defaultsDeep = require('@nodeutils/defaults-deep')
 const libp2p = require('../../..')
diff --git a/examples/libp2p-in-the-browser/index.js b/examples/libp2p-in-the-browser/index.js
index fe5e31afed..397d1201a7 100644
--- a/examples/libp2p-in-the-browser/index.js
+++ b/examples/libp2p-in-the-browser/index.js
@@ -2,7 +2,7 @@ import 'babel-polyfill'
 import Libp2p from 'libp2p'
 import Websockets from 'libp2p-websockets'
 import WebRTCStar from 'libp2p-webrtc-star'
-import { NOISE } from 'libp2p-noise'
+import { NOISE } from '@chainsafe/libp2p-noise'
 import Mplex from 'libp2p-mplex'
 import Bootstrap from 'libp2p-bootstrap'
 
diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json
index 3aa948d5c3..cf2fdc7dee 100644
--- a/examples/libp2p-in-the-browser/package.json
+++ b/examples/libp2p-in-the-browser/package.json
@@ -18,7 +18,7 @@
     "libp2p": "../../",
     "libp2p-bootstrap": "^0.13.0",
     "libp2p-mplex": "^0.10.4",
-    "libp2p-noise": "^4.0.0",
+    "@chainsafe/libp2p-noise": "^4.1.0",
     "libp2p-webrtc-star": "^0.23.0",
     "libp2p-websockets": "^0.16.1"
   },
diff --git a/examples/peer-and-content-routing/1.js b/examples/peer-and-content-routing/1.js
index 46c54f1d79..bf2c6d1b15 100644
--- a/examples/peer-and-content-routing/1.js
+++ b/examples/peer-and-content-routing/1.js
@@ -4,7 +4,7 @@
 const Libp2p = require('../../')
 const TCP = require('libp2p-tcp')
 const Mplex = require('libp2p-mplex')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 const KadDHT = require('libp2p-kad-dht')
 
 const delay = require('delay')
diff --git a/examples/peer-and-content-routing/2.js b/examples/peer-and-content-routing/2.js
index 928234b2fa..4b528e4ac6 100644
--- a/examples/peer-and-content-routing/2.js
+++ b/examples/peer-and-content-routing/2.js
@@ -4,7 +4,7 @@
 const Libp2p = require('../../')
 const TCP = require('libp2p-tcp')
 const Mplex = require('libp2p-mplex')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 const { CID } = require('multiformats/cid')
 const KadDHT = require('libp2p-kad-dht')
 
diff --git a/examples/pnet/libp2p-node.js b/examples/pnet/libp2p-node.js
index 8fc8a01f4b..8c01fde938 100644
--- a/examples/pnet/libp2p-node.js
+++ b/examples/pnet/libp2p-node.js
@@ -3,7 +3,7 @@
 const Libp2p = require('libp2p')
 const TCP = require('libp2p-tcp')
 const MPLEX = require('libp2p-mplex')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 const Protector = require('libp2p/src/pnet')
 
 /**
diff --git a/examples/protocol-and-stream-muxing/1.js b/examples/protocol-and-stream-muxing/1.js
index 70ee3175f6..5499ba0b83 100644
--- a/examples/protocol-and-stream-muxing/1.js
+++ b/examples/protocol-and-stream-muxing/1.js
@@ -3,7 +3,7 @@
 const Libp2p = require('../../')
 const TCP = require('libp2p-tcp')
 const MPLEX = require('libp2p-mplex')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 
 const pipe = require('it-pipe')
 
diff --git a/examples/protocol-and-stream-muxing/2.js b/examples/protocol-and-stream-muxing/2.js
index 3ac6bd4206..2fecbe32d7 100644
--- a/examples/protocol-and-stream-muxing/2.js
+++ b/examples/protocol-and-stream-muxing/2.js
@@ -3,7 +3,7 @@
 const Libp2p = require('../../')
 const TCP = require('libp2p-tcp')
 const MPLEX = require('libp2p-mplex')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 
 const pipe = require('it-pipe')
 
diff --git a/examples/protocol-and-stream-muxing/3.js b/examples/protocol-and-stream-muxing/3.js
index bd15f55f8a..23cf4e7693 100644
--- a/examples/protocol-and-stream-muxing/3.js
+++ b/examples/protocol-and-stream-muxing/3.js
@@ -4,7 +4,7 @@
 const Libp2p = require('../../')
 const TCP = require('libp2p-tcp')
 const MPLEX = require('libp2p-mplex')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 
 const pipe = require('it-pipe')
 
diff --git a/examples/pubsub/1.js b/examples/pubsub/1.js
index 6d9b0fa7c7..c4eed3d0c5 100644
--- a/examples/pubsub/1.js
+++ b/examples/pubsub/1.js
@@ -4,7 +4,7 @@
 const Libp2p = require('../../')
 const TCP = require('libp2p-tcp')
 const Mplex = require('libp2p-mplex')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 const Gossipsub = require('libp2p-gossipsub')
 const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
 const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
diff --git a/examples/pubsub/message-filtering/1.js b/examples/pubsub/message-filtering/1.js
index dc1fe5811e..fce8d041c2 100644
--- a/examples/pubsub/message-filtering/1.js
+++ b/examples/pubsub/message-filtering/1.js
@@ -4,7 +4,7 @@
 const Libp2p = require('../../../')
 const TCP = require('libp2p-tcp')
 const Mplex = require('libp2p-mplex')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 const Gossipsub = require('libp2p-gossipsub')
 const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
 const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
diff --git a/examples/transports/1.js b/examples/transports/1.js
index 73857aad4d..81d8a2bc0d 100644
--- a/examples/transports/1.js
+++ b/examples/transports/1.js
@@ -3,7 +3,7 @@
 
 const Libp2p = require('../..')
 const TCP = require('libp2p-tcp')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 
 const createNode = async () => {
   const node = await Libp2p.create({
diff --git a/examples/transports/2.js b/examples/transports/2.js
index 2d62849db0..ee1f1c83ab 100644
--- a/examples/transports/2.js
+++ b/examples/transports/2.js
@@ -3,7 +3,7 @@
 
 const Libp2p = require('../..')
 const TCP = require('libp2p-tcp')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 const MPLEX = require('libp2p-mplex')
 
 const pipe = require('it-pipe')
diff --git a/examples/transports/3.js b/examples/transports/3.js
index c692444341..90fc1b1768 100644
--- a/examples/transports/3.js
+++ b/examples/transports/3.js
@@ -4,7 +4,7 @@
 const Libp2p = require('../..')
 const TCP = require('libp2p-tcp')
 const WebSockets = require('libp2p-websockets')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 const MPLEX = require('libp2p-mplex')
 
 const pipe = require('it-pipe')
diff --git a/examples/transports/4.js b/examples/transports/4.js
index 487315ec68..b46d147373 100644
--- a/examples/transports/4.js
+++ b/examples/transports/4.js
@@ -4,7 +4,7 @@
 const Libp2p = require('../..')
 const TCP = require('libp2p-tcp')
 const WebSockets = require('libp2p-websockets')
-const { NOISE } = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 const MPLEX = require('libp2p-mplex')
 
 const fs = require('fs');
diff --git a/examples/transports/README.md b/examples/transports/README.md
index 8378c9a3e1..8c9d23b908 100644
--- a/examples/transports/README.md
+++ b/examples/transports/README.md
@@ -13,7 +13,7 @@ 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-noise
+> npm install libp2p libp2p-tcp @chainsafe/libp2p-noise
 ```
 
 Then, in your favorite text editor create a file with the `.js` extension. I've called mine `1.js`.
@@ -25,7 +25,7 @@ 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 { NOISE } = require('@chainsafe/libp2p-noise')
 
 const createNode = async () => {
   const node = await Libp2p.create({
diff --git a/examples/webrtc-direct/dialer.js b/examples/webrtc-direct/dialer.js
index a7ba855886..b08720c255 100644
--- a/examples/webrtc-direct/dialer.js
+++ b/examples/webrtc-direct/dialer.js
@@ -2,7 +2,7 @@ import 'babel-polyfill'
 const Libp2p = require('libp2p')
 const WebRTCDirect = require('libp2p-webrtc-direct')
 const Mplex = require('libp2p-mplex')
-const {NOISE} = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 const Bootstrap = require('libp2p-bootstrap')
 
 document.addEventListener('DOMContentLoaded', async () => {
diff --git a/examples/webrtc-direct/listener.js b/examples/webrtc-direct/listener.js
index 47f3a97185..a8ac6cd9ae 100644
--- a/examples/webrtc-direct/listener.js
+++ b/examples/webrtc-direct/listener.js
@@ -2,7 +2,7 @@ const Libp2p = require('libp2p')
 const Bootstrap = require('libp2p-bootstrap')
 const WebRTCDirect = require('libp2p-webrtc-direct')
 const Mplex = require('libp2p-mplex')
-const {NOISE} = require('libp2p-noise')
+const { NOISE } = require('@chainsafe/libp2p-noise')
 const PeerId = require('peer-id')
 
 ;(async () => {
diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json
index c04ad84f67..a1ef5448e9 100644
--- a/examples/webrtc-direct/package.json
+++ b/examples/webrtc-direct/package.json
@@ -23,7 +23,7 @@
     "libp2p": "../../",
     "libp2p-bootstrap": "^0.13.0",
     "libp2p-mplex": "^0.10.4",
-    "libp2p-noise": "^4.0.0",
+    "@chainsafe/libp2p-noise": "^4.1.0",
     "libp2p-webrtc-direct": "^0.7.0",
     "peer-id": "^0.15.0"
   },
diff --git a/package.json b/package.json
index b0d646620f..5e7136a386 100644
--- a/package.json
+++ b/package.json
@@ -130,6 +130,7 @@
     "xsalsa20": "^1.1.0"
   },
   "devDependencies": {
+    "@chainsafe/libp2p-noise": "^4.0.0",
     "@nodeutils/defaults-deep": "^1.1.0",
     "@types/es6-promisify": "^6.0.0",
     "@types/node": "^16.0.1",
@@ -150,12 +151,11 @@
     "libp2p-delegated-content-routing": "^0.11.0",
     "libp2p-delegated-peer-routing": "^0.10.0",
     "libp2p-floodsub": "^0.27.0",
-    "libp2p-gossipsub": "^0.10.0",
+    "libp2p-gossipsub": "^0.11.0",
     "libp2p-interfaces-compliance-tests": "^1.0.0",
     "libp2p-kad-dht": "^0.23.0",
     "libp2p-mdns": "^0.17.0",
     "libp2p-mplex": "^0.10.1",
-    "libp2p-noise": "^4.0.0",
     "libp2p-tcp": "^0.17.0",
     "libp2p-webrtc-star": "^0.23.0",
     "libp2p-websockets": "^0.16.0",
diff --git a/test/configuration/utils.js b/test/configuration/utils.js
index c1a7aa5c6a..2e3ec5387a 100644
--- a/test/configuration/utils.js
+++ b/test/configuration/utils.js
@@ -1,7 +1,7 @@
 'use strict'
 
 const Pubsub = require('libp2p-interfaces/src/pubsub')
-const { NOISE: Crypto } = require('libp2p-noise')
+const { NOISE: Crypto } = require('@chainsafe/libp2p-noise')
 const Muxer = require('libp2p-mplex')
 const Transport = require('libp2p-websockets')
 const filters = require('libp2p-websockets/src/filters')
diff --git a/test/core/consume-peer-record.spec.js b/test/core/consume-peer-record.spec.js
index c20a5efad6..609a6c29e0 100644
--- a/test/core/consume-peer-record.spec.js
+++ b/test/core/consume-peer-record.spec.js
@@ -2,7 +2,7 @@
 /* eslint-env mocha */
 
 const Transport = require('libp2p-websockets')
-const { NOISE: Crypto } = require('libp2p-noise')
+const { NOISE: Crypto } = require('@chainsafe/libp2p-noise')
 
 const Libp2p = require('../../src')
 const { createPeerId } = require('../utils/creators/peer')
diff --git a/test/core/encryption.spec.js b/test/core/encryption.spec.js
index bac84be800..da5c6e11ff 100644
--- a/test/core/encryption.spec.js
+++ b/test/core/encryption.spec.js
@@ -4,7 +4,7 @@
 const { expect } = require('aegir/utils/chai')
 
 const Transport = require('libp2p-websockets')
-const { NOISE: Crypto } = require('libp2p-noise')
+const { NOISE: Crypto } = require('@chainsafe/libp2p-noise')
 
 const Libp2p = require('../../src')
 const { codes: ErrorCodes } = require('../../src/errors')
diff --git a/test/core/listening.node.js b/test/core/listening.node.js
index 7e948ca869..c83c6d8951 100644
--- a/test/core/listening.node.js
+++ b/test/core/listening.node.js
@@ -4,7 +4,7 @@
 const { expect } = require('aegir/utils/chai')
 
 const Transport = require('libp2p-tcp')
-const { NOISE: Crypto } = require('libp2p-noise')
+const { NOISE: Crypto } = require('@chainsafe/libp2p-noise')
 
 const { create } = require('../../src')
 const peerUtils = require('../utils/creators/peer')
diff --git a/test/dialing/direct.node.js b/test/dialing/direct.node.js
index 74b3989c6f..57a8008492 100644
--- a/test/dialing/direct.node.js
+++ b/test/dialing/direct.node.js
@@ -5,7 +5,7 @@ const { expect } = require('aegir/utils/chai')
 const sinon = require('sinon')
 const Transport = require('libp2p-tcp')
 const Muxer = require('libp2p-mplex')
-const { NOISE: Crypto } = require('libp2p-noise')
+const { NOISE: Crypto } = require('@chainsafe/libp2p-noise')
 const { Multiaddr } = require('multiaddr')
 const PeerId = require('peer-id')
 const delay = require('delay')
diff --git a/test/dialing/direct.spec.js b/test/dialing/direct.spec.js
index a24a1ca2f4..7f56b91dbb 100644
--- a/test/dialing/direct.spec.js
+++ b/test/dialing/direct.spec.js
@@ -9,7 +9,7 @@ const delay = require('delay')
 const Transport = require('libp2p-websockets')
 const filters = require('libp2p-websockets/src/filters')
 const Muxer = require('libp2p-mplex')
-const { NOISE: Crypto } = require('libp2p-noise')
+const { NOISE: Crypto } = require('@chainsafe/libp2p-noise')
 const { Multiaddr } = require('multiaddr')
 const AggregateError = require('aggregate-error')
 const { AbortError } = require('libp2p-interfaces/src/transport/errors')
diff --git a/test/transports/transport-manager.spec.js b/test/transports/transport-manager.spec.js
index 75bb1b9193..1fb5be1ed6 100644
--- a/test/transports/transport-manager.spec.js
+++ b/test/transports/transport-manager.spec.js
@@ -7,7 +7,7 @@ const sinon = require('sinon')
 const { Multiaddr } = require('multiaddr')
 const Transport = require('libp2p-websockets')
 const filters = require('libp2p-websockets/src/filters')
-const { NOISE: Crypto } = require('libp2p-noise')
+const { NOISE: Crypto } = require('@chainsafe/libp2p-noise')
 const AddressManager = require('../../src/address-manager')
 const TransportManager = require('../../src/transport-manager')
 const mockUpgrader = require('../utils/mockUpgrader')
diff --git a/test/ts-use/package.json b/test/ts-use/package.json
index 8a0670c65a..6445e42fb1 100644
--- a/test/ts-use/package.json
+++ b/test/ts-use/package.json
@@ -12,7 +12,7 @@
     "libp2p-interfaces": "^1.0.1",
     "libp2p-kad-dht": "^0.23.1",
     "libp2p-mplex": "^0.10.4",
-    "libp2p-noise": "^4.0.0",
+    "@chainsafe/libp2p-noise": "^4.1.0",
     "libp2p-record": "^0.10.4",
     "libp2p-tcp": "^0.17.1",
     "libp2p-websockets": "^0.16.1",
diff --git a/test/ts-use/src/main.ts b/test/ts-use/src/main.ts
index 3d18cd3a3e..d75a79f4ba 100644
--- a/test/ts-use/src/main.ts
+++ b/test/ts-use/src/main.ts
@@ -3,7 +3,7 @@ import Libp2pRecord = require('libp2p-record')
 import TCP = require('libp2p-tcp')
 
 const WEBSOCKETS = require('libp2p-websockets')
-const NOISE = require('libp2p-noise')
+const NOISE = require('@chainsafe/libp2p-noise')
 const MPLEX = require('libp2p-mplex')
 const Gossipsub = require('libp2p-gossipsub')
 const DHT = require('libp2p-kad-dht')
diff --git a/test/upgrading/upgrader.spec.js b/test/upgrading/upgrader.spec.js
index 36ed56c261..e78f7007c3 100644
--- a/test/upgrading/upgrader.spec.js
+++ b/test/upgrading/upgrader.spec.js
@@ -10,7 +10,7 @@ const pipe = require('it-pipe')
 const { collect } = require('streaming-iterables')
 const pSettle = require('p-settle')
 const Transport = require('libp2p-websockets')
-const { NOISE: Crypto } = require('libp2p-noise')
+const { NOISE: Crypto } = require('@chainsafe/libp2p-noise')
 const Protector = require('../../src/pnet')
 const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
 const swarmKeyBuffer = uint8ArrayFromString(require('../fixtures/swarm.key'))
diff --git a/test/utils/base-options.browser.js b/test/utils/base-options.browser.js
index d033a4c945..1d8b5941d3 100644
--- a/test/utils/base-options.browser.js
+++ b/test/utils/base-options.browser.js
@@ -3,7 +3,7 @@
 const Transport = require('libp2p-websockets')
 const filters = require('libp2p-websockets/src/filters')
 const Muxer = require('libp2p-mplex')
-const { NOISE: Crypto } = require('libp2p-noise')
+const { NOISE: Crypto } = require('@chainsafe/libp2p-noise')
 
 const transportKey = Transport.prototype[Symbol.toStringTag]
 
diff --git a/test/utils/base-options.js b/test/utils/base-options.js
index e2db4f85a2..e403ff0fc5 100644
--- a/test/utils/base-options.js
+++ b/test/utils/base-options.js
@@ -2,7 +2,7 @@
 
 const Transport = require('libp2p-tcp')
 const Muxer = require('libp2p-mplex')
-const { NOISE: Crypto } = require('libp2p-noise')
+const { NOISE: Crypto } = require('@chainsafe/libp2p-noise')
 
 module.exports = {
   modules: {

From 1d62ead8e57ba1629155fe6bd263647efacb52cb Mon Sep 17 00:00:00 2001
From: Vasco Santos 
Date: Fri, 20 Aug 2021 09:25:14 +0200
Subject: [PATCH 237/447] chore: update contributors

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 5e7136a386..8ffff68341 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "libp2p",
-  "version": "0.32.3",
+  "version": "0.32.4",
   "description": "JavaScript implementation of libp2p, a modular peer to peer network stack",
   "leadMaintainer": "Jacob Heun ",
   "main": "src/index.js",

From e82b6e414b8afd29eec5e24572be90b0dfe3629d Mon Sep 17 00:00:00 2001
From: Vasco Santos 
Date: Fri, 20 Aug 2021 09:25:15 +0200
Subject: [PATCH 238/447] chore: release version v0.32.4

---
 CHANGELOG.md | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 781a0360ee..7d11e69660 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## [0.32.4](https://github.com/libp2p/js-libp2p/compare/v0.32.3...v0.32.4) (2021-08-20)
+
+
+
 ## [0.32.3](https://github.com/libp2p/js-libp2p/compare/v0.32.2...v0.32.3) (2021-08-16)
 
 

From fe63990a1653523abd3d9c209c97600af263fb58 Mon Sep 17 00:00:00 2001
From: Vasco Santos 
Date: Thu, 26 Aug 2021 11:19:13 +0200
Subject: [PATCH 239/447] chore: libp2p interop job needs exit for aegir (#971)

---
 .github/workflows/main.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 83f8a68892..69cd85eb3d 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -67,4 +67,4 @@ jobs:
     steps:
       - uses: actions/checkout@v2
       - run: npm install
-      - run: cd node_modules/interop-libp2p && yarn && LIBP2P_JS=${GITHUB_WORKSPACE}/src/index.js npx aegir test -t node --bail
+      - run: cd node_modules/interop-libp2p && yarn && LIBP2P_JS=${GITHUB_WORKSPACE}/src/index.js npx aegir test -t node --bail -- --exit

From f342c1ff50b292c1eece7c7d1e726533c5ccba28 Mon Sep 17 00:00:00 2001
From: Vasco Santos 
Date: Thu, 26 Aug 2021 12:02:17 +0200
Subject: [PATCH 240/447] docs: update package list (#969)

---
 README.md         | 2 +-
 package-list.json | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 8765f6bd48..d5c901252f 100644
--- a/README.md
+++ b/README.md
@@ -163,9 +163,9 @@ List of packages currently in existence for libp2p
 | [`libp2p-kad-dht`](//github.com/libp2p/js-libp2p-kad-dht) | [![npm](https://img.shields.io/npm/v/libp2p-kad-dht.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-kad-dht/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-kad-dht.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-kad-dht) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-kad-dht/master)](https://travis-ci.com/libp2p/js-libp2p-kad-dht) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-kad-dht/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-kad-dht) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
 | **utilities** |
 | [`libp2p-crypto`](//github.com/libp2p/js-libp2p-crypto) | [![npm](https://img.shields.io/npm/v/libp2p-crypto.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-crypto/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-crypto.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-crypto) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-crypto/master)](https://travis-ci.com/libp2p/js-libp2p-crypto) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-crypto/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-crypto) | [Jacob Heun](mailto:jacobheun@gmail.com) |
-| [`libp2p-crypto-secp256k1`](//github.com/libp2p/js-libp2p-crypto-secp256k1) | [![npm](https://img.shields.io/npm/v/libp2p-crypto-secp256k1.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-crypto-secp256k1/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-crypto-secp256k1.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-crypto-secp256k1) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-crypto-secp256k1/master)](https://travis-ci.com/libp2p/js-libp2p-crypto-secp256k1) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-crypto-secp256k1/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-crypto-secp256k1) | [Friedel Ziegelmayer](mailto:dignifiedquire@gmail.com) |
 | **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) |
+| [`libp2p-record`](//github.com/libp2p/js-libp2p-record) | [![npm](https://img.shields.io/npm/v/libp2p-record.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-record/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-record.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-record) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-record/master)](https://travis-ci.com/libp2p/js-libp2p-record) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-record/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-record) | [Jacob Heun](mailto:santos.vasco10@gmail.com) |
 | **pubsub** |
 | [`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) |
diff --git a/package-list.json b/package-list.json
index 28588539c5..bfcdfd53a4 100644
--- a/package-list.json
+++ b/package-list.json
@@ -44,10 +44,10 @@
 
     "utilities",
     ["libp2p/js-libp2p-crypto", "libp2p-crypto"],
-    ["libp2p/js-libp2p-crypto-secp256k1", "libp2p-crypto-secp256k1"],
 
     "data types",
     ["libp2p/js-peer-id", "peer-id"],
+    ["libp2p/js-libp2p-record", "libp2p-record"],
 
     "pubsub",
     ["libp2p/js-libp2p-floodsub", "libp2p-floodsub"],

From f8e3cf10b0dc84e7da9c43c1e7d8c4fd8f8bee6c Mon Sep 17 00:00:00 2001
From: Vasco Santos 
Date: Thu, 26 Aug 2021 12:02:28 +0200
Subject: [PATCH 241/447] chore: add dependabot (#968)

---
 .github/dependabot.yml | 8 ++++++++
 1 file changed, 8 insertions(+)
 create mode 100644 .github/dependabot.yml

diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000000..de46e32616
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,8 @@
+version: 2
+updates:
+- package-ecosystem: npm
+  directory: "/"
+  schedule:
+    interval: daily
+    time: "11:00"
+  open-pull-requests-limit: 10

From d3f78edffeedd46a739605ca834d49565c86b74e Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 26 Aug 2021 12:28:44 +0200
Subject: [PATCH 242/447] chore(deps-dev): bump ipfs-http-client from 50.1.2 to
 52.0.2 (#973)

Bumps [ipfs-http-client](https://github.com/ipfs/js-ipfs) from 50.1.2 to 52.0.2.
- [Release notes](https://github.com/ipfs/js-ipfs/releases)
- [Changelog](https://github.com/ipfs/js-ipfs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@50.1.2...ipfs-http-client@52.0.2)

---
updated-dependencies:
- dependency-name: ipfs-http-client
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] 

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 8ffff68341..602a8656d1 100644
--- a/package.json
+++ b/package.json
@@ -142,7 +142,7 @@
     "delay": "^5.0.0",
     "interop-libp2p": "^0.4.0",
     "into-stream": "^6.0.0",
-    "ipfs-http-client": "^50.1.1",
+    "ipfs-http-client": "^52.0.2",
     "it-concat": "^2.0.0",
     "it-pair": "^1.0.0",
     "it-pushable": "^1.4.0",

From 97107c4ef756629a44cedd1203f2ae9a92cc3974 Mon Sep 17 00:00:00 2001
From: Leask Wong 
Date: Tue, 31 Aug 2021 05:51:14 -0400
Subject: [PATCH 243/447] chore: update datastore usage in CONFIGURATION.md
 (#982)

Co-authored-by: Vasco Santos 
---
 doc/CONFIGURATION.md | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md
index 135390eb1e..e64c257bd7 100644
--- a/doc/CONFIGURATION.md
+++ b/doc/CONFIGURATION.md
@@ -1,4 +1,4 @@
-# Configuration
+# 
 
 - [Configuration](#configuration)
   - [Overview](#overview)
@@ -503,6 +503,9 @@ const MPLEX = require('libp2p-mplex')
 const { NOISE } = require('libp2p-noise')
 const LevelStore = require('datastore-level')
 
+const datastore = new LevelStore('path/to/store')
+await datastore.open()
+
 const node = await Libp2p.create({
   modules: {
     transport: [TCP],
@@ -511,7 +514,7 @@ const node = await Libp2p.create({
   },
   keychain: {
     pass: 'notsafepassword123456789',
-    datastore: new LevelStore('path/to/store')
+    datastore: dsInstant,
   }
 })
 
@@ -673,16 +676,18 @@ const Libp2p = require('libp2p')
 const TCP = require('libp2p-tcp')
 const MPLEX = require('libp2p-mplex')
 const { NOISE } = require('libp2p-noise')
-
 const LevelStore = require('datastore-level')
 
+const datastore = new LevelStore('path/to/store')
+const dsInstant = await datastore.open()
+
 const node = await Libp2p.create({
   modules: {
     transport: [TCP],
     streamMuxer: [MPLEX],
     connEncryption: [NOISE]
   },
-  datastore: new LevelStore('path/to/store'),
+  datastore: dsInstant,
   peerStore: {
     persistence: true,
     threshold: 5

From 122c89dd0df55a59edaae078e3dc7c31b5603715 Mon Sep 17 00:00:00 2001
From: XiaoZhang 
Date: Tue, 21 Sep 2021 15:36:37 +0800
Subject: [PATCH 244/447] fix: move abortable-iterator to dependencies (#992)

fix #986
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 602a8656d1..a4d9146d0d 100644
--- a/package.json
+++ b/package.json
@@ -78,6 +78,7 @@
     ]
   },
   "dependencies": {
+    "abortable-iterator": "^3.0.0",
     "@motrix/nat-api": "^0.3.1",
     "@vascosantos/moving-average": "^1.1.0",
     "abort-controller": "^3.0.0",
@@ -136,7 +137,6 @@
     "@types/node": "^16.0.1",
     "@types/node-forge": "^0.10.1",
     "@types/varint": "^6.0.0",
-    "abortable-iterator": "^3.0.0",
     "aegir": "^33.1.1",
     "buffer": "^6.0.3",
     "delay": "^5.0.0",

From 2ab811d7086714d4e66afec97ddcae572deb8396 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 21 Sep 2021 09:39:25 +0200
Subject: [PATCH 245/447] chore(deps-dev): bump libp2p-kad-dht from 0.23.4 to
 0.24.2 (#991)

Bumps [libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht) from 0.23.4 to 0.24.2.
- [Release notes](https://github.com/libp2p/js-libp2p-kad-dht/releases)
- [Changelog](https://github.com/libp2p/js-libp2p-kad-dht/blob/master/CHANGELOG.md)
- [Commits](https://github.com/libp2p/js-libp2p-kad-dht/compare/v0.23.4...v0.24.2)

---
updated-dependencies:
- dependency-name: libp2p-kad-dht
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] 

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index a4d9146d0d..0c50e6ac64 100644
--- a/package.json
+++ b/package.json
@@ -153,7 +153,7 @@
     "libp2p-floodsub": "^0.27.0",
     "libp2p-gossipsub": "^0.11.0",
     "libp2p-interfaces-compliance-tests": "^1.0.0",
-    "libp2p-kad-dht": "^0.23.0",
+    "libp2p-kad-dht": "^0.24.2",
     "libp2p-mdns": "^0.17.0",
     "libp2p-mplex": "^0.10.1",
     "libp2p-tcp": "^0.17.0",

From ede653cad913cb31e984c776975266e5320ecc97 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 21 Sep 2021 09:39:58 +0200
Subject: [PATCH 246/447] chore(deps-dev): bump into-stream from 6.0.0 to 7.0.0
 (#972)

Bumps [into-stream](https://github.com/sindresorhus/into-stream) from 6.0.0 to 7.0.0.
- [Release notes](https://github.com/sindresorhus/into-stream/releases)
- [Commits](https://github.com/sindresorhus/into-stream/compare/v6.0.0...v7.0.0)

---
updated-dependencies:
- dependency-name: into-stream
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] 

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 0c50e6ac64..c668d066b6 100644
--- a/package.json
+++ b/package.json
@@ -141,7 +141,7 @@
     "buffer": "^6.0.3",
     "delay": "^5.0.0",
     "interop-libp2p": "^0.4.0",
-    "into-stream": "^6.0.0",
+    "into-stream": "^7.0.0",
     "ipfs-http-client": "^52.0.2",
     "it-concat": "^2.0.0",
     "it-pair": "^1.0.0",

From 3aedf5511513227bb9d96f3ed863d57f1613420f Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 21 Sep 2021 09:40:36 +0200
Subject: [PATCH 247/447] chore(deps): bump es6-promisify from 6.1.1 to 7.0.0
 (#974)

Bumps [es6-promisify](https://github.com/mikehall314/es6-promisify) from 6.1.1 to 7.0.0.
- [Release notes](https://github.com/mikehall314/es6-promisify/releases)
- [Commits](https://github.com/mikehall314/es6-promisify/compare/v6.1.1...v7.0.0)

---
updated-dependencies:
- dependency-name: es6-promisify
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] 

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index c668d066b6..ad971f61d0 100644
--- a/package.json
+++ b/package.json
@@ -88,7 +88,7 @@
     "class-is": "^1.1.0",
     "debug": "^4.3.1",
     "err-code": "^3.0.0",
-    "es6-promisify": "^6.1.1",
+    "es6-promisify": "^7.0.0",
     "events": "^3.3.0",
     "hashlru": "^2.3.0",
     "interface-datastore": "^5.1.1",

From c635b08d2fbd2f641343fa951b098284dae3799a Mon Sep 17 00:00:00 2001
From: Vasco Santos 
Date: Tue, 21 Sep 2021 09:47:01 +0200
Subject: [PATCH 248/447] chore: update contributors

---
 package.json | 82 +++++++++++++++++++++++++++-------------------------
 1 file changed, 42 insertions(+), 40 deletions(-)

diff --git a/package.json b/package.json
index ad971f61d0..6870255ce1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "libp2p",
-  "version": "0.32.4",
+  "version": "0.32.5",
   "description": "JavaScript implementation of libp2p, a modular peer to peer network stack",
   "leadMaintainer": "Jacob Heun ",
   "main": "src/index.js",
@@ -178,67 +178,69 @@
     "Friedel Ziegelmayer ",
     "Maciej Krüger ",
     "Hugo Dias ",
-    "Volker Mische ",
     "Chris Dostert ",
     "dirkmc ",
-    "Richard Littauer ",
+    "Volker Mische ",
     "zeim839 <50573884+zeim839@users.noreply.github.com>",
-    "Ryan Bell ",
+    "Richard Littauer ",
     "a1300 ",
+    "Ryan Bell ",
     "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ",
-    "Andrew Nesbitt ",
-    "Elven ",
     "Franck Royer ",
-    "Giovanni T. Parra ",
-    "Samlior ",
     "Thomas Eizinger ",
+    "Giovanni T. Parra ",
     "acolytec3 <17355484+acolytec3@users.noreply.github.com>",
-    "Didrik Nordström ",
-    "Irakli Gozalishvili ",
-    "Joel Gustafson ",
-    "John Rees ",
-    "João Santos ",
-    "Julien Bouquillon ",
-    "Kevin Kwok ",
-    "Kevin Lacker ",
-    "Lars Gierth ",
-    "Ethan Lam ",
-    "Marcin Tojek ",
-    "Michael Burns <5170+mburns@users.noreply.github.com>",
-    "Miguel Mota ",
-    "Nuno Nogueira ",
-    "Dmitriy Ryajov ",
-    "Philipp Muens ",
+    "Elven ",
+    "Andrew Nesbitt ",
+    "Samlior ",
+    "Didrik Nordström ",
     "RasmusErik Voel Jensen ",
-    "Diogo Silva ",
     "Robert Kiel ",
-    "phillmac ",
-    "robertkiel ",
     "Smite Chow ",
     "Soeren ",
     "Sönke Hahn ",
     "TJKoury ",
-    "shresthagrawal <34920931+shresthagrawal@users.noreply.github.com>",
     "Tiago Alves ",
-    "Daijiro Wachi ",
+    "XiaoZhang ",
     "Yusef Napora ",
     "Zane Starr ",
-    "swedneck <40505480+swedneck@users.noreply.github.com>",
-    "Aleksei ",
-    "Cindy Wu ",
-    "Aditya Bose <13054902+adbose@users.noreply.github.com>",
-    "Chris Bratlien ",
     "ebinks ",
+    "Aditya Bose <13054902+adbose@users.noreply.github.com>",
+    "isan_rivkin ",
+    "mayerwin ",
+    "mcclure ",
+    "phillmac ",
+    "robertkiel ",
+    "shresthagrawal <34920931+shresthagrawal@users.noreply.github.com>",
+    "swedneck <40505480+swedneck@users.noreply.github.com>",
     "greenSnot ",
+    "Aleksei ",
     "Bernd Strehl ",
-    "Francis Gulotta ",
-    "isan_rivkin ",
+    "Chris Bratlien ",
+    "Cindy Wu ",
+    "Daijiro Wachi ",
+    "Diogo Silva ",
+    "Dmitriy Ryajov ",
+    "Ethan Lam ",
+    "Fei Liu ",
+    "Felipe Martins ",
     "Florian-Merle ",
-    "mayerwin ",
+    "Francis Gulotta ",
     "Guy Sviry <32539816+guysv@users.noreply.github.com>",
     "Henrique Dias ",
-    "mcclure ",
-    "Felipe Martins ",
-    "Fei Liu "
+    "Irakli Gozalishvili ",
+    "Joel Gustafson ",
+    "John Rees ",
+    "João Santos ",
+    "Julien Bouquillon ",
+    "Kevin Kwok ",
+    "Kevin Lacker ",
+    "Lars Gierth ",
+    "Leask Wong ",
+    "Marcin Tojek ",
+    "Michael Burns <5170+mburns@users.noreply.github.com>",
+    "Miguel Mota ",
+    "Nuno Nogueira ",
+    "Philipp Muens "
   ]
 }

From 1c2e4d89acbbd657f4dd44c93933d1a531a059f8 Mon Sep 17 00:00:00 2001
From: Vasco Santos 
Date: Tue, 21 Sep 2021 09:47:01 +0200
Subject: [PATCH 249/447] chore: release version v0.32.5

---
 CHANGELOG.md | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7d11e69660..182edf1472 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,12 @@
+## [0.32.5](https://github.com/libp2p/js-libp2p/compare/v0.32.4...v0.32.5) (2021-09-21)
+
+
+### Bug Fixes
+
+* move abortable-iterator to dependencies ([#992](https://github.com/libp2p/js-libp2p/issues/992)) ([122c89d](https://github.com/libp2p/js-libp2p/commit/122c89dd0df55a59edaae078e3dc7c31b5603715)), closes [#986](https://github.com/libp2p/js-libp2p/issues/986)
+
+
+
 ## [0.32.4](https://github.com/libp2p/js-libp2p/compare/v0.32.3...v0.32.4) (2021-08-20)
 
 

From 83734ef52061ad61ddb5ca49aae27e3a8b937058 Mon Sep 17 00:00:00 2001
From: Alex Potsides 
Date: Fri, 24 Sep 2021 09:24:29 +0100
Subject: [PATCH 250/447] chore: update datastore (#990)

`interface-datastore` now only contains the interface definition,
`datastore-core` has the various implementations.

BREAKING CHANGE: datastore implementations provided to libp2p must be compliant with interface-datastore@6.0.0
---
 package.json                                 | 3 ++-
 src/keychain/index.js                        | 2 +-
 src/peer-store/persistent/index.js           | 2 +-
 test/keychain/cms-interop.spec.js            | 2 +-
 test/keychain/keychain.spec.js               | 3 ++-
 test/peer-store/persisted-peer-store.spec.js | 2 +-
 6 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/package.json b/package.json
index 6870255ce1..1681070995 100644
--- a/package.json
+++ b/package.json
@@ -91,7 +91,7 @@
     "es6-promisify": "^7.0.0",
     "events": "^3.3.0",
     "hashlru": "^2.3.0",
-    "interface-datastore": "^5.1.1",
+    "interface-datastore": "^6.0.2",
     "it-all": "^1.0.4",
     "it-buffer": "^0.1.2",
     "it-drain": "^1.0.3",
@@ -139,6 +139,7 @@
     "@types/varint": "^6.0.0",
     "aegir": "^33.1.1",
     "buffer": "^6.0.3",
+    "datastore-core": "^6.0.7",
     "delay": "^5.0.0",
     "interop-libp2p": "^0.4.0",
     "into-stream": "^7.0.0",
diff --git a/src/keychain/index.js b/src/keychain/index.js
index 4bf63de382..20d974de36 100644
--- a/src/keychain/index.js
+++ b/src/keychain/index.js
@@ -7,7 +7,7 @@ const log = Object.assign(debug('libp2p:keychain'), {
 const sanitize = require('sanitize-filename')
 const mergeOptions = require('merge-options')
 const crypto = require('libp2p-crypto')
-const { Key } = require('interface-datastore')
+const { Key } = require('interface-datastore/key')
 const CMS = require('./cms')
 const errcode = require('err-code')
 const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
diff --git a/src/peer-store/persistent/index.js b/src/peer-store/persistent/index.js
index d0bd3dcc30..40c81f6104 100644
--- a/src/peer-store/persistent/index.js
+++ b/src/peer-store/persistent/index.js
@@ -4,7 +4,7 @@ const debug = require('debug')
 const log = Object.assign(debug('libp2p:persistent-peer-store'), {
   error: debug('libp2p:persistent-peer-store:err')
 })
-const { Key } = require('interface-datastore')
+const { Key } = require('interface-datastore/key')
 const { Multiaddr } = require('multiaddr')
 const PeerId = require('peer-id')
 const { base32 } = require('multiformats/bases/base32')
diff --git a/test/keychain/cms-interop.spec.js b/test/keychain/cms-interop.spec.js
index 79930b08a1..bd2f09e7a0 100644
--- a/test/keychain/cms-interop.spec.js
+++ b/test/keychain/cms-interop.spec.js
@@ -5,7 +5,7 @@
 const { expect } = require('aegir/utils/chai')
 const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
 const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
-const { MemoryDatastore } = require('interface-datastore')
+const { MemoryDatastore } = require('datastore-core/memory')
 const Keychain = require('../../src/keychain')
 
 describe('cms interop', () => {
diff --git a/test/keychain/keychain.spec.js b/test/keychain/keychain.spec.js
index 26a1d60892..032a1b11b7 100644
--- a/test/keychain/keychain.spec.js
+++ b/test/keychain/keychain.spec.js
@@ -9,7 +9,8 @@ const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
 
 const peerUtils = require('../utils/creators/peer')
 
-const { MemoryDatastore, Key } = require('interface-datastore')
+const { Key } = require('interface-datastore/key')
+const { MemoryDatastore } = require('datastore-core/memory')
 const Keychain = require('../../src/keychain')
 const PeerId = require('peer-id')
 const crypto = require('libp2p-crypto')
diff --git a/test/peer-store/persisted-peer-store.spec.js b/test/peer-store/persisted-peer-store.spec.js
index 985092600d..67638a10a5 100644
--- a/test/peer-store/persisted-peer-store.spec.js
+++ b/test/peer-store/persisted-peer-store.spec.js
@@ -9,7 +9,7 @@ const PeerRecord = require('../../src/record/peer-record')
 const PeerStore = require('../../src/peer-store/persistent')
 
 const { Multiaddr } = require('multiaddr')
-const { MemoryDatastore } = require('interface-datastore')
+const { MemoryDatastore } = require('datastore-core/memory')
 const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
 
 const peerUtils = require('../utils/creators/peer')

From 4d6587539c94972556c8bc2f7bc0e32eb261852c Mon Sep 17 00:00:00 2001
From: Vasco Santos 
Date: Fri, 24 Sep 2021 10:32:45 +0200
Subject: [PATCH 251/447] chore: update contributors

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 1681070995..1c653f2996 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "libp2p",
-  "version": "0.32.5",
+  "version": "0.33.0",
   "description": "JavaScript implementation of libp2p, a modular peer to peer network stack",
   "leadMaintainer": "Jacob Heun ",
   "main": "src/index.js",

From 2c9c3cf6d5449c0b49a7f7ec8f33f7f3bff7c2d5 Mon Sep 17 00:00:00 2001
From: Vasco Santos 
Date: Fri, 24 Sep 2021 10:32:46 +0200
Subject: [PATCH 252/447] chore: release version v0.33.0

---
 CHANGELOG.md | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 182edf1472..ec0b71a3e3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,17 @@
+# [0.33.0](https://github.com/libp2p/js-libp2p/compare/v0.32.5...v0.33.0) (2021-09-24)
+
+
+### chore
+
+* update datastore ([#990](https://github.com/libp2p/js-libp2p/issues/990)) ([83734ef](https://github.com/libp2p/js-libp2p/commit/83734ef52061ad61ddb5ca49aae27e3a8b937058))
+
+
+### BREAKING CHANGES
+
+* datastore implementations provided to libp2p must be compliant with interface-datastore@6.0.0
+
+
+
 ## [0.32.5](https://github.com/libp2p/js-libp2p/compare/v0.32.4...v0.32.5) (2021-09-21)
 
 

From 43e3af0c12bc6c6d5e404a3991a9e6339baed7fa Mon Sep 17 00:00:00 2001
From: Vasco Santos 
Date: Fri, 24 Sep 2021 11:33:59 +0200
Subject: [PATCH 253/447] chore: add migration guide to 0.33 (#997)

---
 doc/migrations/v0.32-v0.33.md | 14 ++++++++++++++
 1 file changed, 14 insertions(+)
 create mode 100644 doc/migrations/v0.32-v0.33.md

diff --git a/doc/migrations/v0.32-v0.33.md b/doc/migrations/v0.32-v0.33.md
new file mode 100644
index 0000000000..f7f623c021
--- /dev/null
+++ b/doc/migrations/v0.32-v0.33.md
@@ -0,0 +1,14 @@
+
+# Migrating to libp2p@33
+
+A migration guide for refactoring your application code from libp2p v0.32.x to v0.33.0.
+
+## Table of Contents
+
+- [Module Updates](#module-updates)
+
+## Module Updates
+
+Libp2p uses a datastore implementation for Peerstore persistence and for the DHT state. While libp2p defaults to a datastore implementation, it can receive any implementation of a datastore compliant with the [interface-datastore](https://github.com/ipfs/js-ipfs-interfaces/tree/master/packages/interface-datastore) via its configuration.
+
+In this release, we updated to `interface-datastore@6.0.0`. As a result, libp2p users relying on a configured datastore should update it to a compliant implementation for updating libp2p.

From a335fda8528580f70363159a1d19c1d4056cca68 Mon Sep 17 00:00:00 2001
From: Robert Kiel 
Date: Mon, 27 Sep 2021 12:42:53 +0200
Subject: [PATCH 254/447] docs: fix datastore link (#999)

---
 doc/CONFIGURATION.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md
index e64c257bd7..b9b24fd04d 100644
--- a/doc/CONFIGURATION.md
+++ b/doc/CONFIGURATION.md
@@ -210,7 +210,7 @@ const modules = {
 Moreover, the majority of the modules can be customized via option parameters. This way, it is also possible to provide this options through a `config` object. This config object should have the property name of each building block to configure, the same way as the modules specification.
 
 Besides the `modules` and `config`, libp2p allows other internal options and configurations:
-- `datastore`: an instance of [ipfs/interface-datastore](https://github.com/ipfs/interface-datastore/) modules.
+- `datastore`: an instance of [ipfs/interface-datastore](https://github.com/ipfs/js-ipfs-interfaces/tree/master/packages/interface-datastore) modules.
   - This is used in modules such as the DHT. If it is not provided, `js-libp2p` will use an in memory datastore.
 - `peerId`: the identity of the node, an instance of [libp2p/js-peer-id](https://github.com/libp2p/js-peer-id).
   - This is particularly useful if you want to reuse the same `peer-id`, as well as for modules like `libp2p-delegated-content-routing`, which need a `peer-id` in their instantiation.

From 77d7cb8f0815f2cdd3bfdfa8b641a7a186fe9520 Mon Sep 17 00:00:00 2001
From: Vasco Santos 
Date: Fri, 12 Nov 2021 09:34:44 +0000
Subject: [PATCH 255/447] fix: private ip ts compile has no call signatures
 (#1020)

---
 src/nat-manager.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/nat-manager.js b/src/nat-manager.js
index a53e152d15..c5e45cecc0 100644
--- a/src/nat-manager.js
+++ b/src/nat-manager.js
@@ -10,7 +10,6 @@ const log = Object.assign(debug('libp2p:nat'), {
 })
 const { isBrowser } = require('wherearewe')
 const retry = require('p-retry')
-// @ts-ignore private-api does not export types
 const isPrivateIp = require('private-ip')
 const pkg = require('../package.json')
 const errcode = require('err-code')
@@ -115,6 +114,7 @@ class NatManager {
       const client = this._getClient()
       const publicIp = this._externalIp || await client.externalIp()
 
+      // @ts-ignore isPrivate has no call signatures
       if (isPrivateIp(publicIp)) {
         throw new Error(`${publicIp} is private - please set config.nat.externalIp to an externally routable IP or ensure you are not behind a double NAT`)
       }

From 01a8b8da9b682dfac1bf83cc1cc3880eebd36a79 Mon Sep 17 00:00:00 2001
From: TheStarBoys <41286328+TheStarBoys@users.noreply.github.com>
Date: Fri, 12 Nov 2021 17:50:11 +0800
Subject: [PATCH 256/447] chore: example docs for auto-relay with correct port

---
 examples/auto-relay/README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/examples/auto-relay/README.md b/examples/auto-relay/README.md
index bad4f2b619..17ed555477 100644
--- a/examples/auto-relay/README.md
+++ b/examples/auto-relay/README.md
@@ -125,7 +125,7 @@ As you can see in the code, we need to provide the relay address, `relayAddr`, a
 You should now run the following to start the node running Auto Relay:
 
 ```sh
-node listener.js /ip4/192.168.1.120/tcp/58941/ws/p2p/QmQKCBm87HQMbFqy14oqC85pMmnRrj6iD46ggM6reqNpsd
+node listener.js /ip4/192.168.1.120/tcp/61592/ws/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3
 ```
 
 This should print out something similar to the following:
@@ -173,7 +173,7 @@ console.log(`Connected to the auto relay node via ${conn.remoteAddr.toString()}`
 You should now run the following to start the relay node using the listen address from step 2:
 
 ```sh
-node dialer.js /ip4/192.168.1.120/tcp/58941/ws/p2p/QmQKCBm87HQMbFqy14oqC85pMmnRrj6iD46ggM6reqNpsd
+node dialer.js /ip4/192.168.1.120/tcp/61592/ws/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3
 ```
 
 Once you start your test node, it should print out something similar to the following:

From bb0ca2819588626d05445782362ab0b431dc3794 Mon Sep 17 00:00:00 2001
From: patrickwoodhead <91056047+patrickwoodhead@users.noreply.github.com>
Date: Tue, 16 Nov 2021 15:55:00 +0000
Subject: [PATCH 257/447] docs: update connection link in API docs (#1024)

Fixes #1018

The issue was caused when the repo [js-libp2p-interfaces](https://github.com/libp2p/js-libp2p-interfaces) was renamed and refactored in this [commit](https://github.com/libp2p/js-libp2p-interfaces/commit/946348f7f8acc1ff7bc9cd0ab4c2602d41106f76)
---
 doc/API.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/API.md b/doc/API.md
index 21e144b0ac..9ec8a67610 100644
--- a/doc/API.md
+++ b/doc/API.md
@@ -2086,7 +2086,7 @@ the NatManager performing NAT hole punching.
 
 [address]: https://github.com/libp2p/js-libp2p/tree/master/src/peer-store/address-book.js
 [cid]: https://github.com/multiformats/js-cid
-[connection]: https://github.com/libp2p/js-interfaces/tree/master/src/connection
+[connection]: https://github.com/libp2p/js-libp2p-interfaces/tree/master/packages/interfaces/src/connection
 [multiaddr]: https://github.com/multiformats/js-multiaddr
 [peer-id]: https://github.com/libp2p/js-peer-id
 [keys]: https://github.com/libp2p/js-libp2p-crypto/tree/master/src/keys

From 3fb424914fb48dcecdcc8fab87283227825a24b0 Mon Sep 17 00:00:00 2001
From: Alex Potsides 
Date: Fri, 19 Nov 2021 07:26:38 +0000
Subject: [PATCH 258/447] chore: fix examples (#1026)

Uses npm to install deps for examples.

We can put yarn back when we remove `node-fetch@2.x` from ipfs-utils, or when
yarn can download tarball dependencies reliably.

This either needs:

1. https://github.com/node-fetch/node-fetch/pull/1172 merging
2. Swap node-fetch for undici
3. Drop CJS support (node-fetch 3 has the above fix but is ESM-only)
---
 .github/workflows/examples.yml | 65 +++++++++++++++++++++++++++-------
 1 file changed, 52 insertions(+), 13 deletions(-)

diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml
index b2b0aa37f6..36173f4f8a 100644
--- a/.github/workflows/examples.yml
+++ b/.github/workflows/examples.yml
@@ -12,6 +12,9 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - uses: actions/checkout@v2
+    - uses: actions/setup-node@v2
+      with:
+        node-version: 16
     - run: npm install
     - run: npx aegir lint
     - run: npx aegir ts -p check
@@ -21,82 +24,118 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
+      - uses: actions/setup-node@v2
+        with:
+          node-version: 16
       - run: npm install
-      - run: cd examples && yarn && npm run test -- auto-relay
+      - run: cd examples && npm i && npm run test -- auto-relay
   test-chat-example:
     needs: check
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
+      - uses: actions/setup-node@v2
+        with:
+          node-version: 16
       - run: npm install
-      - run: cd examples && yarn && npm run test -- chat
+      - run: cd examples && npm i && npm run test -- chat
   test-connection-encryption-example:
     needs: check
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
+      - uses: actions/setup-node@v2
+        with:
+          node-version: 16
       - run: npm install
-      - run: cd examples && yarn && npm run test -- connection-encryption
+      - run: cd examples && npm i && npm run test -- connection-encryption
   test-discovery-mechanisms-example:
     needs: check
     runs-on: macos-latest
     steps:
       - uses: actions/checkout@v2
+      - uses: actions/setup-node@v2
+        with:
+          node-version: 16
       - run: npm install
-      - run: cd examples && yarn && npm run test -- discovery-mechanisms
+      - run: cd examples && npm i && npm run test -- discovery-mechanisms
   test-echo-example:
     needs: check
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
+      - uses: actions/setup-node@v2
+        with:
+          node-version: 16
       - run: npm install
-      - run: cd examples && yarn && npm run test -- echo
+      - run: cd examples && npm i && npm run test -- echo
   test-libp2p-in-the-browser-example:
     needs: check
     runs-on: macos-latest
     steps:
       - uses: actions/checkout@v2
+      - uses: actions/setup-node@v2
+        with:
+          node-version: 16
       - run: npm install
-      - run: cd examples && yarn && npm run test -- libp2p-in-the-browser
+      - run: cd examples && npm i && npm run test -- libp2p-in-the-browser
   test-peer-and-content-routing-example:
     needs: check
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
+      - uses: actions/setup-node@v2
+        with:
+            node-version: 16
       - run: npm install
-      - run: cd examples && yarn && npm run test -- peer-and-content-routing
+      - run: cd examples && npm i && npm run test -- peer-and-content-routing
   test-pnet-example:
     needs: check
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
+      - uses: actions/setup-node@v2
+        with:
+            node-version: 16
       - run: npm install
-      - run: cd examples && yarn && npm run test -- pnet
+      - run: cd examples && npm i && npm run test -- pnet
   test-protocol-and-stream-muxing-example:
     needs: check
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
+      - uses: actions/setup-node@v2
+        with:
+            node-version: 16
       - run: npm install
-      - run: cd examples && yarn && npm run test -- protocol-and-stream-muxing
+      - run: cd examples && npm i && npm run test -- protocol-and-stream-muxing
   test-pubsub-example:
     needs: check
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
+      - uses: actions/setup-node@v2
+        with:
+            node-version: 16
       - run: npm install
-      - run: cd examples && yarn && npm run test -- pubsub
+      - run: cd examples && npm i && npm run test -- pubsub
   test-transports-example:
     needs: check
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
+      - uses: actions/setup-node@v2
+        with:
+            node-version: 16
       - run: npm install
-      - run: cd examples && yarn && npm run test -- transports
+      - run: cd examples && npm i && npm run test -- transports
   test-webrtc-direct-example:
     needs: check
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
-      - run: npm install
-      - run: cd examples && yarn && npm run test -- webrtc-direct
\ No newline at end of file
+      - uses: actions/setup-node@v2
+        with:
+            node-version: 16
+      - run: npm install -g @mapbox/node-pre-gyp && npm install
+      - run: cd examples && npm i && npm run test -- webrtc-direct
\ No newline at end of file

From 3bed7b4cb25157191b29d4b058cb6222eb3bfcd7 Mon Sep 17 00:00:00 2001
From: Alex Potsides 
Date: Fri, 19 Nov 2021 08:02:24 +0000
Subject: [PATCH 259/447] chore: update aegir (#1027)

Updates aegir, fixes all new linting errors.
---
 examples/libp2p-in-the-browser/test.js       |  2 +-
 examples/transports/3.js                     |  2 +-
 examples/utils.js                            |  4 ++--
 examples/webrtc-direct/test.js               |  2 +-
 package.json                                 |  5 +++--
 src/circuit/auto-relay.js                    |  8 ++++----
 src/circuit/circuit/hop.js                   |  4 ++--
 src/circuit/circuit/stop.js                  |  2 +-
 src/circuit/circuit/utils.js                 |  4 ++--
 src/circuit/index.js                         |  2 +-
 src/circuit/transport.js                     |  2 +-
 src/connection-manager/index.js              |  2 +-
 src/dialer/index.js                          |  4 ++--
 src/get-peer.js                              |  2 +-
 src/identify/index.js                        | 16 ++++++++--------
 src/index.js                                 |  8 ++++----
 src/insecure/plaintext.js                    |  2 +-
 src/keychain/cms.js                          |  4 ++--
 src/keychain/index.js                        | 18 +++++++++---------
 src/nat-manager.js                           |  2 +-
 src/peer-routing.js                          |  2 +-
 src/peer-store/address-book.js               |  2 +-
 src/peer-store/metadata-book.js              |  3 ++-
 src/peer-store/persistent/index.js           | 10 +++++-----
 src/pnet/crypto.js                           |  2 +-
 src/pnet/key-generator.js                    |  2 +-
 src/transport-manager.js                     |  2 +-
 src/upgrader.js                              | 18 +++++++++---------
 test/content-routing/content-routing.node.js |  4 ++--
 test/dialing/dial-request.spec.js            |  6 +++---
 test/dialing/direct.node.js                  |  2 +-
 test/dialing/direct.spec.js                  |  2 +-
 test/keychain/keychain.spec.js               |  8 ++++----
 test/peer-routing/peer-routing.node.js       |  4 ++--
 test/peer-store/address-book.spec.js         | 18 +++++++++---------
 test/peer-store/key-book.spec.js             |  4 ++--
 test/peer-store/metadata-book.spec.js        | 16 ++++++++--------
 test/transports/transport-manager.spec.js    |  2 +-
 test/utils/mockConnection.js                 |  4 ++--
 39 files changed, 104 insertions(+), 102 deletions(-)

diff --git a/examples/libp2p-in-the-browser/test.js b/examples/libp2p-in-the-browser/test.js
index 574e5739b2..97e65224bd 100644
--- a/examples/libp2p-in-the-browser/test.js
+++ b/examples/libp2p-in-the-browser/test.js
@@ -38,7 +38,7 @@ async function run() {
         )
         await browser.close();
 
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         console.error(err)
         process.exit(1)
       } finally {
diff --git a/examples/transports/3.js b/examples/transports/3.js
index 90fc1b1768..b3843b15af 100644
--- a/examples/transports/3.js
+++ b/examples/transports/3.js
@@ -81,7 +81,7 @@ function print ({ stream }) {
   // node 3 (listening WebSockets) can dial node 1 (TCP)
   try {
     await node3.dialProtocol(node1.peerId, '/print')
-  } catch (err) {
+  } catch (/** @type {any} */ err) {
     console.log('node 3 failed to dial to node 1 with:', err.message)
   }
 })();
diff --git a/examples/utils.js b/examples/utils.js
index aec6df5418..95b509ceb4 100644
--- a/examples/utils.js
+++ b/examples/utils.js
@@ -9,7 +9,7 @@ async function isExecutable (command) {
     await fs.access(command, fs.constants.X_OK)
 
     return true
-  } catch (err) {
+  } catch (/** @type {any} */ err) {
     if (err.code === 'ENOENT') {
       return isExecutable(await which(command))
     }
@@ -49,7 +49,7 @@ async function waitForOutput (expectedOutput, command, args = [], opts = {}) {
 
   try {
     await proc
-  } catch (err) {
+  } catch (/** @type {any} */ err) {
     if (!err.killed) {
       throw err
     }
diff --git a/examples/webrtc-direct/test.js b/examples/webrtc-direct/test.js
index 6f990073aa..d6603f36dd 100644
--- a/examples/webrtc-direct/test.js
+++ b/examples/webrtc-direct/test.js
@@ -72,7 +72,7 @@ async function test () {
                   { timeout: 10000 }
                 )
                 await browser.close();
-            } catch (err) {
+            } catch (/** @type {any} */ err) {
                 console.error(err)
                 process.exit(1)
             } finally {
diff --git a/package.json b/package.json
index 1c653f2996..919d16a8b1 100644
--- a/package.json
+++ b/package.json
@@ -74,7 +74,8 @@
     "extends": "ipfs",
     "ignorePatterns": [
       "!.aegir.js",
-      "test/ts-use"
+      "test/ts-use",
+      "*.d.ts"
     ]
   },
   "dependencies": {
@@ -137,7 +138,7 @@
     "@types/node": "^16.0.1",
     "@types/node-forge": "^0.10.1",
     "@types/varint": "^6.0.0",
-    "aegir": "^33.1.1",
+    "aegir": "^36.0.0",
     "buffer": "^6.0.3",
     "datastore-core": "^6.0.7",
     "delay": "^5.0.0",
diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js
index c27bfc5631..1c238f54a2 100644
--- a/src/circuit/auto-relay.js
+++ b/src/circuit/auto-relay.js
@@ -116,7 +116,7 @@ class AutoRelay {
         this._peerStore.metadataBook.set(peerId, HOP_METADATA_KEY, uint8ArrayFromString(HOP_METADATA_VALUE))
         await this._addListenRelay(connection, id)
       }
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       this._onError(err)
     }
   }
@@ -169,7 +169,7 @@ class AutoRelay {
     try {
       await this._transportManager.listen([new Multiaddr(listenAddr)])
       // Announce multiaddrs will update on listen success by TransportManager event being triggered
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       this._onError(err)
       this._listenRelays.delete(id)
     }
@@ -267,7 +267,7 @@ class AutoRelay {
           return
         }
       }
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       this._onError(err)
     }
   }
@@ -279,7 +279,7 @@ class AutoRelay {
     try {
       const connection = await this._libp2p.dial(peerId)
       await this._addListenRelay(connection, peerId.toB58String())
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       this._onError(err, `could not connect and listen on known hop relay ${peerId.toB58String()}`)
     }
   }
diff --git a/src/circuit/circuit/hop.js b/src/circuit/circuit/hop.js
index b2ce72de0a..73d9b1f855 100644
--- a/src/circuit/circuit/hop.js
+++ b/src/circuit/circuit/hop.js
@@ -54,7 +54,7 @@ async function handleHop ({
   // Validate the HOP request has the required input
   try {
     validateAddrs(request, streamHandler)
-  } catch (err) {
+  } catch (/** @type {any} */ err) {
     return log.error('invalid hop request via peer %s', connection.remotePeer.toB58String(), err)
   }
 
@@ -93,7 +93,7 @@ async function handleHop ({
       connection: destinationConnection,
       request: stopRequest
     })
-  } catch (err) {
+  } catch (/** @type {any} */ err) {
     return log.error(err)
   }
 
diff --git a/src/circuit/circuit/stop.js b/src/circuit/circuit/stop.js
index 6fa92a07c0..8efd00f892 100644
--- a/src/circuit/circuit/stop.js
+++ b/src/circuit/circuit/stop.js
@@ -34,7 +34,7 @@ module.exports.handleStop = function handleStop ({
   // Validate the STOP request has the required input
   try {
     validateAddrs(request, streamHandler)
-  } catch (err) {
+  } catch (/** @type {any} */ err) {
     return log.error('invalid stop request via peer %s', connection.remotePeer.toB58String(), err)
   }
 
diff --git a/src/circuit/circuit/utils.js b/src/circuit/circuit/utils.js
index a69dcb50ba..624d0ba490 100644
--- a/src/circuit/circuit/utils.js
+++ b/src/circuit/circuit/utils.js
@@ -34,7 +34,7 @@ function validateAddrs (msg, streamHandler) {
         return new Multiaddr(addr)
       })
     }
-  } catch (err) {
+  } catch (/** @type {any} */ err) {
     writeResponse(streamHandler, msg.type === CircuitRelay.Type.HOP
       ? CircuitRelay.Status.HOP_DST_MULTIADDR_INVALID
       : CircuitRelay.Status.STOP_DST_MULTIADDR_INVALID)
@@ -47,7 +47,7 @@ function validateAddrs (msg, streamHandler) {
         return new Multiaddr(addr)
       })
     }
-  } catch (err) {
+  } catch (/** @type {any} */ err) {
     writeResponse(streamHandler, msg.type === CircuitRelay.Type.HOP
       ? CircuitRelay.Status.HOP_SRC_MULTIADDR_INVALID
       : CircuitRelay.Status.STOP_SRC_MULTIADDR_INVALID)
diff --git a/src/circuit/index.js b/src/circuit/index.js
index da8e879e52..4d180b6edd 100644
--- a/src/circuit/index.js
+++ b/src/circuit/index.js
@@ -87,7 +87,7 @@ class Relay {
     try {
       const cid = await namespaceToCid(RELAY_RENDEZVOUS_NS)
       await this._libp2p.contentRouting.provide(cid)
-    } catch (err) {
+    } catch (/** @type {any} */ 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
diff --git a/src/circuit/transport.js b/src/circuit/transport.js
index 1dc284cab4..5d0ad50d63 100644
--- a/src/circuit/transport.js
+++ b/src/circuit/transport.js
@@ -171,7 +171,7 @@ class Circuit {
       log('new outbound connection %s', maConn.remoteAddr)
 
       return this._upgrader.upgradeOutbound(maConn)
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       log.error('Circuit relay dial failed', err)
       disconnectOnFailure && await relayConnection.close()
       throw err
diff --git a/src/connection-manager/index.js b/src/connection-manager/index.js
index d63ebf7581..802c9fe65d 100644
--- a/src/connection-manager/index.js
+++ b/src/connection-manager/index.js
@@ -350,7 +350,7 @@ class ConnectionManager extends EventEmitter {
           if (!this._started) {
             return
           }
-        } catch (err) {
+        } catch (/** @type {any} */ err) {
           log.error('could not connect to peerStore stored peer', err)
         }
       }
diff --git a/src/dialer/index.js b/src/dialer/index.js
index 65afe266e7..630e9c50b2 100644
--- a/src/dialer/index.js
+++ b/src/dialer/index.js
@@ -95,7 +95,7 @@ class Dialer {
     for (const dial of this._pendingDials.values()) {
       try {
         dial.controller.abort()
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         log.error(err)
       }
     }
@@ -129,7 +129,7 @@ class Dialer {
       const connection = await pendingDial.promise
       log('dial succeeded to %s', dialTarget.id)
       return connection
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       // Error is a timeout
       if (pendingDial.controller.signal.aborted) {
         err.code = codes.ERR_TIMEOUT
diff --git a/src/get-peer.js b/src/get-peer.js
index a0de9ef8b6..afad64df06 100644
--- a/src/get-peer.js
+++ b/src/get-peer.js
@@ -32,7 +32,7 @@ function getPeer (peer) {
 
     try {
       peer = PeerId.createFromB58String(idStr)
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       throw errCode(
         new Error(`${peer} is not a valid peer type`),
         codes.ERR_INVALID_MULTIADDR
diff --git a/src/identify/index.js b/src/identify/index.js
index b198b45041..d08d11963b 100644
--- a/src/identify/index.js
+++ b/src/identify/index.js
@@ -124,7 +124,7 @@ class IdentifyService {
           stream,
           consume
         )
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         // Just log errors
         log.error('could not push identify update to peer', err)
       }
@@ -182,7 +182,7 @@ class IdentifyService {
     let message
     try {
       message = Message.Identify.decode(data)
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       throw errCode(err, codes.ERR_INVALID_MESSAGE)
     }
 
@@ -211,14 +211,14 @@ class IdentifyService {
         this.peerStore.metadataBook.set(id, 'ProtocolVersion', uint8ArrayFromString(message.protocolVersion))
         return
       }
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       log('received invalid envelope, discard it and fallback to listenAddrs is available', err)
     }
 
     // LEGACY: Update peers data in PeerStore
     try {
       this.peerStore.addressBook.set(id, listenAddrs.map((addr) => new Multiaddr(addr)))
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       log.error('received invalid addrs', err)
     }
 
@@ -287,7 +287,7 @@ class IdentifyService {
         stream,
         consume
       )
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       log.error('could not respond to identify request', err)
     }
   }
@@ -313,7 +313,7 @@ class IdentifyService {
         collect
       )
       message = Message.Identify.decode(data)
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       return log.error('received invalid message', err)
     }
 
@@ -325,7 +325,7 @@ class IdentifyService {
         this.peerStore.protoBook.set(id, message.protocols)
         return
       }
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       log('received invalid envelope, discard it and fallback to listenAddrs is available', err)
     }
 
@@ -333,7 +333,7 @@ class IdentifyService {
     try {
       this.peerStore.addressBook.set(id,
         message.listenAddrs.map((addr) => new Multiaddr(addr)))
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       log.error('received invalid addrs', err)
     }
 
diff --git a/src/index.js b/src/index.js
index 3bd1fbfe77..b06393bd3d 100644
--- a/src/index.js
+++ b/src/index.js
@@ -363,7 +363,7 @@ class Libp2p extends EventEmitter {
       await this._onStarting()
       await this._onDidStart()
       log('libp2p has started')
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       this.emit('error', err)
       log.error('An error occurred starting libp2p', err)
       await this.stop()
@@ -408,7 +408,7 @@ class Libp2p extends EventEmitter {
 
       ping.unmount(this)
       this.dialer.destroy()
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       if (err) {
         log.error(err)
         this.emit('error', err)
@@ -431,7 +431,7 @@ class Libp2p extends EventEmitter {
 
     try {
       await this.keychain.findKeyByName('self')
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       await this.keychain.importPeer('self', this.peerId)
     }
   }
@@ -698,7 +698,7 @@ class Libp2p extends EventEmitter {
         log('connecting to discovered peer %s', peerId.toB58String())
         try {
           await this.dialer.connectToPeer(peerId)
-        } catch (err) {
+        } catch (/** @type {any} */ err) {
           log.error(`could not connect to discovered peer ${peerId.toB58String()} with ${err}`)
         }
       }
diff --git a/src/insecure/plaintext.js b/src/insecure/plaintext.js
index 2ea0458315..99921e5376 100644
--- a/src/insecure/plaintext.js
+++ b/src/insecure/plaintext.js
@@ -55,7 +55,7 @@ async function encrypt (localId, conn, remoteId) {
   let peerId
   try {
     peerId = await PeerId.createFromPubKey(id.pubkey.Data)
-  } catch (err) {
+  } catch (/** @type {any} */ err) {
     log.error(err)
     throw new InvalidCryptoExchangeError('Remote did not provide its public key')
   }
diff --git a/src/keychain/cms.js b/src/keychain/cms.js
index 286e75a4ae..e9361882df 100644
--- a/src/keychain/cms.js
+++ b/src/keychain/cms.js
@@ -90,7 +90,7 @@ class CMS {
       const obj = forge.asn1.fromDer(buf)
       // @ts-ignore not defined
       cms = forge.pkcs7.messageFromAsn1(obj)
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       throw errcode(new Error('Invalid CMS: ' + err.message), 'ERR_INVALID_CMS')
     }
 
@@ -114,7 +114,7 @@ class CMS {
       try {
         const key = await this.keychain.findKeyById(recipient.keyId)
         if (key) return true
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         return false
       }
       return false
diff --git a/src/keychain/index.js b/src/keychain/index.js
index 20d974de36..b25be5a854 100644
--- a/src/keychain/index.js
+++ b/src/keychain/index.js
@@ -248,7 +248,7 @@ class Keychain {
       batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo)))
 
       await batch.commit()
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       return throwDelayed(err)
     }
 
@@ -284,7 +284,7 @@ class Keychain {
     try {
       const keys = await this.listKeys()
       return keys.find((k) => k.id === id)
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       return throwDelayed(err)
     }
   }
@@ -304,7 +304,7 @@ class Keychain {
     try {
       const res = await this.store.get(dsname)
       return JSON.parse(uint8ArrayToString(res))
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       return throwDelayed(errcode(new Error(`Key '${name}' does not exist. ${err.message}`), 'ERR_KEY_NOT_FOUND'))
     }
   }
@@ -365,7 +365,7 @@ class Keychain {
       batch.delete(oldInfoName)
       await batch.commit()
       return keyInfo
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       return throwDelayed(err)
     }
   }
@@ -393,7 +393,7 @@ class Keychain {
       const dek = privates.get(this).dek
       const privateKey = await crypto.keys.import(pem, dek)
       return privateKey.export(password)
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       return throwDelayed(err)
     }
   }
@@ -421,7 +421,7 @@ class Keychain {
     let privateKey
     try {
       privateKey = await crypto.keys.import(pem, password)
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       return throwDelayed(errcode(new Error('Cannot read the key, most likely the password is wrong'), 'ERR_CANNOT_READ_KEY'))
     }
 
@@ -431,7 +431,7 @@ class Keychain {
       /** @type {string} */
       const dek = privates.get(this).dek
       pem = await privateKey.export(dek)
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       return throwDelayed(err)
     }
 
@@ -482,7 +482,7 @@ class Keychain {
       batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo)))
       await batch.commit()
       return keyInfo
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       return throwDelayed(err)
     }
   }
@@ -502,7 +502,7 @@ class Keychain {
       const dsname = DsName(name)
       const res = await this.store.get(dsname)
       return uint8ArrayToString(res)
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       return throwDelayed(errcode(new Error(`Key '${name}' does not exist. ${err.message}`), 'ERR_KEY_NOT_FOUND'))
     }
   }
diff --git a/src/nat-manager.js b/src/nat-manager.js
index c5e45cecc0..247401c7b8 100644
--- a/src/nat-manager.js
+++ b/src/nat-manager.js
@@ -188,7 +188,7 @@ class NatManager {
     try {
       await this._client.destroy()
       this._client = null
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       log.error(err)
     }
   }
diff --git a/src/peer-routing.js b/src/peer-routing.js
index aaf0674530..bc22f8e4aa 100644
--- a/src/peer-routing.js
+++ b/src/peer-routing.js
@@ -79,7 +79,7 @@ class PeerRouting {
     try {
       // nb getClosestPeers adds the addresses to the address book
       await drain(this.getClosestPeers(this._peerId.id))
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       log.error(err)
     }
   }
diff --git a/src/peer-store/address-book.js b/src/peer-store/address-book.js
index eda90bdcc3..4e28eca923 100644
--- a/src/peer-store/address-book.js
+++ b/src/peer-store/address-book.js
@@ -83,7 +83,7 @@ class AddressBook extends Book {
     let peerRecord
     try {
       peerRecord = PeerRecord.createFromProtobuf(envelope.payload)
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       log.error('invalid peer record received')
       return false
     }
diff --git a/src/peer-store/metadata-book.js b/src/peer-store/metadata-book.js
index c2769a0aec..3eb5b38ed9 100644
--- a/src/peer-store/metadata-book.js
+++ b/src/peer-store/metadata-book.js
@@ -80,10 +80,11 @@ class MetadataBook extends Book {
   /**
    * Set data into the datastructure
    *
-   * @override
    * @param {PeerId} peerId
    * @param {string} key
    * @param {Uint8Array} value
+   * @param {object} [opts]
+   * @param {boolean} [opts.emit]
    */
   _setValue (peerId, key, value, { emit = true } = {}) {
     const id = peerId.toB58String()
diff --git a/src/peer-store/persistent/index.js b/src/peer-store/persistent/index.js
index 40c81f6104..5655d20743 100644
--- a/src/peer-store/persistent/index.js
+++ b/src/peer-store/persistent/index.js
@@ -249,7 +249,7 @@ class PersistentPeerStore extends PeerStore {
       }).finish()
 
       batch.put(key, encodedData)
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       log.error(err)
     }
   }
@@ -275,7 +275,7 @@ class PersistentPeerStore extends PeerStore {
       const encodedData = peerId.marshalPubKey()
 
       batch.put(key, encodedData)
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       log.error(err)
     }
   }
@@ -302,7 +302,7 @@ class PersistentPeerStore extends PeerStore {
           batch.delete(key)
         }
       })
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       log.error(err)
     }
   }
@@ -330,7 +330,7 @@ class PersistentPeerStore extends PeerStore {
       const encodedData = Protocols.encode({ protocols }).finish()
 
       batch.put(key, encodedData)
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       log.error(err)
     }
   }
@@ -399,7 +399,7 @@ class PersistentPeerStore extends PeerStore {
         default:
           log('invalid data persisted for: ', key.toString())
       }
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       log.error(err)
     }
   }
diff --git a/src/pnet/crypto.js b/src/pnet/crypto.js
index 6ef946b6b3..eb9cb22316 100644
--- a/src/pnet/crypto.js
+++ b/src/pnet/crypto.js
@@ -77,7 +77,7 @@ module.exports.decodeV1PSK = (pskBuffer) => {
       codecName: codec,
       psk: psk
     }
-  } catch (err) {
+  } catch (/** @type {any} */ err) {
     log.error(err)
     throw new Error(Errors.INVALID_PSK)
   }
diff --git a/src/pnet/key-generator.js b/src/pnet/key-generator.js
index 4bd3bab902..ad94b14083 100644
--- a/src/pnet/key-generator.js
+++ b/src/pnet/key-generator.js
@@ -28,6 +28,6 @@ try {
     // @ts-ignore
     generate(process.stdout)
   }
-} catch (error) {
+} catch (/** @type {any} */ error) {
 
 }
diff --git a/src/transport-manager.js b/src/transport-manager.js
index 1993edf80c..8a7303ef3d 100644
--- a/src/transport-manager.js
+++ b/src/transport-manager.js
@@ -109,7 +109,7 @@ class TransportManager {
 
     try {
       return await transport.dial(ma, options)
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       if (!err.code) err.code = codes.ERR_TRANSPORT_DIAL_FAILED
       throw err
     }
diff --git a/src/upgrader.js b/src/upgrader.js
index 8ee6c65412..8b07be9731 100644
--- a/src/upgrader.js
+++ b/src/upgrader.js
@@ -106,7 +106,7 @@ class Upgrader {
       } else {
         upgradedConn = encryptedConn
       }
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       log.error('Failed to upgrade inbound connection', err)
       await maConn.close(err)
       throw err
@@ -181,7 +181,7 @@ class Upgrader {
       } else {
         upgradedConn = encryptedConn
       }
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       log.error('Failed to upgrade outbound connection', err)
       await maConn.close(err)
       throw err
@@ -245,7 +245,7 @@ class Upgrader {
             if (this.metrics) this.metrics.trackStream({ stream, remotePeer, protocol })
             connection.addStream(muxedStream, { protocol })
             this._onStream({ connection, stream: { ...muxedStream, ...stream }, protocol })
-          } catch (err) {
+          } catch (/** @type {any} */ err) {
             log.error(err)
           }
         },
@@ -263,7 +263,7 @@ class Upgrader {
           const { stream, protocol } = await mss.select(protocols)
           if (this.metrics) this.metrics.trackStream({ stream, remotePeer, protocol })
           return { stream: { ...muxedStream, ...stream }, protocol }
-        } catch (err) {
+        } catch (/** @type {any} */ err) {
           log.error('could not create new stream', err)
           throw errCode(err, codes.ERR_UNSUPPORTED_PROTOCOL)
         }
@@ -283,7 +283,7 @@ class Upgrader {
               if (connection.stat.status === 'open') {
                 await connection.close()
               }
-            } catch (err) {
+            } catch (/** @type {any} */ err) {
               log.error(err)
             } finally {
               this.onConnectionEnd(connection)
@@ -371,7 +371,7 @@ class Upgrader {
         ...await crypto.secureInbound(localPeer, stream),
         protocol
       }
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       throw errCode(err, codes.ERR_ENCRYPTION_FAILED)
     }
   }
@@ -406,7 +406,7 @@ class Upgrader {
         ...await crypto.secureOutbound(localPeer, stream, remotePeerId),
         protocol
       }
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       throw errCode(err, codes.ERR_ENCRYPTION_FAILED)
     }
   }
@@ -430,7 +430,7 @@ class Upgrader {
       log('%s selected as muxer protocol', protocol)
       const Muxer = muxers.get(protocol)
       return { stream, Muxer }
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       throw errCode(err, codes.ERR_MUXER_UNAVAILABLE)
     }
   }
@@ -453,7 +453,7 @@ class Upgrader {
       const { stream, protocol } = await listener.handle(protocols)
       const Muxer = muxers.get(protocol)
       return { stream, Muxer }
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       throw errCode(err, codes.ERR_MUXER_UNAVAILABLE)
     }
   }
diff --git a/test/content-routing/content-routing.node.js b/test/content-routing/content-routing.node.js
index 0a8db15b64..7206b19906 100644
--- a/test/content-routing/content-routing.node.js
+++ b/test/content-routing/content-routing.node.js
@@ -34,7 +34,7 @@ describe('content-routing', () => {
       try {
         for await (const _ of node.contentRouting.findProviders('a cid')) {} // eslint-disable-line
         throw new Error('.findProviders should return an error')
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err).to.exist()
         expect(err.code).to.equal('NO_ROUTERS_AVAILABLE')
       }
@@ -238,7 +238,7 @@ describe('content-routing', () => {
       try {
         for await (const _ of node.contentRouting.findProviders(cid)) { } // eslint-disable-line
         throw new Error('should handle errors when finding providers')
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err).to.exist()
       }
 
diff --git a/test/dialing/dial-request.spec.js b/test/dialing/dial-request.spec.js
index fd56620a00..2c72fb7b87 100644
--- a/test/dialing/dial-request.spec.js
+++ b/test/dialing/dial-request.spec.js
@@ -125,7 +125,7 @@ describe('Dial Request', () => {
     try {
       await dialRequest.run({ signal: controller.signal })
       expect.fail('Should have thrown')
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       expect(err).to.be.an.instanceof(AggregateError)
     }
 
@@ -162,7 +162,7 @@ describe('Dial Request', () => {
     try {
       await dialRequest.run({ signal: controller.signal })
       expect.fail('Should have thrown')
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       expect(err).to.be.an.instanceof(AggregateError)
     }
 
@@ -212,7 +212,7 @@ describe('Dial Request', () => {
       setTimeout(() => controller.abort(), 100)
       await dialRequest.run({ signal: controller.signal })
       expect.fail('dial should have failed')
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       expect(err).to.be.an.instanceof(AggregateError)
     }
 
diff --git a/test/dialing/direct.node.js b/test/dialing/direct.node.js
index 57a8008492..6db0cb6b90 100644
--- a/test/dialing/direct.node.js
+++ b/test/dialing/direct.node.js
@@ -277,7 +277,7 @@ describe('Dialing (direct, TCP)', () => {
 
       try {
         await libp2p.dial(remoteLibp2p.transportManager.getAddrs()[0])
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err).to.have.property('code', ErrorCodes.ERR_INVALID_MULTIADDR)
         return
       }
diff --git a/test/dialing/direct.spec.js b/test/dialing/direct.spec.js
index 7f56b91dbb..1617322b8a 100644
--- a/test/dialing/direct.spec.js
+++ b/test/dialing/direct.spec.js
@@ -304,7 +304,7 @@ describe('Dialing (direct, WebSockets)', () => {
       dialer.destroy()
       await dialPromise
       expect.fail('should have failed')
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       expect(err).to.be.an.instanceof(AggregateError)
       expect(dialer._pendingDials.size).to.equal(0) // 1 dial request
     }
diff --git a/test/keychain/keychain.spec.js b/test/keychain/keychain.spec.js
index 032a1b11b7..0404ebf03e 100644
--- a/test/keychain/keychain.spec.js
+++ b/test/keychain/keychain.spec.js
@@ -519,7 +519,7 @@ describe('keychain', () => {
     it('should validate newPass is a string', async () => {
       try {
         await kc.rotateKeychainPass(oldPass, 1234567890)
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err).to.exist()
       }
     })
@@ -527,7 +527,7 @@ describe('keychain', () => {
     it('should validate oldPass is a string', async () => {
       try {
         await kc.rotateKeychainPass(1234, 'newInsecurePassword1')
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err).to.exist()
       }
     })
@@ -535,7 +535,7 @@ describe('keychain', () => {
     it('should validate newPass is at least 20 characters', async () => {
       try {
         await kc.rotateKeychainPass(oldPass, 'not20Chars')
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err).to.exist()
       }
     })
@@ -586,7 +586,7 @@ describe('libp2p.keychain', () => {
 
     try {
       await libp2p.keychain.createKey('keyName', 'rsa', 2048)
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       expect(err).to.exist()
       return
     }
diff --git a/test/peer-routing/peer-routing.node.js b/test/peer-routing/peer-routing.node.js
index 9cb9ca0fb1..fd76ee792d 100644
--- a/test/peer-routing/peer-routing.node.js
+++ b/test/peer-routing/peer-routing.node.js
@@ -43,7 +43,7 @@ describe('peer-routing', () => {
       try {
         for await (const _ of node.peerRouting.getClosestPeers('a cid')) { } // eslint-disable-line
         throw new Error('.getClosestPeers should return an error')
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err).to.exist()
         expect(err.code).to.equal('NO_ROUTERS_AVAILABLE')
       }
@@ -275,7 +275,7 @@ describe('peer-routing', () => {
       try {
         for await (const _ of node.peerRouting.getClosestPeers(peerId.id)) { } // eslint-disable-line
         throw new Error('should handle errors when getting the closest peers')
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err).to.exist()
       }
 
diff --git a/test/peer-store/address-book.spec.js b/test/peer-store/address-book.spec.js
index ea71ed0b32..929a45fbff 100644
--- a/test/peer-store/address-book.spec.js
+++ b/test/peer-store/address-book.spec.js
@@ -45,7 +45,7 @@ describe('addressBook', () => {
     it('throwns invalid parameters error if invalid PeerId is provided', () => {
       try {
         ab.set('invalid peerId')
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
         return
       }
@@ -55,7 +55,7 @@ describe('addressBook', () => {
     it('throwns invalid parameters error if no addresses provided', () => {
       try {
         ab.set(peerId)
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
         return
       }
@@ -65,7 +65,7 @@ describe('addressBook', () => {
     it('throwns invalid parameters error if invalid multiaddrs are provided', () => {
       try {
         ab.set(peerId, ['invalid multiaddr'])
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
         return
       }
@@ -159,7 +159,7 @@ describe('addressBook', () => {
     it('throwns invalid parameters error if invalid PeerId is provided', () => {
       try {
         ab.add('invalid peerId')
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
         return
       }
@@ -169,7 +169,7 @@ describe('addressBook', () => {
     it('throwns invalid parameters error if no addresses provided', () => {
       try {
         ab.add(peerId)
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
         return
       }
@@ -179,7 +179,7 @@ describe('addressBook', () => {
     it('throwns invalid parameters error if invalid multiaddrs are provided', () => {
       try {
         ab.add(peerId, ['invalid multiaddr'])
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
         return
       }
@@ -308,7 +308,7 @@ describe('addressBook', () => {
     it('throwns invalid parameters error if invalid PeerId is provided', () => {
       try {
         ab.get('invalid peerId')
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
         return
       }
@@ -343,7 +343,7 @@ describe('addressBook', () => {
     it('throwns invalid parameters error if invalid PeerId is provided', () => {
       try {
         ab.getMultiaddrsForPeer('invalid peerId')
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
         return
       }
@@ -391,7 +391,7 @@ describe('addressBook', () => {
     it('throwns invalid parameters error if invalid PeerId is provided', () => {
       try {
         ab.delete('invalid peerId')
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
         return
       }
diff --git a/test/peer-store/key-book.spec.js b/test/peer-store/key-book.spec.js
index 4e06ea2f4d..8c439a9051 100644
--- a/test/peer-store/key-book.spec.js
+++ b/test/peer-store/key-book.spec.js
@@ -23,7 +23,7 @@ describe('keyBook', () => {
   it('throws invalid parameters error if invalid PeerId is provided in set', () => {
     try {
       kb.set('invalid peerId')
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
       return
     }
@@ -33,7 +33,7 @@ describe('keyBook', () => {
   it('throws invalid parameters error if invalid PeerId is provided in get', () => {
     try {
       kb.get('invalid peerId')
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
       return
     }
diff --git a/test/peer-store/metadata-book.spec.js b/test/peer-store/metadata-book.spec.js
index a1b0d2a105..478dfff5dd 100644
--- a/test/peer-store/metadata-book.spec.js
+++ b/test/peer-store/metadata-book.spec.js
@@ -34,7 +34,7 @@ describe('metadataBook', () => {
     it('throws invalid parameters error if invalid PeerId is provided', () => {
       try {
         mb.set('invalid peerId')
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
         return
       }
@@ -44,7 +44,7 @@ describe('metadataBook', () => {
     it('throws invalid parameters error if no key provided', () => {
       try {
         mb.set(peerId)
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
         return
       }
@@ -54,7 +54,7 @@ describe('metadataBook', () => {
     it('throws invalid parameters error if no value provided', () => {
       try {
         mb.set(peerId, 'location')
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
         return
       }
@@ -64,7 +64,7 @@ describe('metadataBook', () => {
     it('throws invalid parameters error if value is not a buffer', () => {
       try {
         mb.set(peerId, 'location', 'mars')
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
         return
       }
@@ -163,7 +163,7 @@ describe('metadataBook', () => {
     it('throws invalid parameters error if invalid PeerId is provided', () => {
       try {
         mb.get('invalid peerId')
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
         return
       }
@@ -199,7 +199,7 @@ describe('metadataBook', () => {
     it('throws invalid parameters error if invalid PeerId is provided', () => {
       try {
         mb.getValue('invalid peerId')
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
         return
       }
@@ -248,7 +248,7 @@ describe('metadataBook', () => {
     it('throwns invalid parameters error if invalid PeerId is provided', () => {
       try {
         mb.delete('invalid peerId')
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
         return
       }
@@ -305,7 +305,7 @@ describe('metadataBook', () => {
     it('throws invalid parameters error if invalid PeerId is provided', () => {
       try {
         mb.deleteValue('invalid peerId')
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
         return
       }
diff --git a/test/transports/transport-manager.spec.js b/test/transports/transport-manager.spec.js
index 1fb5be1ed6..23006cdd6b 100644
--- a/test/transports/transport-manager.spec.js
+++ b/test/transports/transport-manager.spec.js
@@ -201,7 +201,7 @@ describe('libp2p.transportManager (dial only)', () => {
 
     try {
       await libp2p.start()
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       expect(err).to.exist()
       expect(err.code).to.equal(ErrorCodes.ERR_NO_VALID_ADDRESSES)
       return
diff --git a/test/utils/mockConnection.js b/test/utils/mockConnection.js
index 66c659fb16..6c1439aad8 100644
--- a/test/utils/mockConnection.js
+++ b/test/utils/mockConnection.js
@@ -108,7 +108,7 @@ function createConnection ({
         // Need to be able to notify a peer of this this._onStream({ connection, stream, protocol })
         const handler = protocols.get(protocol)
         handler({ connection, stream, protocol })
-      } catch (err) {
+      } catch (/** @type {any} */ err) {
         // Do nothing
       }
     },
@@ -124,7 +124,7 @@ function createConnection ({
     try {
       const { stream, protocol } = await mss.select(protocols)
       return { stream: { ...muxedStream, ...stream }, protocol }
-    } catch (err) {
+    } catch (/** @type {any} */ err) {
       throw errCode(err, codes.ERR_UNSUPPORTED_PROTOCOL)
     }
   }

From 443a102528f9a3225786293ab071f49d9da8916e Mon Sep 17 00:00:00 2001
From: Alan Smithee 
Date: Sun, 21 Nov 2021 23:18:45 +0100
Subject: [PATCH 260/447] docs: minor corrections to discovery-mechanisms
 readme (#1030)

---
 examples/discovery-mechanisms/README.md | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/examples/discovery-mechanisms/README.md b/examples/discovery-mechanisms/README.md
index 2db83431dd..6d5c647e55 100644
--- a/examples/discovery-mechanisms/README.md
+++ b/examples/discovery-mechanisms/README.md
@@ -55,7 +55,7 @@ const node = await Libp2p.create({
   peerId,
   addresses: {
     listen: ['/ip4/0.0.0.0/tcp/0']
-  }
+  },
   modules: {
     transport: [ TCP ],
     streamMuxer: [ Mplex ],
@@ -117,7 +117,7 @@ const createNode = () => {
   return Libp2p.create({
     addresses: {
       listen: ['/ip4/0.0.0.0/tcp/0']
-    }
+    },
     modules: {
       transport: [ TCP ],
       streamMuxer: [ Mplex ],
@@ -144,8 +144,13 @@ const [node1, node2] = await Promise.all([
   createNode()
 ])
 
-node1.on('peer:discovery', (peer) => console.log('Discovered:', peer.id.toB58String()))
-node2.on('peer:discovery', (peer) => console.log('Discovered:', peer.id.toB58String()))
+node1.on('peer:discovery', (peer) => console.log('Discovered:', peerId.toB58String()))
+node2.on('peer:discovery', (peer) => console.log('Discovered:', peerId.toB58String()))
+
+await Promise.all([
+  node1.start(),
+  node2.start()
+])
 ```
 
 If you run this example, you will see the other peers being discovered.

From 2f598eba09cff4301474af08196158065e3602d8 Mon Sep 17 00:00:00 2001
From: Alex Potsides 
Date: Thu, 25 Nov 2021 16:32:19 +0000
Subject: [PATCH 261/447] feat: update dht (#1009)

Changes dht creation to use factory function and updates docs

BREAKING CHANGE: libp2p-kad-dht has a new event-based API which is exposed as `_dht`
---
 .github/workflows/main.yml              |  3 +-
 doc/CONFIGURATION.md                    | 76 ++++++++++++-------------
 examples/delegated-routing/package.json |  2 +-
 examples/transports/README.md           |  2 +-
 package.json                            |  5 +-
 src/content-routing/index.js            | 44 +++++++++++---
 src/dht/dht-content-routing.js          | 44 ++++++++++++++
 src/dht/dht-peer-routing.js             | 51 +++++++++++++++++
 src/errors.js                           |  4 +-
 src/index.js                            | 11 +---
 src/nat-manager.js                      |  2 +-
 src/peer-routing.js                     |  3 +-
 test/ts-use/package.json                |  2 +-
 13 files changed, 182 insertions(+), 67 deletions(-)
 create mode 100644 src/dht/dht-content-routing.js
 create mode 100644 src/dht/dht-peer-routing.js

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 69cd85eb3d..8c401645d6 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -17,7 +17,6 @@ jobs:
         node-version: 14
     - run: npm install
     - run: npx aegir lint
-    - uses: gozala/typescript-error-reporter-action@v1.0.8
     - run: npx aegir build
     - run: npx aegir dep-check
     - uses: ipfs/aegir/actions/bundle-size@v32.1.0
@@ -67,4 +66,4 @@ jobs:
     steps:
       - uses: actions/checkout@v2
       - run: npm install
-      - run: cd node_modules/interop-libp2p && yarn && LIBP2P_JS=${GITHUB_WORKSPACE}/src/index.js npx aegir test -t node --bail -- --exit
+      - run: npm run test:interop -- --bail -- --exit
diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md
index b9b24fd04d..7d4523f76f 100644
--- a/doc/CONFIGURATION.md
+++ b/doc/CONFIGURATION.md
@@ -1,37 +1,37 @@
-# 
-
-- [Configuration](#configuration)
-  - [Overview](#overview)
-  - [Modules](#modules)
-    - [Transport](#transport)
-    - [Stream Multiplexing](#stream-multiplexing)
-    - [Connection Encryption](#connection-encryption)
-    - [Peer Discovery](#peer-discovery)
-    - [Content Routing](#content-routing)
-    - [Peer Routing](#peer-routing)
-    - [DHT](#dht)
-    - [Pubsub](#pubsub)
-  - [Customizing libp2p](#customizing-libp2p)
-    - [Examples](#examples)
-      - [Basic setup](#basic-setup)
-      - [Customizing Peer Discovery](#customizing-peer-discovery)
-      - [Setup webrtc transport and discovery](#setup-webrtc-transport-and-discovery)
-      - [Customizing Pubsub](#customizing-pubsub)
-      - [Customizing DHT](#customizing-dht)
-      - [Setup with Content and Peer Routing](#setup-with-content-and-peer-routing)
-      - [Setup with Relay](#setup-with-relay)
-      - [Setup with Auto Relay](#setup-with-auto-relay)
-      - [Setup with Keychain](#setup-with-keychain)
-      - [Configuring Dialing](#configuring-dialing)
-      - [Configuring Connection Manager](#configuring-connection-manager)
-      - [Configuring Transport Manager](#configuring-transport-manager)
-      - [Configuring Metrics](#configuring-metrics)
-      - [Configuring PeerStore](#configuring-peerstore)
-      - [Customizing Transports](#customizing-transports)
-      - [Configuring the NAT Manager](#configuring-the-nat-manager)
-        - [Browser support](#browser-support)
-        - [UPnP and NAT-PMP](#upnp-and-nat-pmp)
-  - [Configuration examples](#configuration-examples)
+#
+
+- [Overview](#overview)
+- [Modules](#modules)
+  - [Transport](#transport)
+  - [Stream Multiplexing](#stream-multiplexing)
+  - [Connection Encryption](#connection-encryption)
+  - [Peer Discovery](#peer-discovery)
+  - [Content Routing](#content-routing)
+  - [Peer Routing](#peer-routing)
+  - [DHT](#dht)
+  - [Pubsub](#pubsub)
+- [Customizing libp2p](#customizing-libp2p)
+  - [Examples](#examples)
+    - [Basic setup](#basic-setup)
+    - [Customizing Peer Discovery](#customizing-peer-discovery)
+    - [Setup webrtc transport and discovery](#setup-webrtc-transport-and-discovery)
+    - [Customizing Pubsub](#customizing-pubsub)
+    - [Customizing DHT](#customizing-dht)
+    - [Setup with Content and Peer Routing](#setup-with-content-and-peer-routing)
+    - [Setup with Relay](#setup-with-relay)
+    - [Setup with Auto Relay](#setup-with-auto-relay)
+    - [Setup with Keychain](#setup-with-keychain)
+    - [Configuring Dialing](#configuring-dialing)
+    - [Configuring Connection Manager](#configuring-connection-manager)
+    - [Configuring Transport Manager](#configuring-transport-manager)
+    - [Configuring Metrics](#configuring-metrics)
+    - [Configuring PeerStore](#configuring-peerstore)
+    - [Customizing Transports](#customizing-transports)
+    - [Configuring the NAT Manager](#configuring-the-nat-manager)
+      - [Browser support](#browser-support)
+      - [UPnP and NAT-PMP](#upnp-and-nat-pmp)
+    - [Configuring protocol name](#configuring-protocol-name)
+- [Configuration examples](#configuration-examples)
 
 ## Overview
 
@@ -374,11 +374,7 @@ const node = await Libp2p.create({
     dht: {                        // The DHT options (and defaults) can be found in its documentation
       kBucketSize: 20,
       enabled: true,              // This flag is required for DHT to run (disabled by default)
-      randomWalk: {
-        enabled: true,            // Allows to disable discovery (enabled by default)
-        interval: 300e3,
-        timeout: 10e3
-      }
+      clientMode: false           // Whether to run the WAN DHT in client or server mode (default: client mode)
     }
   }
 })
@@ -788,7 +784,7 @@ By default under nodejs libp2p will attempt to use [UPnP](https://en.wikipedia.o
 
 #### Configuring protocol name
 
-Changing the protocol name prefix can isolate default public network (IPFS) for custom purposes. 
+Changing the protocol name prefix can isolate default public network (IPFS) for custom purposes.
 
 ```js
 const node = await Libp2p.create({
diff --git a/examples/delegated-routing/package.json b/examples/delegated-routing/package.json
index 164e0d3c54..76a06b46ed 100644
--- a/examples/delegated-routing/package.json
+++ b/examples/delegated-routing/package.json
@@ -7,7 +7,7 @@
     "libp2p": "github:libp2p/js-libp2p#master",
     "libp2p-delegated-content-routing": "~0.2.2",
     "libp2p-delegated-peer-routing": "~0.2.2",
-    "libp2p-kad-dht": "~0.14.12",
+    "libp2p-kad-dht": "^0.26.5",
     "libp2p-mplex": "~0.8.5",
     "libp2p-secio": "~0.11.1",
     "libp2p-webrtc-star": "~0.15.8",
diff --git a/examples/transports/README.md b/examples/transports/README.md
index 8c9d23b908..18f5975bc7 100644
--- a/examples/transports/README.md
+++ b/examples/transports/README.md
@@ -91,7 +91,7 @@ const concat = require('it-concat')
 const MPLEX = require('libp2p-mplex')
 ```
 
-We are going to reuse the `createNode` function from step 1, but this time add a stream multiplexer from `libp2p-mplex`. 
+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({
diff --git a/package.json b/package.json
index 919d16a8b1..7febe18590 100644
--- a/package.json
+++ b/package.json
@@ -41,6 +41,7 @@
     "test:node": "aegir test -t node -f \"./test/**/*.{node,spec}.js\"",
     "test:browser": "aegir test -t browser",
     "test:examples": "cd examples && npm run test:all",
+    "test:interop": "LIBP2P_JS=$PWD npx aegir test -t node -f ./node_modules/libp2p-interop/test/*",
     "prepare": "aegir build --no-bundle",
     "release": "aegir release -t node -t browser",
     "release-minor": "aegir release --type minor -t node -t browser",
@@ -142,7 +143,6 @@
     "buffer": "^6.0.3",
     "datastore-core": "^6.0.7",
     "delay": "^5.0.0",
-    "interop-libp2p": "^0.4.0",
     "into-stream": "^7.0.0",
     "ipfs-http-client": "^52.0.2",
     "it-concat": "^2.0.0",
@@ -155,7 +155,8 @@
     "libp2p-floodsub": "^0.27.0",
     "libp2p-gossipsub": "^0.11.0",
     "libp2p-interfaces-compliance-tests": "^1.0.0",
-    "libp2p-kad-dht": "^0.24.2",
+    "libp2p-interop": "^0.5.0",
+    "libp2p-kad-dht": "^0.26.5",
     "libp2p-mdns": "^0.17.0",
     "libp2p-mplex": "^0.10.1",
     "libp2p-tcp": "^0.17.0",
diff --git a/src/content-routing/index.js b/src/content-routing/index.js
index 7fc4b4fb5c..924b987344 100644
--- a/src/content-routing/index.js
+++ b/src/content-routing/index.js
@@ -8,9 +8,10 @@ const {
   requirePeers,
   maybeLimitSource
 } = require('./utils')
-
+const drain = require('it-drain')
 const merge = require('it-merge')
 const { pipe } = require('it-pipe')
+const { DHTContentRouting } = require('../dht/dht-content-routing')
 
 /**
  * @typedef {import('peer-id')} PeerId
@@ -38,7 +39,7 @@ class ContentRouting {
 
     // If we have the dht, add it to the available content routers
     if (this.dht && libp2p._config.dht.enabled) {
-      this.routers.push(this.dht)
+      this.routers.push(new DHTContentRouting(this.dht))
     }
   }
 
@@ -91,12 +92,12 @@ class ContentRouting {
    * @param {number} [options.minPeers] - minimum number of peers required to successfully put
    * @returns {Promise}
    */
-  put (key, value, options) {
+  async put (key, value, options) {
     if (!this.libp2p.isStarted() || !this.dht.isStarted) {
       throw errCode(new Error(messages.NOT_STARTED_YET), codes.DHT_NOT_STARTED)
     }
 
-    return this.dht.put(key, value, options)
+    await drain(this.dht.put(key, value, options))
   }
 
   /**
@@ -108,12 +109,18 @@ class ContentRouting {
    * @param {number} [options.timeout] - optional timeout (default: 60000)
    * @returns {Promise}
    */
-  get (key, options) {
+  async get (key, options) {
     if (!this.libp2p.isStarted() || !this.dht.isStarted) {
       throw errCode(new Error(messages.NOT_STARTED_YET), codes.DHT_NOT_STARTED)
     }
 
-    return this.dht.get(key, options)
+    for await (const event of this.dht.get(key, options)) {
+      if (event.name === 'VALUE') {
+        return { from: event.peerId, val: event.value }
+      }
+    }
+
+    throw errCode(new Error(messages.NOT_FOUND), codes.ERR_NOT_FOUND)
   }
 
   /**
@@ -123,14 +130,33 @@ class ContentRouting {
    * @param {number} nVals
    * @param {Object} [options] - get options
    * @param {number} [options.timeout] - optional timeout (default: 60000)
-   * @returns {Promise}
    */
-  async getMany (key, nVals, options) { // eslint-disable-line require-await
+  async * getMany (key, nVals, options) { // eslint-disable-line require-await
     if (!this.libp2p.isStarted() || !this.dht.isStarted) {
       throw errCode(new Error(messages.NOT_STARTED_YET), codes.DHT_NOT_STARTED)
     }
 
-    return this.dht.getMany(key, nVals, options)
+    if (!nVals) {
+      return
+    }
+
+    let gotValues = 0
+
+    for await (const event of this.dht.get(key, options)) {
+      if (event.name === 'VALUE') {
+        yield { from: event.peerId, val: event.value }
+
+        gotValues++
+
+        if (gotValues === nVals) {
+          break
+        }
+      }
+    }
+
+    if (gotValues === 0) {
+      throw errCode(new Error(messages.NOT_FOUND), codes.ERR_NOT_FOUND)
+    }
   }
 }
 
diff --git a/src/dht/dht-content-routing.js b/src/dht/dht-content-routing.js
new file mode 100644
index 0000000000..ead668f3d4
--- /dev/null
+++ b/src/dht/dht-content-routing.js
@@ -0,0 +1,44 @@
+'use strict'
+
+const drain = require('it-drain')
+
+/**
+ * @typedef {import('peer-id')} PeerId
+ * @typedef {import('libp2p-interfaces/src/content-routing/types').ContentRouting} ContentRoutingModule
+ * @typedef {import('multiformats/cid').CID} CID
+ */
+
+/**
+ * Wrapper class to convert events into returned values
+ *
+ * @implements {ContentRoutingModule}
+ */
+class DHTContentRouting {
+  /**
+   * @param {import('libp2p-kad-dht').DHT} dht
+   */
+  constructor (dht) {
+    this._dht = dht
+  }
+
+  /**
+   * @param {CID} cid
+   */
+  async provide (cid) {
+    await drain(this._dht.provide(cid))
+  }
+
+  /**
+   * @param {CID} cid
+   * @param {*} options
+   */
+  async * findProviders (cid, options) {
+    for await (const event of this._dht.findProviders(cid, options)) {
+      if (event.name === 'PROVIDER') {
+        yield * event.providers
+      }
+    }
+  }
+}
+
+module.exports = { DHTContentRouting }
diff --git a/src/dht/dht-peer-routing.js b/src/dht/dht-peer-routing.js
new file mode 100644
index 0000000000..762abc80fa
--- /dev/null
+++ b/src/dht/dht-peer-routing.js
@@ -0,0 +1,51 @@
+'use strict'
+
+const errCode = require('err-code')
+const { messages, codes } = require('../errors')
+
+/**
+ * @typedef {import('peer-id')} PeerId
+ * @typedef {import('libp2p-interfaces/src/peer-routing/types').PeerRouting} PeerRoutingModule
+ */
+
+/**
+ * Wrapper class to convert events into returned values
+ *
+ * @implements {PeerRoutingModule}
+ */
+class DHTPeerRouting {
+  /**
+   * @param {import('libp2p-kad-dht').DHT} dht
+   */
+  constructor (dht) {
+    this._dht = dht
+  }
+
+  /**
+   * @param {PeerId} peerId
+   * @param {any} options
+   */
+  async findPeer (peerId, options = {}) {
+    for await (const event of this._dht.findPeer(peerId, options)) {
+      if (event.name === 'FINAL_PEER') {
+        return event.peer
+      }
+    }
+
+    throw errCode(new Error(messages.NOT_FOUND), codes.ERR_NOT_FOUND)
+  }
+
+  /**
+   * @param {Uint8Array} key
+   * @param {any} options
+   */
+  async * getClosestPeers (key, options = {}) {
+    for await (const event of this._dht.getClosestPeers(key, options)) {
+      if (event.name === 'PEER_RESPONSE') {
+        yield * event.closer
+      }
+    }
+  }
+}
+
+module.exports = { DHTPeerRouting }
diff --git a/src/errors.js b/src/errors.js
index 5b4d070fb2..efaed29c06 100644
--- a/src/errors.js
+++ b/src/errors.js
@@ -3,7 +3,8 @@
 exports.messages = {
   NOT_STARTED_YET: 'The libp2p node is not started yet',
   DHT_DISABLED: 'DHT is not available',
-  CONN_ENCRYPTION_REQUIRED: 'At least one connection encryption module is required'
+  CONN_ENCRYPTION_REQUIRED: 'At least one connection encryption module is required',
+  NOT_FOUND: 'Not found'
 }
 
 exports.codes = {
@@ -29,6 +30,7 @@ exports.codes = {
   ERR_INVALID_PARAMETERS: 'ERR_INVALID_PARAMETERS',
   ERR_INVALID_PEER: 'ERR_INVALID_PEER',
   ERR_MUXER_UNAVAILABLE: 'ERR_MUXER_UNAVAILABLE',
+  ERR_NOT_FOUND: 'ERR_NOT_FOUND',
   ERR_TIMEOUT: 'ERR_TIMEOUT',
   ERR_TRANSPORT_UNAVAILABLE: 'ERR_TRANSPORT_UNAVAILABLE',
   ERR_TRANSPORT_DIAL_FAILED: 'ERR_TRANSPORT_DIAL_FAILED',
diff --git a/src/index.js b/src/index.js
index b06393bd3d..63edbf6924 100644
--- a/src/index.js
+++ b/src/index.js
@@ -301,14 +301,9 @@ class Libp2p extends EventEmitter {
     // dht provided components (peerRouting, contentRouting, dht)
     if (this._modules.dht) {
       const DHT = this._modules.dht
-      // @ts-ignore Object is not constructable
-      this._dht = new DHT({
+      // @ts-ignore TODO: types need fixing - DHT is an `object` which has no `create` method
+      this._dht = DHT.create({
         libp2p: this,
-        dialer: this.dialer,
-        peerId: this.peerId,
-        peerStore: this.peerStore,
-        registrar: this.registrar,
-        datastore: this.datastore,
         ...this._config.dht
       })
     }
@@ -624,7 +619,7 @@ class Libp2p extends EventEmitter {
 
     // DHT subsystem
     if (this._config.dht.enabled) {
-      this._dht && this._dht.start()
+      this._dht && await this._dht.start()
 
       // TODO: this should be modified once random-walk is used as
       // the other discovery modules
diff --git a/src/nat-manager.js b/src/nat-manager.js
index 247401c7b8..4b0b60dd24 100644
--- a/src/nat-manager.js
+++ b/src/nat-manager.js
@@ -114,7 +114,7 @@ class NatManager {
       const client = this._getClient()
       const publicIp = this._externalIp || await client.externalIp()
 
-      // @ts-ignore isPrivate has no call signatures
+      // @ts-expect-error types are wrong
       if (isPrivateIp(publicIp)) {
         throw new Error(`${publicIp} is private - please set config.nat.externalIp to an externally routable IP or ensure you are not behind a double NAT`)
       }
diff --git a/src/peer-routing.js b/src/peer-routing.js
index bc22f8e4aa..f37ce6638f 100644
--- a/src/peer-routing.js
+++ b/src/peer-routing.js
@@ -21,6 +21,7 @@ const {
   clearDelayedInterval
 // @ts-ignore module with no types
 } = require('set-delayed-interval')
+const { DHTPeerRouting } = require('./dht/dht-peer-routing')
 
 /**
  * @typedef {import('peer-id')} PeerId
@@ -51,7 +52,7 @@ class PeerRouting {
 
     // If we have the dht, add it to the available peer routers
     if (libp2p._dht && libp2p._config.dht.enabled) {
-      this._routers.push(libp2p._dht)
+      this._routers.push(new DHTPeerRouting(libp2p._dht))
     }
 
     this._refreshManagerOptions = libp2p._options.peerRouting.refreshManager
diff --git a/test/ts-use/package.json b/test/ts-use/package.json
index 6445e42fb1..c056526a85 100644
--- a/test/ts-use/package.json
+++ b/test/ts-use/package.json
@@ -10,7 +10,7 @@
     "libp2p-delegated-peer-routing": "^0.10.0",
     "libp2p-gossipsub": "^0.9.0",
     "libp2p-interfaces": "^1.0.1",
-    "libp2p-kad-dht": "^0.23.1",
+    "libp2p-kad-dht": "^0.26.5",
     "libp2p-mplex": "^0.10.4",
     "@chainsafe/libp2p-noise": "^4.1.0",
     "libp2p-record": "^0.10.4",

From a4a2fac41e9ae094308df2776a4715f180d06f68 Mon Sep 17 00:00:00 2001
From: achingbrain 
Date: Thu, 25 Nov 2021 16:48:41 +0000
Subject: [PATCH 262/447] chore: node_modules is not required

---
 tsconfig.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tsconfig.json b/tsconfig.json
index d9083a6a34..47b635e4af 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,5 +1,5 @@
 {
-  "extends": "./node_modules/aegir/src/config/tsconfig.aegir.json",
+  "extends": "aegir/src/config/tsconfig.aegir.json",
   "compilerOptions": {
     "outDir": "dist"
   },

From ee60e1821382a7e65f71c4b3bae384802dbc4f3d Mon Sep 17 00:00:00 2001
From: achingbrain 
Date: Thu, 25 Nov 2021 16:49:46 +0000
Subject: [PATCH 263/447] chore: update contributors

---
 package.json | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/package.json b/package.json
index 7febe18590..1ba2603c08 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "libp2p",
-  "version": "0.33.0",
+  "version": "0.34.0",
   "description": "JavaScript implementation of libp2p, a modular peer to peer network stack",
   "leadMaintainer": "Jacob Heun ",
   "main": "src/index.js",
@@ -188,21 +188,22 @@
     "Richard Littauer ",
     "a1300 ",
     "Ryan Bell ",
+    "Elven ",
     "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ",
-    "Franck Royer ",
     "Thomas Eizinger ",
-    "Giovanni T. Parra ",
-    "acolytec3 <17355484+acolytec3@users.noreply.github.com>",
-    "Elven ",
+    "Robert Kiel ",
     "Andrew Nesbitt ",
     "Samlior ",
+    "acolytec3 <17355484+acolytec3@users.noreply.github.com>",
+    "Franck Royer ",
+    "Giovanni T. Parra ",
     "Didrik Nordström ",
     "RasmusErik Voel Jensen ",
-    "Robert Kiel ",
     "Smite Chow ",
     "Soeren ",
     "Sönke Hahn ",
     "TJKoury ",
+    "TheStarBoys <41286328+TheStarBoys@users.noreply.github.com>",
     "Tiago Alves ",
     "XiaoZhang ",
     "Yusef Napora ",
@@ -212,11 +213,13 @@
     "isan_rivkin ",
     "mayerwin ",
     "mcclure ",
+    "patrickwoodhead <91056047+patrickwoodhead@users.noreply.github.com>",
     "phillmac ",
     "robertkiel ",
     "shresthagrawal <34920931+shresthagrawal@users.noreply.github.com>",
     "swedneck <40505480+swedneck@users.noreply.github.com>",
     "greenSnot ",
+    "Alan Smithee ",
     "Aleksei ",
     "Bernd Strehl ",
     "Chris Bratlien ",

From 5cc5a8749ab60e1810eb5f0ef2f37bbb43e48810 Mon Sep 17 00:00:00 2001
From: achingbrain 
Date: Thu, 25 Nov 2021 16:49:46 +0000
Subject: [PATCH 264/447] chore: release version v0.34.0

---
 CHANGELOG.md | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index ec0b71a3e3..b749a798b2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,22 @@
+# [0.34.0](https://github.com/libp2p/js-libp2p/compare/v0.33.0...v0.34.0) (2021-11-25)
+
+
+### Bug Fixes
+
+* private ip ts compile has no call signatures ([#1020](https://github.com/libp2p/js-libp2p/issues/1020)) ([77d7cb8](https://github.com/libp2p/js-libp2p/commit/77d7cb8f0815f2cdd3bfdfa8b641a7a186fe9520))
+
+
+### Features
+
+* update dht ([#1009](https://github.com/libp2p/js-libp2p/issues/1009)) ([2f598eb](https://github.com/libp2p/js-libp2p/commit/2f598eba09cff4301474af08196158065e3602d8))
+
+
+### BREAKING CHANGES
+
+* libp2p-kad-dht has a new event-based API which is exposed as `_dht`
+
+
+
 # [0.33.0](https://github.com/libp2p/js-libp2p/compare/v0.32.5...v0.33.0) (2021-09-24)
 
 

From 7f2cc4dc4429251b0ab7132b331c69e0dc7108e5 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 26 Nov 2021 14:26:26 +0000
Subject: [PATCH 265/447] chore(deps-dev): bump ipfs-http-client from 52.0.5 to
 54.0.2 (#1035)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 1ba2603c08..81d7c655eb 100644
--- a/package.json
+++ b/package.json
@@ -144,7 +144,7 @@
     "datastore-core": "^6.0.7",
     "delay": "^5.0.0",
     "into-stream": "^7.0.0",
-    "ipfs-http-client": "^52.0.2",
+    "ipfs-http-client": "^54.0.2",
     "it-concat": "^2.0.0",
     "it-pair": "^1.0.0",
     "it-pushable": "^1.4.0",

From eacd7e8f76f94043daa4adf2b5d61d0116d6327a Mon Sep 17 00:00:00 2001
From: Alex Potsides 
Date: Fri, 26 Nov 2021 16:00:47 +0000
Subject: [PATCH 266/447] chore: update deps (#1038)

---
 examples/libp2p-in-the-browser/package.json |  2 +-
 examples/webrtc-direct/index.html           |  2 +-
 examples/webrtc-direct/package.json         |  5 ++---
 package.json                                | 14 +++++++-------
 test/ts-use/package.json                    |  2 +-
 5 files changed, 12 insertions(+), 13 deletions(-)

diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json
index cf2fdc7dee..9a45c90d51 100644
--- a/examples/libp2p-in-the-browser/package.json
+++ b/examples/libp2p-in-the-browser/package.json
@@ -28,6 +28,6 @@
     "babel-plugin-syntax-async-functions": "^6.13.0",
     "babel-plugin-transform-regenerator": "^6.26.0",
     "babel-polyfill": "^6.26.0",
-    "parcel": "next"
+    "parcel": "^2.0.1"
   }
 }
diff --git a/examples/webrtc-direct/index.html b/examples/webrtc-direct/index.html
index a29b43bf3a..3fcf9c35d7 100644
--- a/examples/webrtc-direct/index.html
+++ b/examples/webrtc-direct/index.html
@@ -12,6 +12,6 @@ 

Starting libp2p...


   
- + diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index a1ef5448e9..f555383be0 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -3,7 +3,6 @@ "version": "0.0.1", "private": true, "description": "", - "main": "dist/index.html", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "parcel build index.html", @@ -16,7 +15,7 @@ "babel-plugin-syntax-async-functions": "^6.13.0", "babel-plugin-transform-regenerator": "^6.26.0", "babel-polyfill": "^6.26.0", - "parcel-bundler": "1.12.3", + "parcel": "^2.0.1", "util": "^0.12.3" }, "dependencies": { @@ -25,7 +24,7 @@ "libp2p-mplex": "^0.10.4", "@chainsafe/libp2p-noise": "^4.1.0", "libp2p-webrtc-direct": "^0.7.0", - "peer-id": "^0.15.0" + "peer-id": "^0.15.4" }, "browser": { "ipfs": "ipfs/dist/index.min.js" diff --git a/package.json b/package.json index 81d7c655eb..f228f49c2a 100644 --- a/package.json +++ b/package.json @@ -105,8 +105,8 @@ "it-merge": "^1.0.0", "it-pipe": "^1.1.0", "it-take": "^1.0.0", - "libp2p-crypto": "^0.19.4", - "libp2p-interfaces": "^1.0.0", + "libp2p-crypto": "^0.20.0", + "libp2p-interfaces": "^1.3.1", "libp2p-utils": "^0.4.0", "mafmt": "^10.0.0", "merge-options": "^3.0.4", @@ -119,7 +119,7 @@ "p-fifo": "^1.0.0", "p-retry": "^4.4.0", "p-settle": "^4.1.1", - "peer-id": "^0.15.0", + "peer-id": "^0.15.4", "private-ip": "^2.1.0", "protobufjs": "^6.10.2", "retimer": "^3.0.0", @@ -143,7 +143,7 @@ "buffer": "^6.0.3", "datastore-core": "^6.0.7", "delay": "^5.0.0", - "into-stream": "^7.0.0", + "into-stream": "^6.0.0", "ipfs-http-client": "^54.0.2", "it-concat": "^2.0.0", "it-pair": "^1.0.0", @@ -154,13 +154,13 @@ "libp2p-delegated-peer-routing": "^0.10.0", "libp2p-floodsub": "^0.27.0", "libp2p-gossipsub": "^0.11.0", - "libp2p-interfaces-compliance-tests": "^1.0.0", + "libp2p-interfaces-compliance-tests": "^1.2.1", "libp2p-interop": "^0.5.0", - "libp2p-kad-dht": "^0.26.5", + "libp2p-kad-dht": "^0.26.6", "libp2p-mdns": "^0.17.0", "libp2p-mplex": "^0.10.1", "libp2p-tcp": "^0.17.0", - "libp2p-webrtc-star": "^0.23.0", + "libp2p-webrtc-star": "^0.24.0", "libp2p-websockets": "^0.16.0", "nock": "^13.0.3", "p-defer": "^3.0.0", diff --git a/test/ts-use/package.json b/test/ts-use/package.json index c056526a85..612eef97f1 100644 --- a/test/ts-use/package.json +++ b/test/ts-use/package.json @@ -16,7 +16,7 @@ "libp2p-record": "^0.10.4", "libp2p-tcp": "^0.17.1", "libp2p-websockets": "^0.16.1", - "peer-id": "^0.15.0" + "peer-id": "^0.15.4" }, "scripts": { "build": "npx tsc", From 3a9d5f64d96719ebb4d3b083c4f5832db4fa0816 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 30 Nov 2021 18:07:57 +0000 Subject: [PATCH 267/447] fix: stop dht before connection manager (#1041) Stop the dht before the connection manager, otherwise in-flight eviction pings fail and we move on to the next one when we should just abort them all. Also pulls in the fix from #1039 and splits the auto-dialler out from the connection manager as during shutdown it can get into a weird state where it's simultaneously killing and creating connections so stop auto-dialling things before we cause connections to dip below the low watermark by killing existing connections. Fixes: https://github.com/ipfs/js-ipfs/issues/3923 --- src/connection-manager/auto-dialler.js | 118 +++++++++++++++++++++++++ src/connection-manager/index.js | 52 ----------- src/index.js | 11 ++- 3 files changed, 127 insertions(+), 54 deletions(-) create mode 100644 src/connection-manager/auto-dialler.js diff --git a/src/connection-manager/auto-dialler.js b/src/connection-manager/auto-dialler.js new file mode 100644 index 0000000000..977b13f72b --- /dev/null +++ b/src/connection-manager/auto-dialler.js @@ -0,0 +1,118 @@ +'use strict' + +const debug = require('debug') +const mergeOptions = require('merge-options') +// @ts-ignore retimer does not have types +const retimer = require('retimer') + +const log = Object.assign(debug('libp2p:connection-manager:auto-dialler'), { + error: debug('libp2p:connection-manager:auto-dialler:err') +}) + +const defaultOptions = { + enabled: true, + minConnections: 0, + autoDialInterval: 10000 +} + +/** + * @typedef {import('../index')} Libp2p + * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection + */ + +/** + * @typedef {Object} AutoDiallerOptions + * @property {boolean} [enabled = true] - Should preemptively guarantee connections are above the low watermark + * @property {number} [minConnections = 0] - The minimum number of connections to avoid pruning + * @property {number} [autoDialInterval = 10000] - How often, in milliseconds, it should preemptively guarantee connections are above the low watermark + */ + +class AutoDialler { + /** + * 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. + * + * @class + * @param {Libp2p} libp2p + * @param {AutoDiallerOptions} options + */ + constructor (libp2p, options = {}) { + this._options = mergeOptions.call({ ignoreUndefined: true }, defaultOptions, options) + this._libp2p = libp2p + this._running = false + this._autoDialTimeout = null + this._autoDial = this._autoDial.bind(this) + + log('options: %j', this._options) + } + + /** + * Starts the auto dialer + */ + start () { + if (!this._options.enabled) { + log('not enabled') + return + } + + this._running = true + this._autoDial() + log('started') + } + + /** + * Stops the auto dialler + */ + async stop () { + if (!this._options.enabled) { + log('not enabled') + return + } + + this._running = false + this._autoDialTimeout && this._autoDialTimeout.clear() + log('stopped') + } + + async _autoDial () { + const minConnections = this._options.minConnections + + // Already has enough connections + if (this._libp2p.connections.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; this._running && i < peers.length && this._libp2p.connections.size < minConnections; i++) { + if (!this._libp2p.connectionManager.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) + } catch (/** @type {any} */ err) { + log.error('could not connect to peerStore stored peer', err) + } + } + } + + // Connection Manager was stopped + if (!this._running) { + return + } + + this._autoDialTimeout = retimer(this._autoDial, this._options.autoDialInterval) + } +} + +module.exports = AutoDialler diff --git a/src/connection-manager/index.js b/src/connection-manager/index.js index 802c9fe65d..84711272b9 100644 --- a/src/connection-manager/index.js +++ b/src/connection-manager/index.js @@ -94,9 +94,7 @@ class ConnectionManager extends EventEmitter { this._started = false this._timer = null - this._autoDialTimeout = null this._checkMetrics = this._checkMetrics.bind(this) - this._autoDial = this._autoDial.bind(this) this._latencyMonitor = new LatencyMonitor({ latencyCheckIntervalMs: this._options.pollInterval, @@ -128,8 +126,6 @@ class ConnectionManager extends EventEmitter { this._started = true log('started') - - this._options.autoDial && this._autoDial() } /** @@ -138,7 +134,6 @@ class ConnectionManager extends EventEmitter { * @async */ async stop () { - this._autoDialTimeout && this._autoDialTimeout.clear() this._timer && this._timer.clear() this._latencyMonitor.removeListener('data', this._onLatencyMeasure) @@ -312,53 +307,6 @@ 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 - */ - 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 (/** @type {any} */ 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. diff --git a/src/index.js b/src/index.js index 63edbf6924..2b42822312 100644 --- a/src/index.js +++ b/src/index.js @@ -18,6 +18,7 @@ const { codes, messages } = require('./errors') const AddressManager = require('./address-manager') const ConnectionManager = require('./connection-manager') +const AutoDialler = require('./connection-manager/auto-dialler') const Circuit = require('./circuit/transport') const Relay = require('./circuit') const Dialer = require('./dialer') @@ -193,9 +194,13 @@ class Libp2p extends EventEmitter { // Create the Connection Manager this.connectionManager = new ConnectionManager(this, { - autoDial: this._config.peerDiscovery.autoDial, ...this._options.connectionManager }) + this._autodialler = new AutoDialler(this, { + enabled: this._config.peerDiscovery.autoDial, + minConnections: this._options.connectionManager.minConnections, + autoDialInterval: this._options.connectionManager.autoDialInterval + }) // Create Metrics if (this._options.metrics.enabled) { @@ -380,6 +385,8 @@ class Libp2p extends EventEmitter { this.relay && this.relay.stop() this.peerRouting.stop() + this._autodialler.stop() + await (this._dht && this._dht.stop()) for (const service of this._discovery.values()) { service.removeListener('peer', this._onDiscoveryPeer) @@ -394,7 +401,6 @@ class Libp2p extends EventEmitter { await Promise.all([ this.pubsub && this.pubsub.stop(), - this._dht && this._dht.stop(), this.metrics && this.metrics.stop() ]) @@ -650,6 +656,7 @@ class Libp2p extends EventEmitter { } this.connectionManager.start() + this._autodialler.start() // Peer discovery await this._setupPeerDiscovery() From 9cbf36fcb54099e6fed35ceccc4a2376f0926c1f Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 2 Dec 2021 10:11:23 +0000 Subject: [PATCH 268/447] chore: update peer id and libp2p crypto (#1042) BREAKING CHANGE: requires node 15+ --- .github/workflows/main.yml | 16 ++++++++++++++-- examples/webrtc-direct/package.json | 2 +- package.json | 26 +++++++++++++------------- src/dialer/index.js | 3 +-- test/ts-use/package.json | 2 +- 5 files changed, 30 insertions(+), 19 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8c401645d6..ab7a340367 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: - node-version: 14 + node-version: 16 - run: npm install - run: npx aegir lint - run: npx aegir build @@ -29,7 +29,7 @@ jobs: strategy: matrix: os: [windows-latest, ubuntu-latest, macos-latest] - node: [14, 16] + node: [16] fail-fast: true steps: - uses: actions/checkout@v2 @@ -44,6 +44,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: lts/* - run: npm install - run: npx aegir test -t browser -t webworker --bail test-firefox: @@ -51,6 +54,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: lts/* - run: npm install - run: npx aegir test -t browser -t webworker --bail -- --browser firefox test-ts: @@ -58,6 +64,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: lts/* - run: npm install - run: npm run test:ts test-interop: @@ -65,5 +74,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: lts/* - run: npm install - run: npm run test:interop -- --bail -- --exit diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index f555383be0..0b912d1e56 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -24,7 +24,7 @@ "libp2p-mplex": "^0.10.4", "@chainsafe/libp2p-noise": "^4.1.0", "libp2p-webrtc-direct": "^0.7.0", - "peer-id": "^0.15.4" + "peer-id": "^0.16.0" }, "browser": { "ipfs": "ipfs/dist/index.min.js" diff --git a/package.json b/package.json index f228f49c2a..e03ef7549e 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "homepage": "https://libp2p.io", "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">=15.0.0" }, "browser": { "@motrix/nat-api": false @@ -80,10 +80,10 @@ ] }, "dependencies": { - "abortable-iterator": "^3.0.0", "@motrix/nat-api": "^0.3.1", "@vascosantos/moving-average": "^1.1.0", "abort-controller": "^3.0.0", + "abortable-iterator": "^3.0.0", "aggregate-error": "^3.1.0", "any-signal": "^2.1.1", "bignumber.js": "^9.0.1", @@ -105,8 +105,8 @@ "it-merge": "^1.0.0", "it-pipe": "^1.1.0", "it-take": "^1.0.0", - "libp2p-crypto": "^0.20.0", - "libp2p-interfaces": "^1.3.1", + "libp2p-crypto": "^0.21.0", + "libp2p-interfaces": "^2.0.1", "libp2p-utils": "^0.4.0", "mafmt": "^10.0.0", "merge-options": "^3.0.4", @@ -119,14 +119,14 @@ "p-fifo": "^1.0.0", "p-retry": "^4.4.0", "p-settle": "^4.1.1", - "peer-id": "^0.15.4", + "peer-id": "^0.16.0", "private-ip": "^2.1.0", "protobufjs": "^6.10.2", "retimer": "^3.0.0", "sanitize-filename": "^1.6.3", "set-delayed-interval": "^1.0.0", "streaming-iterables": "^6.0.0", - "timeout-abort-controller": "^1.1.1", + "timeout-abort-controller": "^2.0.0", "uint8arrays": "^3.0.0", "varint": "^6.0.0", "wherearewe": "^1.0.0", @@ -149,25 +149,25 @@ "it-pair": "^1.0.0", "it-pushable": "^1.4.0", "libp2p": ".", - "libp2p-bootstrap": "^0.13.0", + "libp2p-bootstrap": "^0.14.0", "libp2p-delegated-content-routing": "^0.11.0", - "libp2p-delegated-peer-routing": "^0.10.0", + "libp2p-delegated-peer-routing": "^0.11.0", "libp2p-floodsub": "^0.27.0", "libp2p-gossipsub": "^0.11.0", - "libp2p-interfaces-compliance-tests": "^1.2.1", + "libp2p-interfaces-compliance-tests": "^2.0.1", "libp2p-interop": "^0.5.0", - "libp2p-kad-dht": "^0.26.6", - "libp2p-mdns": "^0.17.0", + "libp2p-kad-dht": "^0.27.1", + "libp2p-mdns": "^0.18.0", "libp2p-mplex": "^0.10.1", "libp2p-tcp": "^0.17.0", - "libp2p-webrtc-star": "^0.24.0", + "libp2p-webrtc-star": "^0.25.0", "libp2p-websockets": "^0.16.0", "nock": "^13.0.3", "p-defer": "^3.0.0", "p-times": "^3.0.0", "p-wait-for": "^3.2.0", "rimraf": "^3.0.2", - "sinon": "^11.1.1", + "sinon": "^12.0.1", "util": "^0.12.3" }, "contributors": [ diff --git a/src/dialer/index.js b/src/dialer/index.js index 630e9c50b2..0cd5e088f8 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -6,8 +6,7 @@ const log = Object.assign(debug('libp2p:dialer'), { }) const errCode = require('err-code') const { Multiaddr } = require('multiaddr') -// @ts-ignore timeout-abourt-controles does not export types -const TimeoutController = require('timeout-abort-controller') +const { TimeoutController } = require('timeout-abort-controller') const { AbortError } = require('abortable-iterator') const { anySignal } = require('any-signal') diff --git a/test/ts-use/package.json b/test/ts-use/package.json index 612eef97f1..8593e19282 100644 --- a/test/ts-use/package.json +++ b/test/ts-use/package.json @@ -16,7 +16,7 @@ "libp2p-record": "^0.10.4", "libp2p-tcp": "^0.17.1", "libp2p-websockets": "^0.16.1", - "peer-id": "^0.15.4" + "peer-id": "^0.16.0" }, "scripts": { "build": "npx tsc", From 15a0b1dbf2c574b37e5819134ee3adac3a40b279 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Thu, 2 Dec 2021 10:44:07 +0000 Subject: [PATCH 269/447] chore: update contributors --- package.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index e03ef7549e..4e1f33cf8e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.34.0", + "version": "0.35.0", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", @@ -181,24 +181,23 @@ "Friedel Ziegelmayer ", "Maciej Krüger ", "Hugo Dias ", - "Chris Dostert ", "dirkmc ", + "Chris Dostert ", "Volker Mische ", "zeim839 <50573884+zeim839@users.noreply.github.com>", "Richard Littauer ", "a1300 ", "Ryan Bell ", - "Elven ", "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", - "Thomas Eizinger ", + "Giovanni T. Parra ", + "acolytec3 <17355484+acolytec3@users.noreply.github.com>", + "Franck Royer ", + "Elven ", "Robert Kiel ", "Andrew Nesbitt ", "Samlior ", - "acolytec3 <17355484+acolytec3@users.noreply.github.com>", - "Franck Royer ", - "Giovanni T. Parra ", + "Thomas Eizinger ", "Didrik Nordström ", - "RasmusErik Voel Jensen ", "Smite Chow ", "Soeren ", "Sönke Hahn ", @@ -247,6 +246,7 @@ "Michael Burns <5170+mburns@users.noreply.github.com>", "Miguel Mota ", "Nuno Nogueira ", - "Philipp Muens " + "Philipp Muens ", + "RasmusErik Voel Jensen " ] } From 6d0ac819f11c6efcd662b0b402c47180ecf35b2b Mon Sep 17 00:00:00 2001 From: achingbrain Date: Thu, 2 Dec 2021 10:44:07 +0000 Subject: [PATCH 270/447] chore: release version v0.35.0 --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b749a798b2..00bee1edd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +# [0.35.0](https://github.com/libp2p/js-libp2p/compare/v0.34.0...v0.35.0) (2021-12-02) + + +### Bug Fixes + +* stop dht before connection manager ([#1041](https://github.com/libp2p/js-libp2p/issues/1041)) ([3a9d5f6](https://github.com/libp2p/js-libp2p/commit/3a9d5f64d96719ebb4d3b083c4f5832db4fa0816)), closes [#1039](https://github.com/libp2p/js-libp2p/issues/1039) + + +### chore + +* update peer id and libp2p crypto ([#1042](https://github.com/libp2p/js-libp2p/issues/1042)) ([9cbf36f](https://github.com/libp2p/js-libp2p/commit/9cbf36fcb54099e6fed35ceccc4a2376f0926c1f)) + + +### BREAKING CHANGES + +* requires node 15+ + + + # [0.34.0](https://github.com/libp2p/js-libp2p/compare/v0.33.0...v0.34.0) (2021-11-25) From 91c2ec9856a3e972b7b2c9c4d9a4eda1d431c7ef Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 3 Dec 2021 15:47:30 +0000 Subject: [PATCH 271/447] fix: do not let closest peers run forever (#1047) The DHT takes a `signal` not a timeout so if a timeout is passed, create a `TimeoutController` that will abort the query after the timeout. --- src/peer-routing.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/peer-routing.js b/src/peer-routing.js index f37ce6638f..2ffa5cfd74 100644 --- a/src/peer-routing.js +++ b/src/peer-routing.js @@ -10,6 +10,7 @@ const { uniquePeers, requirePeers } = require('./content-routing/utils') +const { TimeoutController } = require('timeout-abort-controller') const merge = require('it-merge') const { pipe } = require('it-pipe') @@ -34,6 +35,7 @@ const { DHTPeerRouting } = require('./dht/dht-peer-routing') * @property {boolean} [enabled = true] - Whether to enable the Refresh manager * @property {number} [bootDelay = 6e5] - Boot delay to start the Refresh Manager (in ms) * @property {number} [interval = 10e3] - Interval between each Refresh Manager run (in ms) + * @property {number} [timeout = 10e3] - How long to let each refresh run (in ms) * * @typedef {Object} PeerRoutingOptions * @property {RefreshManagerOptions} [refreshManager] @@ -79,7 +81,7 @@ class PeerRouting { async _findClosestPeersTask () { try { // nb getClosestPeers adds the addresses to the address book - await drain(this.getClosestPeers(this._peerId.id)) + await drain(this.getClosestPeers(this._peerId.id, { timeout: this._refreshManagerOptions.timeout || 10e3 })) } catch (/** @type {any} */ err) { log.error(err) } @@ -131,7 +133,8 @@ class PeerRouting { * * @param {Uint8Array} key - A CID like key * @param {Object} [options] - * @param {number} [options.timeout=30e3] - How long the query can take. + * @param {number} [options.timeout=30e3] - How long the query can take + * @param {AbortSignal} [options.signal] - An AbortSignal to abort the request * @returns {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} */ async * getClosestPeers (key, options = { timeout: 30e3 }) { @@ -139,6 +142,10 @@ class PeerRouting { throw errCode(new Error('No peer routers available'), 'NO_ROUTERS_AVAILABLE') } + if (options.timeout) { + options.signal = new TimeoutController(options.timeout).signal + } + yield * pipe( merge( ...this._routers.map(router => router.getClosestPeers(key, options)) From 149120bebcb3df536213b4fcacaf8e8b01383b13 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Fri, 3 Dec 2021 16:24:13 +0000 Subject: [PATCH 272/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4e1f33cf8e..16423a60a9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.35.0", + "version": "0.35.1", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From ae21299ade9bcb2cf3fd00150c4389fbe3b24267 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Fri, 3 Dec 2021 16:24:14 +0000 Subject: [PATCH 273/447] chore: release version v0.35.1 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00bee1edd0..5236bf7777 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.35.1](https://github.com/libp2p/js-libp2p/compare/v0.35.0...v0.35.1) (2021-12-03) + + +### Bug Fixes + +* do not let closest peers run forever ([#1047](https://github.com/libp2p/js-libp2p/issues/1047)) ([91c2ec9](https://github.com/libp2p/js-libp2p/commit/91c2ec9856a3e972b7b2c9c4d9a4eda1d431c7ef)) + + + # [0.35.0](https://github.com/libp2p/js-libp2p/compare/v0.34.0...v0.35.0) (2021-12-02) From b70fb43427b47df079b55929ec8956f69cbda966 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Mon, 6 Dec 2021 19:54:44 +0000 Subject: [PATCH 274/447] fix: increase maxlisteners on event target (#1050) Sometimes you encounter peers with lots of addresses. When this happens you can attach more than 10x event listeners to the abort signal we use to abort all the dials - this causes node to print a warning which is misleading. This PR increases the default number of listeners on the signal. Fixes #900 --- src/dialer/dial-request.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/dialer/dial-request.js b/src/dialer/dial-request.js index 3f6fc18ae1..707f4e2171 100644 --- a/src/dialer/dial-request.js +++ b/src/dialer/dial-request.js @@ -1,11 +1,12 @@ 'use strict' const errCode = require('err-code') -const AbortController = require('abort-controller').default const { anySignal } = require('any-signal') // @ts-ignore p-fifo does not export types const FIFO = require('p-fifo') const pAny = require('p-any') +// @ts-expect-error setMaxListeners is missing from the types +const { setMaxListeners } = require('events') /** * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection @@ -59,7 +60,12 @@ class DialRequest { const tokenHolder = new FIFO() tokens.forEach(token => tokenHolder.push(token)) - const dialAbortControllers = this.addrs.map(() => new AbortController()) + const dialAbortControllers = this.addrs.map(() => { + const controller = new AbortController() + setMaxListeners && setMaxListeners(Infinity, controller.signal) + + return controller + }) let completedDials = 0 try { From 9b21893b64082bc0019f7fbc76c0ba4876a01bae Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 6 Dec 2021 21:07:38 +0100 Subject: [PATCH 275/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 16423a60a9..63ae6c44d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.35.1", + "version": "0.35.2", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From b9339bccaa679d0698a9960ee650a88faefeb174 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 6 Dec 2021 21:07:39 +0100 Subject: [PATCH 276/447] chore: release version v0.35.2 --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5236bf7777..fd5e3f3e22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,31 @@ +## [0.35.2](https://github.com/libp2p/js-libp2p/compare/v0.33.0...v0.35.2) (2021-12-06) + + +### Bug Fixes + +* do not let closest peers run forever ([#1047](https://github.com/libp2p/js-libp2p/issues/1047)) ([91c2ec9](https://github.com/libp2p/js-libp2p/commit/91c2ec9856a3e972b7b2c9c4d9a4eda1d431c7ef)) +* increase maxlisteners on event target ([#1050](https://github.com/libp2p/js-libp2p/issues/1050)) ([b70fb43](https://github.com/libp2p/js-libp2p/commit/b70fb43427b47df079b55929ec8956f69cbda966)), closes [#900](https://github.com/libp2p/js-libp2p/issues/900) +* private ip ts compile has no call signatures ([#1020](https://github.com/libp2p/js-libp2p/issues/1020)) ([77d7cb8](https://github.com/libp2p/js-libp2p/commit/77d7cb8f0815f2cdd3bfdfa8b641a7a186fe9520)) +* stop dht before connection manager ([#1041](https://github.com/libp2p/js-libp2p/issues/1041)) ([3a9d5f6](https://github.com/libp2p/js-libp2p/commit/3a9d5f64d96719ebb4d3b083c4f5832db4fa0816)), closes [#1039](https://github.com/libp2p/js-libp2p/issues/1039) + + +### chore + +* update peer id and libp2p crypto ([#1042](https://github.com/libp2p/js-libp2p/issues/1042)) ([9cbf36f](https://github.com/libp2p/js-libp2p/commit/9cbf36fcb54099e6fed35ceccc4a2376f0926c1f)) + + +### Features + +* update dht ([#1009](https://github.com/libp2p/js-libp2p/issues/1009)) ([2f598eb](https://github.com/libp2p/js-libp2p/commit/2f598eba09cff4301474af08196158065e3602d8)) + + +### BREAKING CHANGES + +* requires node 15+ +* libp2p-kad-dht has a new event-based API which is exposed as `_dht` + + + ## [0.35.1](https://github.com/libp2p/js-libp2p/compare/v0.35.0...v0.35.1) (2021-12-03) From 51dabb17244100d5dd726a5e805b1e253216841b Mon Sep 17 00:00:00 2001 From: Alan Smithee Date: Mon, 6 Dec 2021 21:12:38 +0100 Subject: [PATCH 277/447] chore: pubsub example subscribe returns void (#1048) Seems like the correct return type of `Libp2p.pubsub.subscribe` is `void`, so the `await` can be removed: https://github.com/libp2p/js-libp2p/blob/ae21299ade9bcb2cf3fd00150c4389fbe3b24267/src/pubsub-adapter.js#L29 --- examples/pubsub/1.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/pubsub/1.js b/examples/pubsub/1.js index c4eed3d0c5..f1f60eb9ac 100644 --- a/examples/pubsub/1.js +++ b/examples/pubsub/1.js @@ -41,13 +41,13 @@ const createNode = async () => { node1.pubsub.on(topic, (msg) => { console.log(`node1 received: ${uint8ArrayToString(msg.data)}`) }) - await node1.pubsub.subscribe(topic) + node1.pubsub.subscribe(topic) // Will not receive own published messages by default node2.pubsub.on(topic, (msg) => { console.log(`node2 received: ${uint8ArrayToString(msg.data)}`) }) - await node2.pubsub.subscribe(topic) + node2.pubsub.subscribe(topic) // node2 publishes "news" every second setInterval(() => { From cbaa5a2ef3e85398da3e87e009bfd60edce72dd0 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 7 Dec 2021 09:37:12 +0000 Subject: [PATCH 278/447] chore: switch to nat api (#1052) @motrix/nat-api is a fork, nat-api has the fix from https://github.com/alxhotel/nat-api/pull/25 --- package.json | 4 ++-- src/nat-manager.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 63ae6c44d6..d8fb051072 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "node": ">=15.0.0" }, "browser": { - "@motrix/nat-api": false + "nat-api": false }, "eslintConfig": { "extends": "ipfs", @@ -80,7 +80,6 @@ ] }, "dependencies": { - "@motrix/nat-api": "^0.3.1", "@vascosantos/moving-average": "^1.1.0", "abort-controller": "^3.0.0", "abortable-iterator": "^3.0.0", @@ -114,6 +113,7 @@ "multiformats": "^9.0.0", "multistream-select": "^2.0.0", "mutable-proxy": "^1.0.0", + "nat-api": "^0.3.1", "node-forge": "^0.10.0", "p-any": "^3.0.0", "p-fifo": "^1.0.0", diff --git a/src/nat-manager.js b/src/nat-manager.js index 4b0b60dd24..cd8bf5aaf5 100644 --- a/src/nat-manager.js +++ b/src/nat-manager.js @@ -1,7 +1,7 @@ 'use strict' // @ts-ignore nat-api does not export types -const NatAPI = require('@motrix/nat-api') +const NatAPI = require('nat-api') const debug = require('debug') const { promisify } = require('es6-promisify') const { Multiaddr } = require('multiaddr') From b25e0fe5312db58a06c39500ae84c50fed3a93bd Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 7 Dec 2021 14:42:10 +0000 Subject: [PATCH 279/447] fix: make error codes consistent (#1054) Sometimes they are `NOT_FOUND`, sometimes `ERR_NOT_FOUND`, etc. Move all error codes into `errors.js` and reference them from there. --- src/dialer/dial-request.js | 3 ++- src/errors.js | 26 +++++++++++++++++- src/keychain/cms.js | 13 ++++----- src/keychain/index.js | 49 +++++++++++++++++----------------- src/peer-routing.js | 9 ++++--- src/peer-store/book.js | 11 +++----- src/ping/index.js | 4 +-- src/upgrader.js | 2 +- test/keychain/keychain.spec.js | 4 +-- 9 files changed, 73 insertions(+), 48 deletions(-) diff --git a/src/dialer/dial-request.js b/src/dialer/dial-request.js index 707f4e2171..397fc784c4 100644 --- a/src/dialer/dial-request.js +++ b/src/dialer/dial-request.js @@ -7,6 +7,7 @@ const FIFO = require('p-fifo') const pAny = require('p-any') // @ts-expect-error setMaxListeners is missing from the types const { setMaxListeners } = require('events') +const { codes } = require('../errors') /** * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection @@ -55,7 +56,7 @@ class DialRequest { const tokens = this.dialer.getTokens(this.addrs.length) // If no tokens are available, throw if (tokens.length < 1) { - throw errCode(new Error('No dial tokens available'), 'ERR_NO_DIAL_TOKENS') + throw errCode(new Error('No dial tokens available'), codes.ERR_NO_DIAL_TOKENS) } const tokenHolder = new FIFO() diff --git a/src/errors.js b/src/errors.js index efaed29c06..a5db3901b1 100644 --- a/src/errors.js +++ b/src/errors.js @@ -36,5 +36,29 @@ exports.codes = { ERR_TRANSPORT_DIAL_FAILED: 'ERR_TRANSPORT_DIAL_FAILED', ERR_UNSUPPORTED_PROTOCOL: 'ERR_UNSUPPORTED_PROTOCOL', ERR_INVALID_MULTIADDR: 'ERR_INVALID_MULTIADDR', - ERR_SIGNATURE_NOT_VALID: 'ERR_SIGNATURE_NOT_VALID' + ERR_SIGNATURE_NOT_VALID: 'ERR_SIGNATURE_NOT_VALID', + ERR_FIND_SELF: 'ERR_FIND_SELF', + ERR_NO_ROUTERS: 'ERR_NO_ROUTERS', + ERR_CONNECTION_NOT_MULTIPLEXED: 'ERR_CONNECTION_NOT_MULTIPLEXED', + ERR_NO_DIAL_TOKENS: 'ERR_NO_DIAL_TOKENS', + ERR_KEYCHAIN_REQUIRED: 'ERR_KEYCHAIN_REQUIRED', + ERR_INVALID_CMS: 'ERR_INVALID_CMS', + ERR_MISSING_KEYS: 'ERR_MISSING_KEYS', + ERR_NO_KEY: 'ERR_NO_KEY', + ERR_INVALID_KEY_NAME: 'ERR_INVALID_KEY_NAME', + ERR_INVALID_KEY_TYPE: 'ERR_INVALID_KEY_TYPE', + ERR_KEY_ALREADY_EXISTS: 'ERR_KEY_ALREADY_EXISTS', + ERR_INVALID_KEY_SIZE: 'ERR_INVALID_KEY_SIZE', + ERR_KEY_NOT_FOUND: 'ERR_KEY_NOT_FOUND', + ERR_OLD_KEY_NAME_INVALID: 'ERR_OLD_KEY_NAME_INVALID', + ERR_NEW_KEY_NAME_INVALID: 'ERR_NEW_KEY_NAME_INVALID', + ERR_PASSWORD_REQUIRED: 'ERR_PASSWORD_REQUIRED', + ERR_PEM_REQUIRED: 'ERR_PEM_REQUIRED', + ERR_CANNOT_READ_KEY: 'ERR_CANNOT_READ_KEY', + ERR_MISSING_PRIVATE_KEY: 'ERR_MISSING_PRIVATE_KEY', + ERR_INVALID_OLD_PASS_TYPE: 'ERR_INVALID_OLD_PASS_TYPE', + ERR_INVALID_NEW_PASS_TYPE: 'ERR_INVALID_NEW_PASS_TYPE', + ERR_INVALID_PASS_LENGTH: 'ERR_INVALID_PASS_LENGTH', + ERR_NOT_IMPLEMENTED: 'ERR_NOT_IMPLEMENTED', + ERR_WRONG_PING_ACK: 'ERR_WRONG_PING_ACK' } diff --git a/src/keychain/cms.js b/src/keychain/cms.js index e9361882df..f929cb4e71 100644 --- a/src/keychain/cms.js +++ b/src/keychain/cms.js @@ -10,6 +10,7 @@ const { certificateForKey, findAsync } = require('./util') const errcode = require('err-code') const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +const { codes } = require('../errors') const privates = new WeakMap() @@ -31,7 +32,7 @@ class CMS { */ constructor (keychain, dek) { if (!keychain) { - throw errcode(new Error('keychain is required'), 'ERR_KEYCHAIN_REQUIRED') + throw errcode(new Error('keychain is required'), codes.ERR_KEYCHAIN_REQUIRED) } this.keychain = keychain @@ -49,7 +50,7 @@ class CMS { */ async encrypt (name, plain) { if (!(plain instanceof Uint8Array)) { - throw errcode(new Error('Plain data must be a Uint8Array'), 'ERR_INVALID_PARAMS') + throw errcode(new Error('Plain data must be a Uint8Array'), codes.ERR_INVALID_PARAMETERS) } const key = await this.keychain.findKeyByName(name) @@ -81,7 +82,7 @@ class CMS { */ async decrypt (cmsData) { if (!(cmsData instanceof Uint8Array)) { - throw errcode(new Error('CMS data is required'), 'ERR_INVALID_PARAMS') + throw errcode(new Error('CMS data is required'), codes.ERR_INVALID_PARAMETERS) } let cms @@ -91,7 +92,7 @@ class CMS { // @ts-ignore not defined cms = forge.pkcs7.messageFromAsn1(obj) } catch (/** @type {any} */ err) { - throw errcode(new Error('Invalid CMS: ' + err.message), 'ERR_INVALID_CMS') + throw errcode(new Error('Invalid CMS: ' + err.message), codes.ERR_INVALID_CMS) } // Find a recipient whose key we hold. We only deal with recipient certs @@ -123,7 +124,7 @@ class CMS { if (!r) { // @ts-ignore cms types not defined const missingKeys = recipients.map(r => r.keyId) - throw errcode(new Error('Decryption needs one of the key(s): ' + missingKeys.join(', ')), 'ERR_MISSING_KEYS', { + throw errcode(new Error('Decryption needs one of the key(s): ' + missingKeys.join(', ')), codes.ERR_MISSING_KEYS, { missingKeys }) } @@ -131,7 +132,7 @@ class CMS { const key = await this.keychain.findKeyById(r.keyId) if (!key) { - throw errcode(new Error('No key available to decrypto'), 'ERR_NO_KEY') + throw errcode(new Error('No key available to decrypto'), codes.ERR_NO_KEY) } const pem = await this.keychain._getPrivateKey(key.name) diff --git a/src/keychain/index.js b/src/keychain/index.js index b25be5a854..f6a17ab62e 100644 --- a/src/keychain/index.js +++ b/src/keychain/index.js @@ -10,6 +10,7 @@ const crypto = require('libp2p-crypto') const { Key } = require('interface-datastore/key') const CMS = require('./cms') const errcode = require('err-code') +const { codes } = require('../errors') const { toString: uint8ArrayToString } = require('uint8arrays/to-string') const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') @@ -210,21 +211,21 @@ class Keychain { const self = this if (!validateKeyName(name) || name === 'self') { - return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME')) + return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME)) } if (typeof type !== 'string') { - return throwDelayed(errcode(new Error(`Invalid key type '${type}'`), 'ERR_INVALID_KEY_TYPE')) + return throwDelayed(errcode(new Error(`Invalid key type '${type}'`), codes.ERR_INVALID_KEY_TYPE)) } 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')) + if (exists) return throwDelayed(errcode(new Error(`Key '${name}' already exists`), codes.ERR_KEY_ALREADY_EXISTS)) switch (type.toLowerCase()) { case 'rsa': if (!Number.isSafeInteger(size) || size < 2048) { - return throwDelayed(errcode(new Error(`Invalid RSA key size ${size}`), 'ERR_INVALID_KEY_SIZE')) + return throwDelayed(errcode(new Error(`Invalid RSA key size ${size}`), codes.ERR_INVALID_KEY_SIZE)) } break default: @@ -297,7 +298,7 @@ class Keychain { */ async findKeyByName (name) { if (!validateKeyName(name)) { - return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME')) + return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME)) } const dsname = DsInfoName(name) @@ -305,7 +306,7 @@ class Keychain { const res = await this.store.get(dsname) return JSON.parse(uint8ArrayToString(res)) } catch (/** @type {any} */ err) { - return throwDelayed(errcode(new Error(`Key '${name}' does not exist. ${err.message}`), 'ERR_KEY_NOT_FOUND')) + return throwDelayed(errcode(new Error(`Key '${name}' does not exist. ${err.message}`), codes.ERR_KEY_NOT_FOUND)) } } @@ -318,7 +319,7 @@ class Keychain { async removeKey (name) { const self = this if (!validateKeyName(name) || name === 'self') { - return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME')) + return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME)) } const dsname = DsName(name) const keyInfo = await self.findKeyByName(name) @@ -339,10 +340,10 @@ class Keychain { async renameKey (oldName, newName) { const self = this if (!validateKeyName(oldName) || oldName === 'self') { - return throwDelayed(errcode(new Error(`Invalid old key name '${oldName}'`), 'ERR_OLD_KEY_NAME_INVALID')) + return throwDelayed(errcode(new Error(`Invalid old key name '${oldName}'`), codes.ERR_OLD_KEY_NAME_INVALID)) } if (!validateKeyName(newName) || newName === 'self') { - return throwDelayed(errcode(new Error(`Invalid new key name '${newName}'`), 'ERR_NEW_KEY_NAME_INVALID')) + return throwDelayed(errcode(new Error(`Invalid new key name '${newName}'`), codes.ERR_NEW_KEY_NAME_INVALID)) } const oldDsname = DsName(oldName) const newDsname = DsName(newName) @@ -350,7 +351,7 @@ class Keychain { const newInfoName = DsInfoName(newName) const exists = await self.store.has(newDsname) - if (exists) return throwDelayed(errcode(new Error(`Key '${newName}' already exists`), 'ERR_KEY_ALREADY_EXISTS')) + if (exists) return throwDelayed(errcode(new Error(`Key '${newName}' already exists`), codes.ERR_KEY_ALREADY_EXISTS)) try { const pem = await self.store.get(oldDsname) @@ -379,10 +380,10 @@ class Keychain { */ async exportKey (name, password) { if (!validateKeyName(name)) { - return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME')) + return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME)) } if (!password) { - return throwDelayed(errcode(new Error('Password is required'), 'ERR_PASSWORD_REQUIRED')) + return throwDelayed(errcode(new Error('Password is required'), codes.ERR_PASSWORD_REQUIRED)) } const dsname = DsName(name) @@ -409,20 +410,20 @@ class Keychain { async importKey (name, pem, password) { const self = this if (!validateKeyName(name) || name === 'self') { - return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME')) + return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME)) } if (!pem) { - return throwDelayed(errcode(new Error('PEM encoded key is required'), 'ERR_PEM_REQUIRED')) + return throwDelayed(errcode(new Error('PEM encoded key is required'), codes.ERR_PEM_REQUIRED)) } const dsname = DsName(name) const exists = await self.store.has(dsname) - if (exists) return throwDelayed(errcode(new Error(`Key '${name}' already exists`), 'ERR_KEY_ALREADY_EXISTS')) + if (exists) return throwDelayed(errcode(new Error(`Key '${name}' already exists`), codes.ERR_KEY_ALREADY_EXISTS)) let privateKey try { privateKey = await crypto.keys.import(pem, password) } catch (/** @type {any} */ err) { - return throwDelayed(errcode(new Error('Cannot read the key, most likely the password is wrong'), 'ERR_CANNOT_READ_KEY')) + return throwDelayed(errcode(new Error('Cannot read the key, most likely the password is wrong'), codes.ERR_CANNOT_READ_KEY)) } let kid @@ -457,16 +458,16 @@ class Keychain { async importPeer (name, peer) { const self = this if (!validateKeyName(name)) { - return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME')) + return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME)) } if (!peer || !peer.privKey) { - return throwDelayed(errcode(new Error('Peer.privKey is required'), 'ERR_MISSING_PRIVATE_KEY')) + return throwDelayed(errcode(new Error('Peer.privKey is required'), codes.ERR_MISSING_PRIVATE_KEY)) } const privateKey = peer.privKey const dsname = DsName(name) const exists = await self.store.has(dsname) - if (exists) return throwDelayed(errcode(new Error(`Key '${name}' already exists`), 'ERR_KEY_ALREADY_EXISTS')) + if (exists) return throwDelayed(errcode(new Error(`Key '${name}' already exists`), codes.ERR_KEY_ALREADY_EXISTS)) try { const kid = await privateKey.id() @@ -495,7 +496,7 @@ class Keychain { */ async _getPrivateKey (name) { if (!validateKeyName(name)) { - return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME')) + return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME)) } try { @@ -503,7 +504,7 @@ class Keychain { const res = await this.store.get(dsname) return uint8ArrayToString(res) } catch (/** @type {any} */ err) { - return throwDelayed(errcode(new Error(`Key '${name}' does not exist. ${err.message}`), 'ERR_KEY_NOT_FOUND')) + return throwDelayed(errcode(new Error(`Key '${name}' does not exist. ${err.message}`), codes.ERR_KEY_NOT_FOUND)) } } @@ -515,13 +516,13 @@ class Keychain { */ async rotateKeychainPass (oldPass, newPass) { if (typeof oldPass !== 'string') { - return throwDelayed(errcode(new Error(`Invalid old pass type '${typeof oldPass}'`), 'ERR_INVALID_OLD_PASS_TYPE')) + return throwDelayed(errcode(new Error(`Invalid old pass type '${typeof oldPass}'`), codes.ERR_INVALID_OLD_PASS_TYPE)) } if (typeof newPass !== 'string') { - return throwDelayed(errcode(new Error(`Invalid new pass type '${typeof newPass}'`), 'ERR_INVALID_NEW_PASS_TYPE')) + return throwDelayed(errcode(new Error(`Invalid new pass type '${typeof newPass}'`), codes.ERR_INVALID_NEW_PASS_TYPE)) } if (newPass.length < 20) { - return throwDelayed(errcode(new Error(`Invalid pass length ${newPass.length}`), 'ERR_INVALID_PASS_LENGTH')) + return throwDelayed(errcode(new Error(`Invalid pass length ${newPass.length}`), codes.ERR_INVALID_PASS_LENGTH)) } log('recreating keychain') const oldDek = privates.get(this).dek diff --git a/src/peer-routing.js b/src/peer-routing.js index 2ffa5cfd74..a49e847c1c 100644 --- a/src/peer-routing.js +++ b/src/peer-routing.js @@ -5,6 +5,7 @@ const log = Object.assign(debug('libp2p:peer-routing'), { error: debug('libp2p:peer-routing:err') }) const errCode = require('err-code') +const errors = require('./errors') const { storeAddresses, uniquePeers, @@ -104,11 +105,11 @@ class PeerRouting { */ async findPeer (id, options) { // eslint-disable-line require-await if (!this._routers.length) { - throw errCode(new Error('No peer routers available'), 'NO_ROUTERS_AVAILABLE') + throw errCode(new Error('No peer routers available'), errors.codes.ERR_NO_ROUTERS) } if (id.toB58String() === this._peerId.toB58String()) { - throw errCode(new Error('Should not try to find self'), 'ERR_FIND_SELF') + throw errCode(new Error('Should not try to find self'), errors.codes.ERR_FIND_SELF) } const output = await pipe( @@ -125,7 +126,7 @@ class PeerRouting { return output } - throw errCode(new Error('not found'), 'NOT_FOUND') + throw errCode(new Error(errors.messages.NOT_FOUND), errors.codes.ERR_NOT_FOUND) } /** @@ -139,7 +140,7 @@ class PeerRouting { */ async * getClosestPeers (key, options = { timeout: 30e3 }) { if (!this._routers.length) { - throw errCode(new Error('No peer routers available'), 'NO_ROUTERS_AVAILABLE') + throw errCode(new Error('No peer routers available'), errors.codes.ERR_NO_ROUTERS) } if (options.timeout) { diff --git a/src/peer-store/book.js b/src/peer-store/book.js index 9b6d561b1e..3de0459486 100644 --- a/src/peer-store/book.js +++ b/src/peer-store/book.js @@ -2,10 +2,7 @@ const errcode = require('err-code') const PeerId = require('peer-id') - -const { - codes: { ERR_INVALID_PARAMETERS } -} = require('../errors') +const { codes } = require('../errors') /** * @param {any} data @@ -48,7 +45,7 @@ class Book { * @param {any[]|any} data */ set (peerId, data) { - throw errcode(new Error('set must be implemented by the subclass'), 'ERR_NOT_IMPLEMENTED') + throw errcode(new Error('set must be implemented by the subclass'), codes.ERR_NOT_IMPLEMENTED) } /** @@ -94,7 +91,7 @@ class Book { */ get (peerId) { if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } const rec = this.data.get(peerId.toB58String()) @@ -111,7 +108,7 @@ class Book { */ delete (peerId) { if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } if (!this.data.delete(peerId.toB58String())) { diff --git a/src/ping/index.js b/src/ping/index.js index 758c0ccabd..15b2692399 100644 --- a/src/ping/index.js +++ b/src/ping/index.js @@ -5,7 +5,7 @@ const log = Object.assign(debug('libp2p:ping'), { error: debug('libp2p:ping:err') }) const errCode = require('err-code') - +const { codes } = require('../errors') const crypto = require('libp2p-crypto') const { pipe } = require('it-pipe') // @ts-ignore it-buffer has no types exported @@ -50,7 +50,7 @@ async function ping (node, peer) { const end = Date.now() if (!equals(data, result)) { - throw errCode(new Error('Received wrong ping ack'), 'ERR_WRONG_PING_ACK') + throw errCode(new Error('Received wrong ping ack'), codes.ERR_WRONG_PING_ACK) } return end - start diff --git a/src/upgrader.js b/src/upgrader.js index 8b07be9731..23f9a338b9 100644 --- a/src/upgrader.js +++ b/src/upgrader.js @@ -297,7 +297,7 @@ class Upgrader { maConn.timeline.upgraded = Date.now() const errConnectionNotMultiplexed = () => { - throw errCode(new Error('connection is not multiplexed'), 'ERR_CONNECTION_NOT_MULTIPLEXED') + throw errCode(new Error('connection is not multiplexed'), codes.ERR_CONNECTION_NOT_MULTIPLEXED) } // Create the connection diff --git a/test/keychain/keychain.spec.js b/test/keychain/keychain.spec.js index 0404ebf03e..070a233da4 100644 --- a/test/keychain/keychain.spec.js +++ b/test/keychain/keychain.spec.js @@ -296,7 +296,7 @@ describe('keychain', () => { it('requires plain data as a Uint8Array', async () => { const err = await ks.cms.encrypt(rsaKeyName, 'plain data').then(fail, err => err) expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_INVALID_PARAMS') + expect(err).to.have.property('code', 'ERR_INVALID_PARAMETERS') }) it('encrypts', async () => { @@ -308,7 +308,7 @@ describe('keychain', () => { it('is a PKCS #7 message', async () => { const err = await ks.cms.decrypt('not CMS').then(fail, err => err) expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_INVALID_PARAMS') + expect(err).to.have.property('code', 'ERR_INVALID_PARAMETERS') }) it('is a PKCS #7 binary message', async () => { From 3b683e715686163e229b7b5c3a892327dfd4fc63 Mon Sep 17 00:00:00 2001 From: Robert Kiel Date: Tue, 7 Dec 2021 18:51:48 +0100 Subject: [PATCH 280/447] fix: fix uncaught promise rejection when finding peers (#1044) Do not abort all attempts to find peers when `findPeers` on one router throws synchronously Co-authored-by: Robert Kiel Co-authored-by: achingbrain --- src/peer-routing.js | 9 ++- test/peer-routing/peer-routing.node.js | 89 ++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 2 deletions(-) diff --git a/src/peer-routing.js b/src/peer-routing.js index a49e847c1c..09de96df6e 100644 --- a/src/peer-routing.js +++ b/src/peer-routing.js @@ -114,10 +114,15 @@ class PeerRouting { const output = await pipe( merge( - ...this._routers.map(router => [router.findPeer(id, options)]) + ...this._routers.map(router => (async function * () { + try { + yield await router.findPeer(id, options) + } catch (err) { + log.error(err) + } + })()) ), (source) => filter(source, Boolean), - // @ts-ignore findPeer resolves a Promise (source) => storeAddresses(source, this._peerStore), (source) => first(source) ) diff --git a/test/peer-routing/peer-routing.node.js b/test/peer-routing/peer-routing.node.js index fd76ee792d..a1c66917f3 100644 --- a/test/peer-routing/peer-routing.node.js +++ b/test/peer-routing/peer-routing.node.js @@ -106,6 +106,95 @@ describe('peer-routing', () => { .to.eventually.be.rejected() .and.to.have.property('code', 'ERR_FIND_SELF') }) + + it('should handle error thrown synchronously during find peer', async () => { + const unknownPeers = await peerUtils.createPeerId({ number: 1, fixture: false }) + + nodes[0].peerRouting._routers = [{ + findPeer () { + throw new Error('Thrown sync') + } + }] + + await expect(nodes[0].peerRouting.findPeer(unknownPeers[0])) + .to.eventually.be.rejected() + .and.to.have.property('code', 'ERR_NOT_FOUND') + }) + + it('should handle error thrown asynchronously during find peer', async () => { + const unknownPeers = await peerUtils.createPeerId({ number: 1, fixture: false }) + + nodes[0].peerRouting._routers = [{ + async findPeer () { + throw new Error('Thrown async') + } + }] + + await expect(nodes[0].peerRouting.findPeer(unknownPeers[0])) + .to.eventually.be.rejected() + .and.to.have.property('code', 'ERR_NOT_FOUND') + }) + + it('should handle error thrown asynchronously after delay during find peer', async () => { + const unknownPeers = await peerUtils.createPeerId({ number: 1, fixture: false }) + + nodes[0].peerRouting._routers = [{ + async findPeer () { + await delay(100) + throw new Error('Thrown async after delay') + } + }] + + await expect(nodes[0].peerRouting.findPeer(unknownPeers[0])) + .to.eventually.be.rejected() + .and.to.have.property('code', 'ERR_NOT_FOUND') + }) + + it('should return value when one router errors synchronously and another returns a value', async () => { + const [peer] = await peerUtils.createPeerId({ number: 1, fixture: false }) + + nodes[0].peerRouting._routers = [{ + findPeer () { + throw new Error('Thrown sync') + } + }, { + async findPeer () { + return Promise.resolve({ + id: peer, + multiaddrs: [] + }) + } + }] + + await expect(nodes[0].peerRouting.findPeer(peer)) + .to.eventually.deep.equal({ + id: peer, + multiaddrs: [] + }) + }) + + it('should return value when one router errors asynchronously and another returns a value', async () => { + const [peer] = await peerUtils.createPeerId({ number: 1, fixture: false }) + + nodes[0].peerRouting._routers = [{ + async findPeer () { + throw new Error('Thrown sync') + } + }, { + async findPeer () { + return Promise.resolve({ + id: peer, + multiaddrs: [] + }) + } + }] + + await expect(nodes[0].peerRouting.findPeer(peer)) + .to.eventually.deep.equal({ + id: peer, + multiaddrs: [] + }) + }) }) describe('via delegate router', () => { From 1f1bbc0ee6b2a90cb636e5f022ed2183b955d4f4 Mon Sep 17 00:00:00 2001 From: Marston Connell <34043723+TheMarstonConnell@users.noreply.github.com> Date: Tue, 7 Dec 2021 16:05:33 -0500 Subject: [PATCH 281/447] docs: naming error in the documentation (#1056) Changed LevelStore to LevelDatastore --- doc/CONFIGURATION.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 7d4523f76f..e6038c44c5 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -497,9 +497,9 @@ const Libp2p = require('libp2p') const TCP = require('libp2p-tcp') const MPLEX = require('libp2p-mplex') const { NOISE } = require('libp2p-noise') -const LevelStore = require('datastore-level') +const LevelDatastore = require('datastore-level') -const datastore = new LevelStore('path/to/store') +const datastore = new LevelDatastore('path/to/store') await datastore.open() const node = await Libp2p.create({ @@ -672,9 +672,9 @@ const Libp2p = require('libp2p') const TCP = require('libp2p-tcp') const MPLEX = require('libp2p-mplex') const { NOISE } = require('libp2p-noise') -const LevelStore = require('datastore-level') +const LevelDatastore = require('datastore-level') -const datastore = new LevelStore('path/to/store') +const datastore = new LevelDatastore('path/to/store') const dsInstant = await datastore.open() const node = await Libp2p.create({ From 103818733e5490f92da065a4d1aecee990cf8734 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Dec 2021 21:05:53 +0000 Subject: [PATCH 282/447] chore(deps-dev): bump @chainsafe/libp2p-noise from 4.1.1 to 5.0.0 (#1053) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d8fb051072..40a0f7e97c 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,7 @@ "xsalsa20": "^1.1.0" }, "devDependencies": { - "@chainsafe/libp2p-noise": "^4.0.0", + "@chainsafe/libp2p-noise": "^5.0.0", "@nodeutils/defaults-deep": "^1.1.0", "@types/es6-promisify": "^6.0.0", "@types/node": "^16.0.1", From b539f9b655f7f4ce03a09c207118ce7194df4d41 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Dec 2021 21:06:06 +0000 Subject: [PATCH 283/447] chore(deps-dev): bump libp2p-gossipsub from 0.11.4 to 0.12.1 (#1045) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 40a0f7e97c..83b05384b6 100644 --- a/package.json +++ b/package.json @@ -153,7 +153,7 @@ "libp2p-delegated-content-routing": "^0.11.0", "libp2p-delegated-peer-routing": "^0.11.0", "libp2p-floodsub": "^0.27.0", - "libp2p-gossipsub": "^0.11.0", + "libp2p-gossipsub": "^0.12.1", "libp2p-interfaces-compliance-tests": "^2.0.1", "libp2p-interop": "^0.5.0", "libp2p-kad-dht": "^0.27.1", From 1b46f47fdbb98b6c40349ed6898c54c9cd985e9f Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 8 Dec 2021 08:38:17 +0000 Subject: [PATCH 284/447] chore: run node tests in ci (#1057) Looks like this project stopped running the `test:node` npm script when it was migrated to gh actions. Re-enable it and fix all the related test failures. --- .github/workflows/main.yml | 6 +- package.json | 2 +- src/circuit/index.js | 4 +- src/config.js | 8 +- src/content-routing/index.js | 4 +- src/dht/dht-peer-routing.js | 8 +- src/errors.js | 2 +- src/index.js | 7 - src/peer-routing.js | 4 +- test/content-routing/content-routing.node.js | 28 +++- .../content-routing/dht/configuration.node.js | 14 +- test/content-routing/dht/operation.node.js | 24 ++-- test/content-routing/dht/utils.js | 8 +- test/content-routing/utils.js | 3 - test/nat-manager/nat-manager.node.js | 6 +- test/peer-discovery/index.node.js | 9 +- test/peer-routing/peer-routing.node.js | 127 ++++++++++-------- test/peer-routing/utils.js | 3 - test/transports/transport-manager.node.js | 11 +- test/ts-use/package.json | 2 +- test/ts-use/src/main.ts | 5 - 21 files changed, 151 insertions(+), 134 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ab7a340367..e9ecbb6043 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -37,7 +37,7 @@ jobs: with: node-version: ${{ matrix.node }} - run: npm install - - run: npx aegir test -t node --cov --bail + - run: npm run test:node -- --cov --bail - uses: codecov/codecov-action@v1 test-chrome: needs: check @@ -48,7 +48,7 @@ jobs: with: node-version: lts/* - run: npm install - - run: npx aegir test -t browser -t webworker --bail + - run: npm run test:browser -- -t browser -t webworker --bail test-firefox: needs: check runs-on: ubuntu-latest @@ -58,7 +58,7 @@ jobs: with: node-version: lts/* - run: npm install - - run: npx aegir test -t browser -t webworker --bail -- --browser firefox + - run: npm run test:browser -- -t browser -t webworker --bail -- --browser firefox test-ts: needs: check runs-on: ubuntu-latest diff --git a/package.json b/package.json index 83b05384b6..82b207c2fa 100644 --- a/package.json +++ b/package.json @@ -151,7 +151,7 @@ "libp2p": ".", "libp2p-bootstrap": "^0.14.0", "libp2p-delegated-content-routing": "^0.11.0", - "libp2p-delegated-peer-routing": "^0.11.0", + "libp2p-delegated-peer-routing": "^0.11.1", "libp2p-floodsub": "^0.27.0", "libp2p-gossipsub": "^0.12.1", "libp2p-interfaces-compliance-tests": "^2.0.1", diff --git a/src/circuit/index.js b/src/circuit/index.js index 4d180b6edd..06d4107a1d 100644 --- a/src/circuit/index.js +++ b/src/circuit/index.js @@ -4,7 +4,7 @@ const debug = require('debug') const log = Object.assign(debug('libp2p:relay'), { error: debug('libp2p:relay:err') }) - +const { codes } = require('./../errors') const { setDelayedInterval, clearDelayedInterval @@ -88,7 +88,7 @@ class Relay { const cid = await namespaceToCid(RELAY_RENDEZVOUS_NS) await this._libp2p.contentRouting.provide(cid) } catch (/** @type {any} */ err) { - if (err.code === 'NO_ROUTERS_AVAILABLE') { + if (err.code === codes.ERR_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() diff --git a/src/config.js b/src/config.js index 9542c70036..0d9bbc4a84 100644 --- a/src/config.js +++ b/src/config.js @@ -60,13 +60,7 @@ const DefaultConfig = { protocolPrefix: 'ipfs', dht: { enabled: false, - kBucketSize: 20, - randomWalk: { - enabled: false, // disabled waiting for https://github.com/libp2p/js-libp2p-kad-dht/issues/86 - queriesPerPeriod: 1, - interval: 300e3, - timeout: 10e3 - } + kBucketSize: 20 }, nat: { enabled: true, diff --git a/src/content-routing/index.js b/src/content-routing/index.js index 924b987344..df04225d7c 100644 --- a/src/content-routing/index.js +++ b/src/content-routing/index.js @@ -54,7 +54,7 @@ class ContentRouting { */ async * findProviders (key, options = {}) { if (!this.routers.length) { - throw errCode(new Error('No content this.routers available'), 'NO_ROUTERS_AVAILABLE') + throw errCode(new Error('No content this.routers available'), codes.ERR_NO_ROUTERS_AVAILABLE) } yield * pipe( @@ -77,7 +77,7 @@ class ContentRouting { */ async provide (key) { if (!this.routers.length) { - throw errCode(new Error('No content routers available'), 'NO_ROUTERS_AVAILABLE') + throw errCode(new Error('No content routers available'), codes.ERR_NO_ROUTERS_AVAILABLE) } await Promise.all(this.routers.map((router) => router.provide(key))) diff --git a/src/dht/dht-peer-routing.js b/src/dht/dht-peer-routing.js index 762abc80fa..61d79f924d 100644 --- a/src/dht/dht-peer-routing.js +++ b/src/dht/dht-peer-routing.js @@ -27,8 +27,12 @@ class DHTPeerRouting { */ async findPeer (peerId, options = {}) { for await (const event of this._dht.findPeer(peerId, options)) { - if (event.name === 'FINAL_PEER') { - return event.peer + if (event.name === 'PEER_RESPONSE') { + const peer = event.closer.find(peerData => peerData.id.equals(peerId)) + + if (peer) { + return peer + } } } diff --git a/src/errors.js b/src/errors.js index a5db3901b1..61f308667e 100644 --- a/src/errors.js +++ b/src/errors.js @@ -38,7 +38,7 @@ exports.codes = { ERR_INVALID_MULTIADDR: 'ERR_INVALID_MULTIADDR', ERR_SIGNATURE_NOT_VALID: 'ERR_SIGNATURE_NOT_VALID', ERR_FIND_SELF: 'ERR_FIND_SELF', - ERR_NO_ROUTERS: 'ERR_NO_ROUTERS', + ERR_NO_ROUTERS_AVAILABLE: 'ERR_NO_ROUTERS_AVAILABLE', ERR_CONNECTION_NOT_MULTIPLEXED: 'ERR_CONNECTION_NOT_MULTIPLEXED', ERR_NO_DIAL_TOKENS: 'ERR_NO_DIAL_TOKENS', ERR_KEYCHAIN_REQUIRED: 'ERR_KEYCHAIN_REQUIRED', diff --git a/src/index.js b/src/index.js index 2b42822312..5e890479ea 100644 --- a/src/index.js +++ b/src/index.js @@ -56,16 +56,9 @@ const { updateSelfPeerRecord } = require('./record/utils') * @property {MuxedStream} stream * @property {string} protocol * - * @typedef {Object} RandomWalkOptions - * @property {boolean} [enabled = false] - * @property {number} [queriesPerPeriod = 1] - * @property {number} [interval = 300e3] - * @property {number} [timeout = 10e3] - * * @typedef {Object} DhtOptions * @property {boolean} [enabled = false] * @property {number} [kBucketSize = 20] - * @property {RandomWalkOptions} [randomWalk] * @property {boolean} [clientMode] * @property {import('libp2p-interfaces/src/types').DhtSelectors} [selectors] * @property {import('libp2p-interfaces/src/types').DhtValidators} [validators] diff --git a/src/peer-routing.js b/src/peer-routing.js index 09de96df6e..8225a5a042 100644 --- a/src/peer-routing.js +++ b/src/peer-routing.js @@ -105,7 +105,7 @@ class PeerRouting { */ async findPeer (id, options) { // eslint-disable-line require-await if (!this._routers.length) { - throw errCode(new Error('No peer routers available'), errors.codes.ERR_NO_ROUTERS) + throw errCode(new Error('No peer routers available'), errors.codes.ERR_NO_ROUTERS_AVAILABLE) } if (id.toB58String() === this._peerId.toB58String()) { @@ -145,7 +145,7 @@ class PeerRouting { */ async * getClosestPeers (key, options = { timeout: 30e3 }) { if (!this._routers.length) { - throw errCode(new Error('No peer routers available'), errors.codes.ERR_NO_ROUTERS) + throw errCode(new Error('No peer routers available'), errors.codes.ERR_NO_ROUTERS_AVAILABLE) } if (options.timeout) { diff --git a/test/content-routing/content-routing.node.js b/test/content-routing/content-routing.node.js index 7206b19906..e35651e4c6 100644 --- a/test/content-routing/content-routing.node.js +++ b/test/content-routing/content-routing.node.js @@ -36,14 +36,14 @@ describe('content-routing', () => { throw new Error('.findProviders should return an error') } catch (/** @type {any} */ err) { expect(err).to.exist() - expect(err.code).to.equal('NO_ROUTERS_AVAILABLE') + expect(err.code).to.equal('ERR_NO_ROUTERS_AVAILABLE') } }) it('.provide should return an error', async () => { await expect(node.contentRouting.provide('a cid')) .to.eventually.be.rejected() - .and.to.have.property('code', 'NO_ROUTERS_AVAILABLE') + .and.to.have.property('code', 'ERR_NO_ROUTERS_AVAILABLE') }) }) @@ -87,8 +87,11 @@ describe('content-routing', () => { sinon.stub(nodes[0]._dht, 'findProviders').callsFake(function * () { deferred.resolve() yield { - id: providerPeerId, - multiaddrs: [] + name: 'PROVIDER', + providers: [{ + id: providerPeerId, + multiaddrs: [] + }] } }) @@ -361,7 +364,12 @@ describe('content-routing', () => { } sinon.stub(node._dht, 'findProviders').callsFake(async function * () { - yield result1 + yield { + name: 'PROVIDER', + providers: [ + result1 + ] + } }) sinon.stub(delegate, 'findProviders').callsFake(async function * () { yield result2 @@ -382,7 +390,8 @@ describe('content-routing', () => { const dhtDeferred = pDefer() const delegatedDeferred = pDefer() - sinon.stub(node._dht, 'provide').callsFake(() => { + sinon.stub(node._dht, 'provide').callsFake(async function * () { + yield dhtDeferred.resolve() }) @@ -406,7 +415,12 @@ describe('content-routing', () => { }] sinon.stub(node._dht, 'findProviders').callsFake(function * () { - yield results[0] + yield { + name: 'PROVIDER', + providers: [ + results[0] + ] + } }) sinon.stub(delegate, 'findProviders').callsFake(function * () { // eslint-disable-line require-yield diff --git a/test/content-routing/dht/configuration.node.js b/test/content-routing/dht/configuration.node.js index 9213d6e22d..ed22d3e9c6 100644 --- a/test/content-routing/dht/configuration.node.js +++ b/test/content-routing/dht/configuration.node.js @@ -38,13 +38,13 @@ describe('DHT subsystem is configurable', () => { }) libp2p = await create(customOptions) - expect(libp2p._dht.isStarted).to.equal(false) + expect(libp2p._dht.isStarted()).to.equal(false) await libp2p.start() - expect(libp2p._dht.isStarted).to.equal(true) + expect(libp2p._dht.isStarted()).to.equal(true) await libp2p.stop() - expect(libp2p._dht.isStarted).to.equal(false) + expect(libp2p._dht.isStarted()).to.equal(false) }) it('should not start if disabled once libp2p starts', async () => { @@ -63,10 +63,10 @@ describe('DHT subsystem is configurable', () => { }) libp2p = await create(customOptions) - expect(libp2p._dht.isStarted).to.equal(false) + expect(libp2p._dht.isStarted()).to.equal(false) await libp2p.start() - expect(libp2p._dht.isStarted).to.equal(false) + expect(libp2p._dht.isStarted()).to.equal(false) }) it('should allow a manual start', async () => { @@ -86,9 +86,9 @@ describe('DHT subsystem is configurable', () => { libp2p = await create(customOptions) await libp2p.start() - expect(libp2p._dht.isStarted).to.equal(false) + expect(libp2p._dht.isStarted()).to.equal(false) await libp2p._dht.start() - expect(libp2p._dht.isStarted).to.equal(true) + expect(libp2p._dht.isStarted()).to.equal(true) }) }) diff --git a/test/content-routing/dht/operation.node.js b/test/content-routing/dht/operation.node.js index c1cfbb13f4..646e843176 100644 --- a/test/content-routing/dht/operation.node.js +++ b/test/content-routing/dht/operation.node.js @@ -60,8 +60,8 @@ describe('DHT subsystem operates correctly', () => { expect(connection).to.exist() return Promise.all([ - pWaitFor(() => libp2p._dht.routingTable.size === 1), - pWaitFor(() => remoteLibp2p._dht.routingTable.size === 1) + pWaitFor(() => libp2p._dht._lan._routingTable.size === 1), + pWaitFor(() => remoteLibp2p._dht._lan._routingTable.size === 1) ]) }) @@ -71,14 +71,14 @@ describe('DHT subsystem operates correctly', () => { await libp2p.dialProtocol(remAddr, subsystemMulticodecs) await Promise.all([ - pWaitFor(() => libp2p._dht.routingTable.size === 1), - pWaitFor(() => remoteLibp2p._dht.routingTable.size === 1) + pWaitFor(() => libp2p._dht._lan._routingTable.size === 1), + pWaitFor(() => remoteLibp2p._dht._lan._routingTable.size === 1) ]) await libp2p.contentRouting.put(key, value) - const fetchedValue = await remoteLibp2p.contentRouting.get(key) - expect(fetchedValue).to.eql(value) + const fetchedValue = await remoteLibp2p.contentRouting.get(key) + expect(fetchedValue).to.have.property('val').that.equalBytes(value) }) }) @@ -119,11 +119,13 @@ describe('DHT subsystem operates correctly', () => { const connection = await libp2p.dial(remAddr) expect(connection).to.exist() - expect(libp2p._dht.routingTable.size).to.be.eql(0) - expect(remoteLibp2p._dht.routingTable.size).to.be.eql(0) + expect(libp2p._dht._lan._routingTable.size).to.be.eql(0) await remoteLibp2p._dht.start() - return pWaitFor(() => libp2p._dht.routingTable.size === 1) + // should be 0 directly after start - TODO this may be susceptible to timing bugs, we should have + // the ability to report stats on the DHT routing table instead of reaching into it's heart like this + expect(remoteLibp2p._dht._lan._routingTable.size).to.be.eql(0) + return pWaitFor(() => libp2p._dht._lan._routingTable.size === 1) }) it('should put on a peer and get from the other', async () => { @@ -133,12 +135,12 @@ describe('DHT subsystem operates correctly', () => { const value = uint8ArrayFromString('world') await remoteLibp2p._dht.start() - await pWaitFor(() => libp2p._dht.routingTable.size === 1) + await pWaitFor(() => libp2p._dht._lan._routingTable.size === 1) await libp2p.contentRouting.put(key, value) const fetchedValue = await remoteLibp2p.contentRouting.get(key) - expect(fetchedValue).to.eql(value) + expect(fetchedValue).to.have.property('val').that.equalBytes(value) }) }) }) diff --git a/test/content-routing/dht/utils.js b/test/content-routing/dht/utils.js index c879f8d068..0a37de41ae 100644 --- a/test/content-routing/dht/utils.js +++ b/test/content-routing/dht/utils.js @@ -1,7 +1,6 @@ 'use strict' const KadDht = require('libp2p-kad-dht') -const { multicodec } = require('libp2p-kad-dht') const Crypto = require('../../../src/insecure/plaintext') const Muxer = require('libp2p-mplex') const Transport = require('libp2p-tcp') @@ -25,13 +24,12 @@ const subsystemOptions = mergeOptions(baseOptions, { config: { dht: { kBucketSize: 20, - randomWalk: { - enabled: true - }, enabled: true } } }) module.exports.subsystemOptions = subsystemOptions -module.exports.subsystemMulticodecs = [multicodec] +module.exports.subsystemMulticodecs = [ + '/ipfs/lan/kad/1.0.0' +] diff --git a/test/content-routing/utils.js b/test/content-routing/utils.js index 120f7281df..7b43d05046 100644 --- a/test/content-routing/utils.js +++ b/test/content-routing/utils.js @@ -13,9 +13,6 @@ const routingOptions = mergeOptions(baseOptions, { config: { dht: { kBucketSize: 20, - randomWalk: { - enabled: true - }, enabled: true } } diff --git a/test/nat-manager/nat-manager.node.js b/test/nat-manager/nat-manager.node.js index 5f5562e0b0..9dcf2b574e 100644 --- a/test/nat-manager/nat-manager.node.js +++ b/test/nat-manager/nat-manager.node.js @@ -244,6 +244,10 @@ describe('Nat Manager (TCP)', () => { }) it('shuts the nat api down when stopping', async function () { + if (process.env.CI) { + return this.skip('CI environments will not let us map external ports') + } + function findRoutableAddress () { const interfaces = networkInterfaces() @@ -261,7 +265,7 @@ describe('Nat Manager (TCP)', () => { if (!addr) { // skip test if no non-loopback address is found - this.skip() + return this.skip() } const { diff --git a/test/peer-discovery/index.node.js b/test/peer-discovery/index.node.js index c7db964603..bdf4587960 100644 --- a/test/peer-discovery/index.node.js +++ b/test/peer-discovery/index.node.js @@ -161,20 +161,13 @@ describe('peer discovery scenarios', () => { autoDial: false }, dht: { - randomWalk: { - enabled: false, - delay: 1000, // start the first query quickly - interval: 10000, - timeout: 5000 - }, enabled: true } } }) const localConfig = getConfig(peerId) - // Only run random walk on our local node - localConfig.config.dht.randomWalk.enabled = true + libp2p = new Libp2p(localConfig) const remoteLibp2p1 = new Libp2p(getConfig(remotePeerId1)) diff --git a/test/peer-routing/peer-routing.node.js b/test/peer-routing/peer-routing.node.js index a1c66917f3..f7835898a6 100644 --- a/test/peer-routing/peer-routing.node.js +++ b/test/peer-routing/peer-routing.node.js @@ -36,7 +36,7 @@ describe('peer-routing', () => { it('.findPeer should return an error', async () => { await expect(node.peerRouting.findPeer('a cid')) .to.eventually.be.rejected() - .and.to.have.property('code', 'NO_ROUTERS_AVAILABLE') + .and.to.have.property('code', 'ERR_NO_ROUTERS_AVAILABLE') }) it('.getClosestPeers should return an error', async () => { @@ -45,7 +45,7 @@ describe('peer-routing', () => { throw new Error('.getClosestPeers should return an error') } catch (/** @type {any} */ err) { expect(err).to.exist() - expect(err.code).to.equal('NO_ROUTERS_AVAILABLE') + expect(err.code).to.equal('ERR_NO_ROUTERS_AVAILABLE') } }) }) @@ -72,33 +72,38 @@ describe('peer-routing', () => { after(() => Promise.all(nodes.map((n) => n.stop()))) - it('should use the nodes dht', () => { - const deferred = pDefer() - - sinon.stub(nodes[0]._dht, 'findPeer').callsFake(() => { - deferred.resolve() - return nodes[1].peerId + it('should use the nodes dht', async () => { + sinon.stub(nodes[0]._dht, 'findPeer').callsFake(async function * () { + yield { + name: 'PEER_RESPONSE', + closer: [{ + id: nodes[1].peerId, + multiaddrs: [] + }] + } }) - nodes[0].peerRouting.findPeer() - return deferred.promise + expect(nodes[0]._dht.findPeer.called).to.be.false() + await nodes[0].peerRouting.findPeer(nodes[1].peerId) + expect(nodes[0]._dht.findPeer.called).to.be.true() + nodes[0]._dht.findPeer.restore() }) it('should use the nodes dht to get the closest peers', async () => { - const deferred = pDefer() - const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) - - sinon.stub(nodes[0]._dht, 'getClosestPeers').callsFake(function * () { - deferred.resolve() + sinon.stub(nodes[0]._dht, 'getClosestPeers').callsFake(async function * () { yield { - id: remotePeerId, - multiaddrs: [] + name: 'PEER_RESPONSE', + closer: [{ + id: nodes[1].peerId, + multiaddrs: [] + }] } }) - await nodes[0].peerRouting.getClosestPeers().next() - - return deferred.promise + expect(nodes[0]._dht.getClosestPeers.called).to.be.false() + await drain(nodes[0].peerRouting.getClosestPeers(nodes[1].peerId)) + expect(nodes[0]._dht.getClosestPeers.called).to.be.true() + nodes[0]._dht.getClosestPeers.restore() }) it('should error when peer tries to find itself', async () => { @@ -234,36 +239,35 @@ describe('peer-routing', () => { }) it('should use the delegate router to find peers', async () => { - const deferred = pDefer() const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) sinon.stub(delegate, 'findPeer').callsFake(() => { - deferred.resolve() return { id: remotePeerId, multiaddrs: [] } }) - await node.peerRouting.findPeer() - return deferred.promise + expect(delegate.findPeer.called).to.be.false() + await node.peerRouting.findPeer(remotePeerId) + expect(delegate.findPeer.called).to.be.true() + delegate.findPeer.restore() }) it('should use the delegate router to get the closest peers', async () => { - const deferred = pDefer() const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) sinon.stub(delegate, 'getClosestPeers').callsFake(function * () { - deferred.resolve() yield { id: remotePeerId, multiaddrs: [] } }) - await node.peerRouting.getClosestPeers().next() - - return deferred.promise + expect(delegate.getClosestPeers.called).to.be.false() + await drain(node.peerRouting.getClosestPeers(remotePeerId)) + expect(delegate.getClosestPeers.called).to.be.true() + delegate.getClosestPeers.restore() }) it('should be able to find a peer', async () => { @@ -289,7 +293,7 @@ describe('peer-routing', () => { }) it('should error when a peer cannot be found', async () => { - const peerKey = 'key of a peer not on the network' + const peerId = await PeerId.create({ keyType: 'ed25519' }) const mockApi = nock('http://0.0.0.0:60197') .post('/api/v0/dht/findpeer') .query(true) @@ -298,20 +302,20 @@ describe('peer-routing', () => { 'X-Chunked-Output', '1' ]) - await expect(node.peerRouting.findPeer(peerKey)) + await expect(node.peerRouting.findPeer(peerId)) .to.eventually.be.rejected() expect(mockApi.isDone()).to.equal(true) }) it('should handle errors from the api', async () => { - const peerKey = 'key of a peer not on the network' + const peerId = await PeerId.create({ keyType: 'ed25519' }) const mockApi = nock('http://0.0.0.0:60197') .post('/api/v0/dht/findpeer') .query(true) .reply(502) - await expect(node.peerRouting.findPeer(peerKey)) + await expect(node.peerRouting.findPeer(peerId)) .to.eventually.be.rejected() expect(mockApi.isDone()).to.equal(true) @@ -319,7 +323,6 @@ describe('peer-routing', () => { it('should be able to get the closest peers', async () => { const peerId = await PeerId.create({ keyType: 'ed25519' }) - const closest1 = '12D3KooWLewYMMdGWAtuX852n4rgCWkK7EBn4CWbwwBzhsVoKxk3' const closest2 = '12D3KooWDtoQbpKhtnWddfj72QmpFvvLDTsBLTFkjvgQm6cde2AK' @@ -338,15 +341,12 @@ describe('peer-routing', () => { 'X-Chunked-Output', '1' ]) - const closestPeers = [] - for await (const peer of node.peerRouting.getClosestPeers(peerId.id, { timeout: 1000 })) { - closestPeers.push(peer) - } + const closestPeers = await all(node.peerRouting.getClosestPeers(peerId.id, { timeout: 1000 })) expect(closestPeers).to.have.length(2) - expect(closestPeers[0].id.toB58String()).to.equal(closest2) + expect(closestPeers[0].id.toB58String()).to.equal(closest1) expect(closestPeers[0].multiaddrs).to.have.lengthOf(2) - expect(closestPeers[1].id.toB58String()).to.equal(closest1) + expect(closestPeers[1].id.toB58String()).to.equal(closest2) expect(closestPeers[1].multiaddrs).to.have.lengthOf(2) expect(mockApi.isDone()).to.equal(true) }) @@ -405,7 +405,7 @@ describe('peer-routing', () => { multiaddrs: [] } - sinon.stub(node._dht, 'findPeer').callsFake(() => {}) + sinon.stub(node._dht, 'findPeer').callsFake(async function * () {}) sinon.stub(delegate, 'findPeer').callsFake(() => { return results }) @@ -423,7 +423,8 @@ describe('peer-routing', () => { const defer = pDefer() - sinon.stub(node._dht, 'findPeer').callsFake(async () => { + sinon.stub(node._dht, 'findPeer').callsFake(async function * () { + yield await defer.promise }) sinon.stub(delegate, 'findPeer').callsFake(() => { @@ -438,29 +439,34 @@ describe('peer-routing', () => { it('should not wait for the delegate to return if the dht does first', async () => { const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) - const results = { + const result = { id: remotePeerId, multiaddrs: [] } const defer = pDefer() - sinon.stub(node._dht, 'findPeer').callsFake(() => { - return results + sinon.stub(node._dht, 'findPeer').callsFake(async function * () { + yield { + name: 'PEER_RESPONSE', + closer: [ + result + ] + } }) sinon.stub(delegate, 'findPeer').callsFake(async () => { await defer.promise }) const peer = await node.peerRouting.findPeer(remotePeerId) - expect(peer).to.eql(results) + expect(peer).to.eql(result) defer.resolve() }) it('should store the addresses of the found peer', async () => { const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) - const results = { + const result = { id: remotePeerId, multiaddrs: [ new Multiaddr('/ip4/123.123.123.123/tcp/38982') @@ -469,14 +475,19 @@ describe('peer-routing', () => { const spy = sinon.spy(node.peerStore.addressBook, 'add') - sinon.stub(node._dht, 'findPeer').callsFake(() => { - return results + sinon.stub(node._dht, 'findPeer').callsFake(async function * () { + yield { + name: 'PEER_RESPONSE', + closer: [ + result + ] + } }) sinon.stub(delegate, 'findPeer').callsFake(() => {}) await node.peerRouting.findPeer(remotePeerId) - expect(spy.calledWith(results.id, results.multiaddrs)).to.be.true() + expect(spy.calledWith(result.id, result.multiaddrs)).to.be.true() }) it('should use the delegate if the dht fails to get the closest peer', async () => { @@ -576,8 +587,18 @@ describe('peer-routing', () => { sinon.spy(node.peerStore.addressBook, 'add') sinon.stub(node._dht, 'getClosestPeers').callsFake(function * () { - yield results[0] - yield results[1] + yield { + name: 'PEER_RESPONSE', + closer: [ + results[0] + ] + } + yield { + name: 'PEER_RESPONSE', + closer: [ + results[1] + ] + } }) await node.start() @@ -611,7 +632,7 @@ describe('peer-routing', () => { started: false }) - sinon.stub(node._dht, 'getClosestPeers').callsFake(function * () { + sinon.stub(node._dht, 'getClosestPeers').callsFake(async function * () { yield throw new Error('should not be called') }) diff --git a/test/peer-routing/utils.js b/test/peer-routing/utils.js index 120f7281df..7b43d05046 100644 --- a/test/peer-routing/utils.js +++ b/test/peer-routing/utils.js @@ -13,9 +13,6 @@ const routingOptions = mergeOptions(baseOptions, { config: { dht: { kBucketSize: 20, - randomWalk: { - enabled: true - }, enabled: true } } diff --git a/test/transports/transport-manager.node.js b/test/transports/transport-manager.node.js index 13854ce031..49876a0554 100644 --- a/test/transports/transport-manager.node.js +++ b/test/transports/transport-manager.node.js @@ -13,6 +13,7 @@ const { Multiaddr } = require('multiaddr') const mockUpgrader = require('../utils/mockUpgrader') const sinon = require('sinon') const Peers = require('../fixtures/peers') +const pWaitFor = require('p-wait-for') const addrs = [ new Multiaddr('/ip4/127.0.0.1/tcp/0'), new Multiaddr('/ip4/127.0.0.1/tcp/0') @@ -72,9 +73,13 @@ describe('Transport Manager (TCP)', () => { 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() + // Should created Self Peer record on new listen address, but it is done async + // with no event so we have to wait a bit + await pWaitFor(async () => { + signedPeerRecord = await tm.libp2p.peerStore.addressBook.getPeerRecord(localPeer) + + return signedPeerRecord != null + }, { interval: 100, timeout: 2000 }) const record = PeerRecord.createFromProtobuf(signedPeerRecord.payload) expect(record).to.exist() diff --git a/test/ts-use/package.json b/test/ts-use/package.json index 8593e19282..ac98a7b4e3 100644 --- a/test/ts-use/package.json +++ b/test/ts-use/package.json @@ -7,7 +7,7 @@ "libp2p": "file:../..", "libp2p-bootstrap": "^0.13.0", "libp2p-delegated-content-routing": "^0.11.0", - "libp2p-delegated-peer-routing": "^0.10.0", + "libp2p-delegated-peer-routing": "^0.11.1", "libp2p-gossipsub": "^0.9.0", "libp2p-interfaces": "^1.0.1", "libp2p-kad-dht": "^0.26.5", diff --git a/test/ts-use/src/main.ts b/test/ts-use/src/main.ts index d75a79f4ba..17c5e4d681 100644 --- a/test/ts-use/src/main.ts +++ b/test/ts-use/src/main.ts @@ -123,11 +123,6 @@ async function main() { dht: { enabled: true, kBucketSize: 20, - randomWalk: { - enabled: true, // Allows to disable discovery (enabled by default) - interval: 300e3, - timeout: 10e3 - }, clientMode: true, validators: { pk: Libp2pRecord.validator.validators.pk From bdc9f16d0cbe56ccf26822f11068e7795bcef046 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 10 Dec 2021 12:42:09 +0000 Subject: [PATCH 285/447] fix: clean up pending dial targets (#1059) If the `Promise.race` throws, execution of the function is terminated so the pending dial target is never removed from the map and we leak memory. This can happen when there are invalid multiaddrs or when a peer reports more dialable addresses than the threshold. Instead wrap the `Promise.race` in a `try/finally` which will always remove the pending dial target in the event of success or failure. --- src/dialer/index.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/dialer/index.js b/src/dialer/index.js index 0cd5e088f8..b4dd87c26d 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -155,14 +155,16 @@ class Dialer { this._pendingDialTargets.set(id, { resolve, reject }) }) - const dialTarget = await Promise.race([ - this._createDialTarget(peer), - cancellablePromise - ]) - - this._pendingDialTargets.delete(id) + try { + const dialTarget = await Promise.race([ + this._createDialTarget(peer), + cancellablePromise + ]) - return dialTarget + return dialTarget + } finally { + this._pendingDialTargets.delete(id) + } } /** From f8e8023aedefb1cad4475b80fd191eb63408b950 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Mon, 13 Dec 2021 09:03:54 +0000 Subject: [PATCH 286/447] chore: update contributors --- package.json | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 82b207c2fa..f0c7c51162 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.35.2", + "version": "0.35.3", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", @@ -184,21 +184,21 @@ "dirkmc ", "Chris Dostert ", "Volker Mische ", - "zeim839 <50573884+zeim839@users.noreply.github.com>", + "Robert Kiel ", "Richard Littauer ", + "zeim839 <50573884+zeim839@users.noreply.github.com>", "a1300 ", "Ryan Bell ", "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", + "Andrew Nesbitt ", + "Franck Royer ", + "Thomas Eizinger ", "Giovanni T. Parra ", "acolytec3 <17355484+acolytec3@users.noreply.github.com>", - "Franck Royer ", + "Alan Smithee ", "Elven ", - "Robert Kiel ", - "Andrew Nesbitt ", "Samlior ", - "Thomas Eizinger ", "Didrik Nordström ", - "Smite Chow ", "Soeren ", "Sönke Hahn ", "TJKoury ", @@ -218,7 +218,6 @@ "shresthagrawal <34920931+shresthagrawal@users.noreply.github.com>", "swedneck <40505480+swedneck@users.noreply.github.com>", "greenSnot ", - "Alan Smithee ", "Aleksei ", "Bernd Strehl ", "Chris Bratlien ", @@ -243,10 +242,12 @@ "Lars Gierth ", "Leask Wong ", "Marcin Tojek ", + "Marston Connell <34043723+TheMarstonConnell@users.noreply.github.com>", "Michael Burns <5170+mburns@users.noreply.github.com>", "Miguel Mota ", "Nuno Nogueira ", "Philipp Muens ", - "RasmusErik Voel Jensen " + "RasmusErik Voel Jensen ", + "Smite Chow " ] } From d172d0d9529691a052827951763d444321c151fe Mon Sep 17 00:00:00 2001 From: achingbrain Date: Mon, 13 Dec 2021 09:03:54 +0000 Subject: [PATCH 287/447] chore: release version v0.35.3 --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd5e3f3e22..e8cb09a7b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## [0.35.3](https://github.com/libp2p/js-libp2p/compare/v0.35.2...v0.35.3) (2021-12-13) + + +### Bug Fixes + +* clean up pending dial targets ([#1059](https://github.com/libp2p/js-libp2p/issues/1059)) ([bdc9f16](https://github.com/libp2p/js-libp2p/commit/bdc9f16d0cbe56ccf26822f11068e7795bcef046)) +* fix uncaught promise rejection when finding peers ([#1044](https://github.com/libp2p/js-libp2p/issues/1044)) ([3b683e7](https://github.com/libp2p/js-libp2p/commit/3b683e715686163e229b7b5c3a892327dfd4fc63)) +* make error codes consistent ([#1054](https://github.com/libp2p/js-libp2p/issues/1054)) ([b25e0fe](https://github.com/libp2p/js-libp2p/commit/b25e0fe5312db58a06c39500ae84c50fed3a93bd)) + + + ## [0.35.2](https://github.com/libp2p/js-libp2p/compare/v0.33.0...v0.35.2) (2021-12-06) From 2f0b311df7127aa44512c2008142d4ca30268986 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 15 Dec 2021 08:03:09 +0000 Subject: [PATCH 288/447] feat: allow per-component metrics to be collected (#1061) Implements the idea from #1060 - allows us to get some insight into what's happening in a libp2p node out side of just bandwidth stats. Configures a few default metrics if metrics are enabled - current connections, the state of the dial queue, etc. Also makes the `Metrics` class not depend on the `ConnectionManager` class, otherwise we can't collect simple metrics from the connection manager class due to the circular dependency. --- .github/workflows/main.yml | 2 +- src/connection-manager/index.js | 14 +++++++++++++ src/dialer/index.js | 18 ++++++++++++++++- src/index.js | 8 +++++--- src/metrics/index.js | 26 ++++++++++++++++-------- test/metrics/index.spec.js | 36 +++++++++++++++++++++------------ 6 files changed, 78 insertions(+), 26 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e9ecbb6043..a82a8cd798 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,7 @@ jobs: - run: npx aegir lint - run: npx aegir build - run: npx aegir dep-check - - uses: ipfs/aegir/actions/bundle-size@v32.1.0 + - uses: ipfs/aegir/actions/bundle-size name: size with: github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/src/connection-manager/index.js b/src/connection-manager/index.js index 84711272b9..8467e26343 100644 --- a/src/connection-manager/index.js +++ b/src/connection-manager/index.js @@ -32,6 +32,10 @@ const defaultOptions = { defaultPeerValue: 1 } +const METRICS_COMPONENT = 'connection-manager' +const METRICS_PEER_CONNECTIONS = 'peer-connections' +const METRICS_ALL_CONNECTIONS = 'all-connections' + /** * @typedef {import('../')} Libp2p * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection @@ -160,6 +164,8 @@ class ConnectionManager extends EventEmitter { await Promise.all(tasks) this.connections.clear() + this._libp2p.metrics && this._libp2p.metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_PEER_CONNECTIONS, 0) + this._libp2p.metrics && this._libp2p.metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_ALL_CONNECTIONS, 0) } /** @@ -211,10 +217,13 @@ class ConnectionManager extends EventEmitter { const storedConn = this.connections.get(peerIdStr) this.emit('peer:connect', connection) + if (storedConn) { storedConn.push(connection) } else { this.connections.set(peerIdStr, [connection]) + this._libp2p.metrics && this._libp2p.metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_PEER_CONNECTIONS, this.connections.size) + this._libp2p.metrics && this._libp2p.metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_ALL_CONNECTIONS, this.size) } this._libp2p.peerStore.keyBook.set(peerId, peerId.pubKey) @@ -243,7 +252,12 @@ class ConnectionManager extends EventEmitter { this.connections.delete(peerId) this._peerValues.delete(connection.remotePeer.toB58String()) this.emit('peer:disconnect', connection) + + this._libp2p.metrics && this._libp2p.metrics.onPeerDisconnected(connection.remotePeer) } + + this._libp2p.metrics && this._libp2p.metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_PEER_CONNECTIONS, this.connections.size) + this._libp2p.metrics && this._libp2p.metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_ALL_CONNECTIONS, this.size) } /** diff --git a/src/dialer/index.js b/src/dialer/index.js index b4dd87c26d..e119d10e5c 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -22,6 +22,10 @@ const { MAX_ADDRS_TO_DIAL } = require('../constants') +const METRICS_COMPONENT = 'dialler' +const METRICS_PENDING_DIALS = 'pending-dials' +const METRICS_PENDING_DIAL_TARGETS = 'pending-dials-targers' + /** * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection * @typedef {import('peer-id')} PeerId @@ -44,6 +48,7 @@ const { * @property {number} [maxDialsPerPeer = MAX_PER_PEER_DIALS] - Number of max concurrent dials per peer. * @property {number} [dialTimeout = DIAL_TIMEOUT] - How long a dial attempt is allowed to take. * @property {Record} [resolvers = {}] - multiaddr resolvers to use when dialing + * @property {import('../metrics')} [metrics] * * @typedef DialTarget * @property {string} id @@ -69,7 +74,8 @@ class Dialer { maxAddrsToDial = MAX_ADDRS_TO_DIAL, dialTimeout = DIAL_TIMEOUT, maxDialsPerPeer = MAX_PER_PEER_DIALS, - resolvers = {} + resolvers = {}, + metrics }) { this.transportManager = transportManager this.peerStore = peerStore @@ -81,6 +87,7 @@ class Dialer { this.tokens = [...new Array(maxParallelDials)].map((_, index) => index) this._pendingDials = new Map() this._pendingDialTargets = new Map() + this._metrics = metrics for (const [key, value] of Object.entries(resolvers)) { Multiaddr.resolvers.set(key, value) @@ -104,6 +111,9 @@ class Dialer { pendingTarget.reject(new AbortError('Dialer was destroyed')) } this._pendingDialTargets.clear() + + this._metrics && this._metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_PENDING_DIALS, 0) + this._metrics && this._metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_PENDING_DIAL_TARGETS, 0) } /** @@ -153,6 +163,7 @@ class Dialer { const id = `${(parseInt(String(Math.random() * 1e9), 10)).toString() + Date.now()}` const cancellablePromise = new Promise((resolve, reject) => { this._pendingDialTargets.set(id, { resolve, reject }) + this._metrics && this._metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_PENDING_DIAL_TARGETS, this._pendingDialTargets.size) }) try { @@ -164,6 +175,7 @@ class Dialer { return dialTarget } finally { this._pendingDialTargets.delete(id) + this._metrics && this._metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_PENDING_DIAL_TARGETS, this._pendingDialTargets.size) } } @@ -252,9 +264,13 @@ class Dialer { destroy: () => { timeoutController.clear() this._pendingDials.delete(dialTarget.id) + this._metrics && this._metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_PENDING_DIALS, this._pendingDials.size) } } this._pendingDials.set(dialTarget.id, pendingDial) + + this._metrics && this._metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_PENDING_DIALS, this._pendingDials.size) + return pendingDial } diff --git a/src/index.js b/src/index.js index 5e890479ea..b9857e37d2 100644 --- a/src/index.js +++ b/src/index.js @@ -197,10 +197,11 @@ class Libp2p extends EventEmitter { // Create Metrics if (this._options.metrics.enabled) { - this.metrics = new Metrics({ - ...this._options.metrics, - connectionManager: this.connectionManager + const metrics = new Metrics({ + ...this._options.metrics }) + + this.metrics = metrics } // Create keychain @@ -262,6 +263,7 @@ class Libp2p extends EventEmitter { this.dialer = new Dialer({ transportManager: this.transportManager, peerStore: this.peerStore, + metrics: this.metrics, ...this._options.dialer }) diff --git a/src/metrics/index.js b/src/metrics/index.js index 8d94861d81..5ea3f96af8 100644 --- a/src/metrics/index.js +++ b/src/metrics/index.js @@ -24,9 +24,6 @@ const directionToEvent = { */ /** - * @typedef MetricsProperties - * @property {import('../connection-manager')} connectionManager - * * @typedef MetricsOptions * @property {number} [computeThrottleMaxQueueSize = defaultOptions.computeThrottleMaxQueueSize] * @property {number} [computeThrottleTimeout = defaultOptions.computeThrottleTimeout] @@ -37,7 +34,7 @@ const directionToEvent = { class Metrics { /** * @class - * @param {MetricsProperties & MetricsOptions} options + * @param {MetricsOptions} options */ constructor (options) { this._options = mergeOptions(defaultOptions, options) @@ -47,10 +44,7 @@ class Metrics { this._oldPeers = oldPeerLRU(this._options.maxOldPeersRetention) this._running = false this._onMessage = this._onMessage.bind(this) - this._connectionManager = options.connectionManager - this._connectionManager.on('peer:disconnect', (connection) => { - this.onPeerDisconnected(connection.remotePeer) - }) + this._componentMetrics = new Map() } /** @@ -94,6 +88,22 @@ class Metrics { return Array.from(this._peerStats.keys()) } + /** + * @returns {Map} + */ + getComponentMetrics () { + return this._componentMetrics + } + + updateComponentMetric (component, metric, value) { + if (!this._componentMetrics.has(component)) { + this._componentMetrics.set(component, new Map()) + } + + const map = this._componentMetrics.get(component) + map.set(metric, value) + } + /** * Returns the `Stats` object for the given `PeerId` whether it * is a live peer, or in the disconnected peer LRU cache. diff --git a/test/metrics/index.spec.js b/test/metrics/index.spec.js index 5f401e8ac6..ed17c57ef8 100644 --- a/test/metrics/index.spec.js +++ b/test/metrics/index.spec.js @@ -3,9 +3,6 @@ const { expect } = require('aegir/utils/chai') const sinon = require('sinon') - -const { EventEmitter } = require('events') - const { randomBytes } = require('libp2p-crypto') const duplexPair = require('it-pair/duplex') const pipe = require('it-pipe') @@ -34,8 +31,7 @@ describe('Metrics', () => { const [local, remote] = duplexPair() const metrics = new Metrics({ computeThrottleMaxQueueSize: 1, // compute after every message - movingAverageIntervals: [10, 100, 1000], - connectionManager: new EventEmitter() + movingAverageIntervals: [10, 100, 1000] }) metrics.trackStream({ @@ -70,8 +66,7 @@ describe('Metrics', () => { const [local, remote] = duplexPair() const metrics = new Metrics({ computeThrottleMaxQueueSize: 1, // compute after every message - movingAverageIntervals: [10, 100, 1000], - connectionManager: new EventEmitter() + movingAverageIntervals: [10, 100, 1000] }) metrics.trackStream({ @@ -119,8 +114,7 @@ describe('Metrics', () => { const [local2, remote2] = duplexPair() const metrics = new Metrics({ computeThrottleMaxQueueSize: 1, // compute after every message - movingAverageIntervals: [10, 100, 1000], - connectionManager: new EventEmitter() + movingAverageIntervals: [10, 100, 1000] }) const protocol = '/echo/1.0.0' metrics.start() @@ -175,8 +169,7 @@ describe('Metrics', () => { const [local, remote] = duplexPair() const metrics = new Metrics({ computeThrottleMaxQueueSize: 1, // compute after every message - movingAverageIntervals: [10, 100, 1000], - connectionManager: new EventEmitter() + movingAverageIntervals: [10, 100, 1000] }) metrics.start() @@ -231,8 +224,7 @@ describe('Metrics', () => { })) const metrics = new Metrics({ - maxOldPeersRetention: 5, // Only keep track of 5 - connectionManager: new EventEmitter() + maxOldPeersRetention: 5 // Only keep track of 5 }) // Clone so trackedPeers isn't modified @@ -262,4 +254,22 @@ describe('Metrics', () => { expect(spy).to.have.property('callCount', 1) } }) + + it('should allow components to track metrics', () => { + const metrics = new Metrics({ + maxOldPeersRetention: 5 // Only keep track of 5 + }) + + expect(metrics.getComponentMetrics()).to.be.empty() + + const component = 'my-component' + const metric = 'some-metric' + const value = 1 + + metrics.updateComponentMetric(component, metric, value) + + expect(metrics.getComponentMetrics()).to.have.lengthOf(1) + expect(metrics.getComponentMetrics().get(component)).to.have.lengthOf(1) + expect(metrics.getComponentMetrics().get(component).get(metric)).to.equal(value) + }) }) From 76f4ea5e8aed1e7530739edd3a3ca7688f68b5cf Mon Sep 17 00:00:00 2001 From: achingbrain Date: Wed, 15 Dec 2021 08:18:44 +0000 Subject: [PATCH 289/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f0c7c51162..51503b9bc8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.35.3", + "version": "0.35.4", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From faf1f89d9e0e2b4f0404e26abc871d69527b9795 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Wed, 15 Dec 2021 08:18:44 +0000 Subject: [PATCH 290/447] chore: release version v0.35.4 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8cb09a7b3..f3c36c9845 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.35.4](https://github.com/libp2p/js-libp2p/compare/v0.35.3...v0.35.4) (2021-12-15) + + +### Features + +* allow per-component metrics to be collected ([#1061](https://github.com/libp2p/js-libp2p/issues/1061)) ([2f0b311](https://github.com/libp2p/js-libp2p/commit/2f0b311df7127aa44512c2008142d4ca30268986)), closes [#1060](https://github.com/libp2p/js-libp2p/issues/1060) + + + ## [0.35.3](https://github.com/libp2p/js-libp2p/compare/v0.35.2...v0.35.3) (2021-12-13) From c8e1b08c198bd1389ba0aaa00ce2ebd3a7b28950 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Wed, 15 Dec 2021 09:25:40 +0000 Subject: [PATCH 291/447] chore: typo --- src/dialer/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dialer/index.js b/src/dialer/index.js index e119d10e5c..555d26f2e3 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -24,7 +24,7 @@ const { const METRICS_COMPONENT = 'dialler' const METRICS_PENDING_DIALS = 'pending-dials' -const METRICS_PENDING_DIAL_TARGETS = 'pending-dials-targers' +const METRICS_PENDING_DIAL_TARGETS = 'pending-dial-targets' /** * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection From fe0d9828bb75ab75a63fd762852566f1a5a21228 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Wed, 15 Dec 2021 09:28:04 +0000 Subject: [PATCH 292/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 51503b9bc8..956eeae4df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.35.4", + "version": "0.35.5", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From 8ce2f085896b12d9e70ad1d314082f3590a2c6f0 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Wed, 15 Dec 2021 09:28:05 +0000 Subject: [PATCH 293/447] chore: release version v0.35.5 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3c36c9845..4a51078fe7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [0.35.5](https://github.com/libp2p/js-libp2p/compare/v0.35.4...v0.35.5) (2021-12-15) + + + ## [0.35.4](https://github.com/libp2p/js-libp2p/compare/v0.35.3...v0.35.4) (2021-12-15) From a642ad2a035dfd33a31fde637d6f4327fe6d8d59 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 15 Dec 2021 09:30:49 +0000 Subject: [PATCH 294/447] chore(deps-dev): bump libp2p-floodsub from 0.27.1 to 0.28.0 (#1062) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 956eeae4df..0e88a87544 100644 --- a/package.json +++ b/package.json @@ -152,7 +152,7 @@ "libp2p-bootstrap": "^0.14.0", "libp2p-delegated-content-routing": "^0.11.0", "libp2p-delegated-peer-routing": "^0.11.1", - "libp2p-floodsub": "^0.27.0", + "libp2p-floodsub": "^0.28.0", "libp2p-gossipsub": "^0.12.1", "libp2p-interfaces-compliance-tests": "^2.0.1", "libp2p-interop": "^0.5.0", From 09a0f940df7fdb4ece34604e85693709df5c213e Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Sat, 18 Dec 2021 07:34:27 +0100 Subject: [PATCH 295/447] fix: increase the maxlisteners for timeout controllers (#1065) We use timeout controllers to ensure we're not dialling peers forever but we can end up registering lots of listeners for the `abort` event when peers have a lot of addresses. In node this means we see an unhelpful `MaxListenersExceededWarning` in the console warning of a potential memory leak. Increase the max number of listeners on the signal to silence the warning. --- src/dialer/index.js | 7 ++++++- src/peer-routing.js | 9 ++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/dialer/index.js b/src/dialer/index.js index 555d26f2e3..aed012c0ee 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -9,7 +9,8 @@ const { Multiaddr } = require('multiaddr') const { TimeoutController } = require('timeout-abort-controller') const { AbortError } = require('abortable-iterator') const { anySignal } = require('any-signal') - +// @ts-expect-error setMaxListeners is missing from the types +const { setMaxListeners } = require('events') const DialRequest = require('./dial-request') const { publicAddressesFirst } = require('libp2p-utils/src/address-sort') const getPeer = require('../get-peer') @@ -253,6 +254,10 @@ class Dialer { // Combine the timeout signal and options.signal, if provided const timeoutController = new TimeoutController(this.timeout) + // this controller will potentially be used while dialing lots of + // peers so prevent MaxListenersExceededWarning appearing in the console + setMaxListeners && setMaxListeners(Infinity, timeoutController.signal) + const signals = [timeoutController.signal] options.signal && signals.push(options.signal) const signal = anySignal(signals) diff --git a/src/peer-routing.js b/src/peer-routing.js index 8225a5a042..fd6f99205b 100644 --- a/src/peer-routing.js +++ b/src/peer-routing.js @@ -24,6 +24,8 @@ const { // @ts-ignore module with no types } = require('set-delayed-interval') const { DHTPeerRouting } = require('./dht/dht-peer-routing') +// @ts-expect-error setMaxListeners is missing from the types +const { setMaxListeners } = require('events') /** * @typedef {import('peer-id')} PeerId @@ -149,7 +151,12 @@ class PeerRouting { } if (options.timeout) { - options.signal = new TimeoutController(options.timeout).signal + const controller = new TimeoutController(options.timeout) + // this controller will potentially be used while dialing lots of + // peers so prevent MaxListenersExceededWarning appearing in the console + setMaxListeners && setMaxListeners(Infinity, controller.signal) + + options.signal = controller.signal } yield * pipe( From 0c3ed0a4acf01dbd2e0109294213e9e36afe0458 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Sat, 18 Dec 2021 07:55:26 +0100 Subject: [PATCH 296/447] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0e88a87544..15ad84c6d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.35.5", + "version": "0.35.6", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From 0a485d07b38a51e9fade7f5f750fbd50400f6c12 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Sat, 18 Dec 2021 07:55:27 +0100 Subject: [PATCH 297/447] chore: release version v0.35.6 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a51078fe7..d2f40ca727 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.35.6](https://github.com/libp2p/js-libp2p/compare/v0.35.5...v0.35.6) (2021-12-18) + + +### Bug Fixes + +* increase the maxlisteners for timeout controllers ([#1065](https://github.com/libp2p/js-libp2p/issues/1065)) ([09a0f94](https://github.com/libp2p/js-libp2p/commit/09a0f940df7fdb4ece34604e85693709df5c213e)) + + + ## [0.35.5](https://github.com/libp2p/js-libp2p/compare/v0.35.4...v0.35.5) (2021-12-15) From b425fa12304def2a007d43a0aa445c28b766ed02 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 21 Dec 2021 15:51:06 +0100 Subject: [PATCH 298/447] fix: add tracked map (#1069) Small refactor of the component stats - adds a `TrackedMap` which encapsulates updating the metrics and means we don't need to null guard on `this._metrics` everywhere. If metrics are not enabled a regular `Map` is used. --- src/connection-manager/index.js | 15 +++----- src/dialer/index.js | 19 ++++------ src/index.js | 18 +++++----- src/metrics/tracked-map.js | 62 +++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 32 deletions(-) create mode 100644 src/metrics/tracked-map.js diff --git a/src/connection-manager/index.js b/src/connection-manager/index.js index 8467e26343..ebdab62335 100644 --- a/src/connection-manager/index.js +++ b/src/connection-manager/index.js @@ -12,7 +12,7 @@ const LatencyMonitor = require('./latency-monitor') const retimer = require('retimer') const { EventEmitter } = require('events') - +const trackedMap = require('../metrics/tracked-map') const PeerId = require('peer-id') const { @@ -34,7 +34,7 @@ const defaultOptions = { const METRICS_COMPONENT = 'connection-manager' const METRICS_PEER_CONNECTIONS = 'peer-connections' -const METRICS_ALL_CONNECTIONS = 'all-connections' +const METRICS_PEER_VALUES = 'peer-values' /** * @typedef {import('../')} Libp2p @@ -87,14 +87,14 @@ class ConnectionManager extends EventEmitter { * * @type {Map} */ - this._peerValues = new Map() + this._peerValues = trackedMap(METRICS_COMPONENT, METRICS_PEER_VALUES, this._libp2p.metrics) /** * Map of connections per peer * * @type {Map} */ - this.connections = new Map() + this.connections = trackedMap(METRICS_COMPONENT, METRICS_PEER_CONNECTIONS, this._libp2p.metrics) this._started = false this._timer = null @@ -164,8 +164,6 @@ class ConnectionManager extends EventEmitter { await Promise.all(tasks) this.connections.clear() - this._libp2p.metrics && this._libp2p.metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_PEER_CONNECTIONS, 0) - this._libp2p.metrics && this._libp2p.metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_ALL_CONNECTIONS, 0) } /** @@ -222,8 +220,6 @@ class ConnectionManager extends EventEmitter { storedConn.push(connection) } else { this.connections.set(peerIdStr, [connection]) - this._libp2p.metrics && this._libp2p.metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_PEER_CONNECTIONS, this.connections.size) - this._libp2p.metrics && this._libp2p.metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_ALL_CONNECTIONS, this.size) } this._libp2p.peerStore.keyBook.set(peerId, peerId.pubKey) @@ -255,9 +251,6 @@ class ConnectionManager extends EventEmitter { this._libp2p.metrics && this._libp2p.metrics.onPeerDisconnected(connection.remotePeer) } - - this._libp2p.metrics && this._libp2p.metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_PEER_CONNECTIONS, this.connections.size) - this._libp2p.metrics && this._libp2p.metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_ALL_CONNECTIONS, this.size) } /** diff --git a/src/dialer/index.js b/src/dialer/index.js index aed012c0ee..eaf1313e9d 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -14,7 +14,7 @@ const { setMaxListeners } = require('events') const DialRequest = require('./dial-request') const { publicAddressesFirst } = require('libp2p-utils/src/address-sort') const getPeer = require('../get-peer') - +const trackedMap = require('../metrics/tracked-map') const { codes } = require('../errors') const { DIAL_TIMEOUT, @@ -86,9 +86,12 @@ class Dialer { this.timeout = dialTimeout this.maxDialsPerPeer = maxDialsPerPeer this.tokens = [...new Array(maxParallelDials)].map((_, index) => index) - this._pendingDials = new Map() - this._pendingDialTargets = new Map() - this._metrics = metrics + + /** @type {Map} */ + this._pendingDials = trackedMap(METRICS_COMPONENT, METRICS_PENDING_DIALS, metrics) + + /** @type {Map void, reject: (err: Error) => void}>} */ + this._pendingDialTargets = trackedMap(METRICS_COMPONENT, METRICS_PENDING_DIAL_TARGETS, metrics) for (const [key, value] of Object.entries(resolvers)) { Multiaddr.resolvers.set(key, value) @@ -112,9 +115,6 @@ class Dialer { pendingTarget.reject(new AbortError('Dialer was destroyed')) } this._pendingDialTargets.clear() - - this._metrics && this._metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_PENDING_DIALS, 0) - this._metrics && this._metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_PENDING_DIAL_TARGETS, 0) } /** @@ -164,7 +164,6 @@ class Dialer { const id = `${(parseInt(String(Math.random() * 1e9), 10)).toString() + Date.now()}` const cancellablePromise = new Promise((resolve, reject) => { this._pendingDialTargets.set(id, { resolve, reject }) - this._metrics && this._metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_PENDING_DIAL_TARGETS, this._pendingDialTargets.size) }) try { @@ -176,7 +175,6 @@ class Dialer { return dialTarget } finally { this._pendingDialTargets.delete(id) - this._metrics && this._metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_PENDING_DIAL_TARGETS, this._pendingDialTargets.size) } } @@ -269,13 +267,10 @@ class Dialer { destroy: () => { timeoutController.clear() this._pendingDials.delete(dialTarget.id) - this._metrics && this._metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_PENDING_DIALS, this._pendingDials.size) } } this._pendingDials.set(dialTarget.id, pendingDial) - this._metrics && this._metrics.updateComponentMetric(METRICS_COMPONENT, METRICS_PENDING_DIALS, this._pendingDials.size) - return pendingDial } diff --git a/src/index.js b/src/index.js index b9857e37d2..d6b8254c0c 100644 --- a/src/index.js +++ b/src/index.js @@ -161,6 +161,15 @@ class Libp2p extends EventEmitter { this.peerId = this._options.peerId this.datastore = this._options.datastore + // Create Metrics + if (this._options.metrics.enabled) { + const metrics = new Metrics({ + ...this._options.metrics + }) + + this.metrics = metrics + } + this.peerStore = (this.datastore && this._options.peerStore.persistence) ? new PersistentPeerStore({ peerId: this.peerId, @@ -195,15 +204,6 @@ class Libp2p extends EventEmitter { autoDialInterval: this._options.connectionManager.autoDialInterval }) - // Create Metrics - if (this._options.metrics.enabled) { - const metrics = new Metrics({ - ...this._options.metrics - }) - - this.metrics = metrics - } - // Create keychain if (this._options.keychain && this._options.keychain.datastore) { log('creating keychain') diff --git a/src/metrics/tracked-map.js b/src/metrics/tracked-map.js new file mode 100644 index 0000000000..c63503f4fd --- /dev/null +++ b/src/metrics/tracked-map.js @@ -0,0 +1,62 @@ +'use strict' + +/** + * @template K + * @template V + */ +class TrackedMap extends Map { + /** + * @param {string} component + * @param {string} name + * @param {import('.')} metrics + */ + constructor (component, name, metrics) { + super() + + this._component = component + this._name = name + this._metrics = metrics + + this._metrics.updateComponentMetric(this._component, this._name, this.size) + } + + /** + * @param {K} key + * @param {V} value + */ + set (key, value) { + super.set(key, value) + this._metrics.updateComponentMetric(this._component, this._name, this.size) + return this + } + + /** + * @param {K} key + */ + delete (key) { + const deleted = super.delete(key) + this._metrics.updateComponentMetric(this._component, this._name, this.size) + return deleted + } +} + +/** + * @template K + * @template V + * @param {string} component + * @param {string} name + * @param {import('.')} [metrics] + * @returns {Map} + */ +module.exports = (component, name, metrics) => { + /** @type {Map} */ + let map + + if (metrics) { + map = new TrackedMap(component, name, metrics) + } else { + map = new Map() + } + + return map +} From a0516ebc85c018d500a50f94939bd6d0c006b147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Wed, 22 Dec 2021 14:01:54 +0100 Subject: [PATCH 299/447] docs: update node and npm version badge according to package.json (#1074) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d5c901252f..90f3ac9f24 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,8 @@ - - + +

From 70a4bb9451c10a91dd42de5c366cb669d1e5c0f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADt=20Habada?= Date: Wed, 22 Dec 2021 14:15:18 +0100 Subject: [PATCH 300/447] docs: peerstore configuration datastore fixed --- doc/CONFIGURATION.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index e6038c44c5..fdfccc800b 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -675,15 +675,15 @@ const { NOISE } = require('libp2p-noise') const LevelDatastore = require('datastore-level') const datastore = new LevelDatastore('path/to/store') -const dsInstant = await datastore.open() +await datastore.open() // level database must be ready before node boot const node = await Libp2p.create({ + datastore, // pass the opened datastore modules: { transport: [TCP], streamMuxer: [MPLEX], connEncryption: [NOISE] }, - datastore: dsInstant, peerStore: { persistence: true, threshold: 5 From c4a442788b734ea66ec8847eaae762829f6e8ce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADt=20Habada?= Date: Wed, 22 Dec 2021 17:03:03 +0100 Subject: [PATCH 301/447] docs: update example config ipfs links (#1077) --- doc/CONFIGURATION.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index fdfccc800b..89d9197340 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -806,8 +806,8 @@ protocols: [ 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/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) +- [libp2p-ipfs-nodejs](https://github.com/ipfs/js-ipfs/blob/master/packages/ipfs-core-config/src/libp2p.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-core-config/src/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. From d1c48dcbeded828f2dd3044cc9aed3f17f02846d Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 24 Dec 2021 10:18:09 +0000 Subject: [PATCH 302/447] fix: main ci (#1079) --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a82a8cd798..f4ffb7e2ab 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,7 @@ jobs: - run: npx aegir lint - run: npx aegir build - run: npx aegir dep-check - - uses: ipfs/aegir/actions/bundle-size + - uses: ipfs/aegir/actions/bundle-size@master name: size with: github_token: ${{ secrets.GITHUB_TOKEN }} From cb0d7d6c99d179498f04e76df76e70e4f7d41c4c Mon Sep 17 00:00:00 2001 From: tuyennhv Date: Fri, 24 Dec 2021 21:46:00 +0700 Subject: [PATCH 303/447] fix: type definitions for big dialrequest and persistent peerstore (#1078) Signed-off-by: Tuyen Nguyen --- src/dialer/index.js | 4 ++-- src/index.js | 4 +++- src/metrics/stats.js | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/dialer/index.js b/src/dialer/index.js index eaf1313e9d..38353bf012 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -56,8 +56,8 @@ const METRICS_PENDING_DIAL_TARGETS = 'pending-dial-targets' * @property {Multiaddr[]} addrs * * @typedef PendingDial - * @property {DialRequest} dialRequest - * @property {TimeoutController} controller + * @property {import('./dial-request')} dialRequest + * @property {import('timeout-abort-controller').TimeoutController} controller * @property {Promise} promise * @property {function():void} destroy */ diff --git a/src/index.js b/src/index.js index d6b8254c0c..f9f2b6c37f 100644 --- a/src/index.js +++ b/src/index.js @@ -48,6 +48,8 @@ const { updateSelfPeerRecord } = require('./record/utils') * @typedef {import('libp2p-interfaces/src/pubsub').PubsubOptions} PubsubOptions * @typedef {import('interface-datastore').Datastore} Datastore * @typedef {import('./pnet')} Protector + * @typedef {Object} PersistentPeerStoreOptions + * @property {number} [threshold] */ /** @@ -110,7 +112,7 @@ const { updateSelfPeerRecord } = require('./record/utils') * @property {KeychainOptions & import('./keychain/index').KeychainOptions} [keychain] * @property {MetricsOptions & import('./metrics').MetricsOptions} [metrics] * @property {import('./peer-routing').PeerRoutingOptions} [peerRouting] - * @property {PeerStoreOptions & import('./peer-store/persistent').PersistentPeerStoreOptions} [peerStore] + * @property {PeerStoreOptions & PersistentPeerStoreOptions} [peerStore] * @property {import('./transport-manager').TransportManagerOptions} [transportManager] * @property {Libp2pConfig} [config] * diff --git a/src/metrics/stats.js b/src/metrics/stats.js index 267f3a2ed3..4246588f21 100644 --- a/src/metrics/stats.js +++ b/src/metrics/stats.js @@ -8,6 +8,7 @@ const retimer = require('retimer') /** * @typedef {import('@vascosantos/moving-average').IMovingAverage} IMovingAverage + * @typedef {import('bignumber.js').BigNumber} Big */ class Stats extends EventEmitter { From 4070dcdf55c1fa83bb07d4de64defe2888b932c5 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 24 Dec 2021 14:51:51 +0000 Subject: [PATCH 304/447] chore: update contributors --- package.json | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 15ad84c6d3..05a69d194a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.35.6", + "version": "0.35.7", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", @@ -181,9 +181,9 @@ "Friedel Ziegelmayer ", "Maciej Krüger ", "Hugo Dias ", - "dirkmc ", - "Chris Dostert ", "Volker Mische ", + "Chris Dostert ", + "dirkmc ", "Robert Kiel ", "Richard Littauer ", "zeim839 <50573884+zeim839@users.noreply.github.com>", @@ -193,22 +193,23 @@ "Andrew Nesbitt ", "Franck Royer ", "Thomas Eizinger ", + "Vít Habada ", "Giovanni T. Parra ", "acolytec3 <17355484+acolytec3@users.noreply.github.com>", "Alan Smithee ", "Elven ", "Samlior ", "Didrik Nordström ", - "Soeren ", - "Sönke Hahn ", + "Aditya Bose <13054902+adbose@users.noreply.github.com>", "TJKoury ", "TheStarBoys <41286328+TheStarBoys@users.noreply.github.com>", "Tiago Alves ", + "Tim Daubenschütz ", "XiaoZhang ", "Yusef Napora ", "Zane Starr ", "ebinks ", - "Aditya Bose <13054902+adbose@users.noreply.github.com>", + "greenSnot ", "isan_rivkin ", "mayerwin ", "mcclure ", @@ -217,7 +218,8 @@ "robertkiel ", "shresthagrawal <34920931+shresthagrawal@users.noreply.github.com>", "swedneck <40505480+swedneck@users.noreply.github.com>", - "greenSnot ", + "tuyennhv ", + "Sönke Hahn ", "Aleksei ", "Bernd Strehl ", "Chris Bratlien ", @@ -248,6 +250,7 @@ "Nuno Nogueira ", "Philipp Muens ", "RasmusErik Voel Jensen ", - "Smite Chow " + "Smite Chow ", + "Soeren " ] } From bbdd559a02813da22bdf0eccb3e94406be031cdf Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 24 Dec 2021 14:51:51 +0000 Subject: [PATCH 305/447] chore: release version v0.35.7 --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2f40ca727..c1e60e6ac4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +## [0.35.7](https://github.com/libp2p/js-libp2p/compare/v0.35.2...v0.35.7) (2021-12-24) + + +### Bug Fixes + +* add tracked map ([#1069](https://github.com/libp2p/js-libp2p/issues/1069)) ([b425fa1](https://github.com/libp2p/js-libp2p/commit/b425fa12304def2a007d43a0aa445c28b766ed02)) +* clean up pending dial targets ([#1059](https://github.com/libp2p/js-libp2p/issues/1059)) ([bdc9f16](https://github.com/libp2p/js-libp2p/commit/bdc9f16d0cbe56ccf26822f11068e7795bcef046)) +* fix uncaught promise rejection when finding peers ([#1044](https://github.com/libp2p/js-libp2p/issues/1044)) ([3b683e7](https://github.com/libp2p/js-libp2p/commit/3b683e715686163e229b7b5c3a892327dfd4fc63)) +* increase the maxlisteners for timeout controllers ([#1065](https://github.com/libp2p/js-libp2p/issues/1065)) ([09a0f94](https://github.com/libp2p/js-libp2p/commit/09a0f940df7fdb4ece34604e85693709df5c213e)) +* main ci ([#1079](https://github.com/libp2p/js-libp2p/issues/1079)) ([d1c48dc](https://github.com/libp2p/js-libp2p/commit/d1c48dcbeded828f2dd3044cc9aed3f17f02846d)) +* make error codes consistent ([#1054](https://github.com/libp2p/js-libp2p/issues/1054)) ([b25e0fe](https://github.com/libp2p/js-libp2p/commit/b25e0fe5312db58a06c39500ae84c50fed3a93bd)) +* type definitions for big dialrequest and persistent peerstore ([#1078](https://github.com/libp2p/js-libp2p/issues/1078)) ([cb0d7d6](https://github.com/libp2p/js-libp2p/commit/cb0d7d6c99d179498f04e76df76e70e4f7d41c4c)) + + +### Features + +* allow per-component metrics to be collected ([#1061](https://github.com/libp2p/js-libp2p/issues/1061)) ([2f0b311](https://github.com/libp2p/js-libp2p/commit/2f0b311df7127aa44512c2008142d4ca30268986)), closes [#1060](https://github.com/libp2p/js-libp2p/issues/1060) + + + ## [0.35.6](https://github.com/libp2p/js-libp2p/compare/v0.35.5...v0.35.6) (2021-12-18) From b4b432406ebc08ef2fc3a1922c64cde7c9060cae Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Mon, 27 Dec 2021 07:14:27 +0100 Subject: [PATCH 306/447] fix: record tracked map clears (#1085) Record the size of a map after we `.clear()` it. --- src/metrics/tracked-map.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/metrics/tracked-map.js b/src/metrics/tracked-map.js index c63503f4fd..d37587a663 100644 --- a/src/metrics/tracked-map.js +++ b/src/metrics/tracked-map.js @@ -38,6 +38,12 @@ class TrackedMap extends Map { this._metrics.updateComponentMetric(this._component, this._name, this.size) return deleted } + + clear () { + super.clear() + + this._metrics.updateComponentMetric(this._component, this._name, this.size) + } } /** From f18fc80b70bf7b6b26fffa70b0a8d0502a6c4801 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 29 Dec 2021 10:51:26 +0100 Subject: [PATCH 307/447] fix: increase listeners on any-signal (#1084) Increase the number of listeners we allow on the actual signal we pass along, instead of the signal we pass into any-signal. --- src/dialer/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/dialer/index.js b/src/dialer/index.js index 38353bf012..e6f3a0d020 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -252,14 +252,15 @@ class Dialer { // Combine the timeout signal and options.signal, if provided const timeoutController = new TimeoutController(this.timeout) - // this controller will potentially be used while dialing lots of - // peers so prevent MaxListenersExceededWarning appearing in the console - setMaxListeners && setMaxListeners(Infinity, timeoutController.signal) const signals = [timeoutController.signal] options.signal && signals.push(options.signal) const signal = anySignal(signals) + // this signal will potentially be used while dialing lots of + // peers so prevent MaxListenersExceededWarning appearing in the console + setMaxListeners && setMaxListeners(Infinity, signal) + const pendingDial = { dialRequest, controller: timeoutController, From 79b3cfc6ad02ecc76fe23a3c3ff2d0b32a0ae4a8 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 29 Dec 2021 10:55:48 +0100 Subject: [PATCH 308/447] fix: do not wait for autodial start (#1089) When we've previously seen loads of peers and stored them in the datastore we'll try to dial them as part of starting the autodial component. If we or our peers have bad network connections this can make starting a libp2p node take ages so don't wait for a round of auto dialing before considering the component started. --- src/connection-manager/auto-dialler.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/connection-manager/auto-dialler.js b/src/connection-manager/auto-dialler.js index 977b13f72b..5e71d7edcb 100644 --- a/src/connection-manager/auto-dialler.js +++ b/src/connection-manager/auto-dialler.js @@ -57,7 +57,9 @@ class AutoDialler { } this._running = true - this._autoDial() + this._autoDial().catch(err => { + log.error('could start autodial', err) + }) log('started') } From d2b7ec0f6be0ee80f2c963279a8ec2385059a889 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 29 Dec 2021 10:56:56 +0100 Subject: [PATCH 309/447] fix: look for final peer event instead of peer response (#1092) `FINAL_PEER` means we found the peer, `PEER_RESPONSE` means a peer responded to our query. --- src/dht/dht-peer-routing.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/dht/dht-peer-routing.js b/src/dht/dht-peer-routing.js index 61d79f924d..762abc80fa 100644 --- a/src/dht/dht-peer-routing.js +++ b/src/dht/dht-peer-routing.js @@ -27,12 +27,8 @@ class DHTPeerRouting { */ async findPeer (peerId, options = {}) { for await (const event of this._dht.findPeer(peerId, options)) { - if (event.name === 'PEER_RESPONSE') { - const peer = event.closer.find(peerData => peerData.id.equals(peerId)) - - if (peer) { - return peer - } + if (event.name === 'FINAL_PEER') { + return event.peer } } From 61bf546c46776385c4660d60a5959e77587668f5 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Wed, 29 Dec 2021 11:00:11 +0100 Subject: [PATCH 310/447] chore: update contributors --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 05a69d194a..ce0ff5a28f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.35.7", + "version": "0.35.8", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", @@ -181,12 +181,12 @@ "Friedel Ziegelmayer ", "Maciej Krüger ", "Hugo Dias ", + "dirkmc ", "Volker Mische ", "Chris Dostert ", - "dirkmc ", + "zeim839 <50573884+zeim839@users.noreply.github.com>", "Robert Kiel ", "Richard Littauer ", - "zeim839 <50573884+zeim839@users.noreply.github.com>", "a1300 ", "Ryan Bell ", "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", From ef54e0a10ee2b8fe7b0e22743e18c0410fad5b29 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Wed, 29 Dec 2021 11:00:11 +0100 Subject: [PATCH 311/447] chore: release version v0.35.8 --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1e60e6ac4..7ee66cfe9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## [0.35.8](https://github.com/libp2p/js-libp2p/compare/v0.35.7...v0.35.8) (2021-12-29) + + +### Bug Fixes + +* do not wait for autodial start ([#1089](https://github.com/libp2p/js-libp2p/issues/1089)) ([79b3cfc](https://github.com/libp2p/js-libp2p/commit/79b3cfc6ad02ecc76fe23a3c3ff2d0b32a0ae4a8)) +* increase listeners on any-signal ([#1084](https://github.com/libp2p/js-libp2p/issues/1084)) ([f18fc80](https://github.com/libp2p/js-libp2p/commit/f18fc80b70bf7b6b26fffa70b0a8d0502a6c4801)) +* look for final peer event instead of peer response ([#1092](https://github.com/libp2p/js-libp2p/issues/1092)) ([d2b7ec0](https://github.com/libp2p/js-libp2p/commit/d2b7ec0f6be0ee80f2c963279a8ec2385059a889)) +* record tracked map clears ([#1085](https://github.com/libp2p/js-libp2p/issues/1085)) ([b4b4324](https://github.com/libp2p/js-libp2p/commit/b4b432406ebc08ef2fc3a1922c64cde7c9060cae)) + + + ## [0.35.7](https://github.com/libp2p/js-libp2p/compare/v0.35.2...v0.35.7) (2021-12-24) From 5043cd56435a264e83db4fb8388d33e9a0442fff Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 29 Dec 2021 15:06:58 +0100 Subject: [PATCH 312/447] fix: cache build artefacts (#1091) To speed up the build and make it more reliable, cache the node_modules folder, dist, etc and re-use on each step. --- .github/workflows/examples.yml | 298 ++++++++++++++++++++++--- .github/workflows/main.yml | 134 ++++++++++- examples/webrtc-direct/package.json | 1 + package.json | 2 +- test/peer-routing/peer-routing.node.js | 18 +- 5 files changed, 408 insertions(+), 45 deletions(-) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 36173f4f8a..0e46e95155 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -8,17 +8,59 @@ on: - '**' jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: 16 + - uses: actions/cache@v2 + id: cache + env: + CACHE_NAME: cache-node-modules + with: + path: | + ~/.cache + ~/.npm + ./node_modules + ./dist + ./examples/node_modules + key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm install + npm run build + cd examples && npm i check: + needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: 16 - - run: npm install + - uses: actions/cache@v2 + id: cache + env: + CACHE_NAME: cache-node-modules + with: + path: | + ~/.cache + ~/.npm + ./node_modules + ./dist + ./examples/node_modules + key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm install + npm run build + cd examples && npm i - run: npx aegir lint - run: npx aegir ts -p check - - run: npx aegir build test-auto-relay-example: needs: check runs-on: ubuntu-latest @@ -27,8 +69,25 @@ jobs: - uses: actions/setup-node@v2 with: node-version: 16 - - run: npm install - - run: cd examples && npm i && npm run test -- auto-relay + - uses: actions/cache@v2 + id: cache + env: + CACHE_NAME: cache-node-modules + with: + path: | + ~/.cache + ~/.npm + ./node_modules + ./dist + ./examples/node_modules + key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm install + npm run build + cd examples && npm i + - run: cd examples && npm run test -- auto-relay test-chat-example: needs: check runs-on: ubuntu-latest @@ -37,8 +96,25 @@ jobs: - uses: actions/setup-node@v2 with: node-version: 16 - - run: npm install - - run: cd examples && npm i && npm run test -- chat + - uses: actions/cache@v2 + id: cache + env: + CACHE_NAME: cache-node-modules + with: + path: | + ~/.cache + ~/.npm + ./node_modules + ./dist + ./examples/node_modules + key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm install + npm run build + cd examples && npm i + - run: cd examples && npm run test -- chat test-connection-encryption-example: needs: check runs-on: ubuntu-latest @@ -47,8 +123,25 @@ jobs: - uses: actions/setup-node@v2 with: node-version: 16 - - run: npm install - - run: cd examples && npm i && npm run test -- connection-encryption + - uses: actions/cache@v2 + id: cache + env: + CACHE_NAME: cache-node-modules + with: + path: | + ~/.cache + ~/.npm + ./node_modules + ./dist + ./examples/node_modules + key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm install + npm run build + cd examples && npm i + - run: cd examples && npm run test -- connection-encryption test-discovery-mechanisms-example: needs: check runs-on: macos-latest @@ -57,8 +150,25 @@ jobs: - uses: actions/setup-node@v2 with: node-version: 16 - - run: npm install - - run: cd examples && npm i && npm run test -- discovery-mechanisms + - uses: actions/cache@v2 + id: cache + env: + CACHE_NAME: cache-node-modules + with: + path: | + ~/.cache + ~/.npm + ./node_modules + ./dist + ./examples/node_modules + key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm install + npm run build + cd examples && npm i + - run: cd examples && npm run test -- discovery-mechanisms test-echo-example: needs: check runs-on: ubuntu-latest @@ -67,8 +177,25 @@ jobs: - uses: actions/setup-node@v2 with: node-version: 16 - - run: npm install - - run: cd examples && npm i && npm run test -- echo + - uses: actions/cache@v2 + id: cache + env: + CACHE_NAME: cache-node-modules + with: + path: | + ~/.cache + ~/.npm + ./node_modules + ./dist + ./examples/node_modules + key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm install + npm run build + cd examples && npm i + - run: cd examples && npm run test -- echo test-libp2p-in-the-browser-example: needs: check runs-on: macos-latest @@ -77,8 +204,25 @@ jobs: - uses: actions/setup-node@v2 with: node-version: 16 - - run: npm install - - run: cd examples && npm i && npm run test -- libp2p-in-the-browser + - uses: actions/cache@v2 + id: cache + env: + CACHE_NAME: cache-node-modules + with: + path: | + ~/.cache + ~/.npm + ./node_modules + ./dist + ./examples/node_modules + key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm install + npm run build + cd examples && npm i + - run: cd examples && npm run test -- libp2p-in-the-browser test-peer-and-content-routing-example: needs: check runs-on: ubuntu-latest @@ -87,8 +231,25 @@ jobs: - uses: actions/setup-node@v2 with: node-version: 16 - - run: npm install - - run: cd examples && npm i && npm run test -- peer-and-content-routing + - uses: actions/cache@v2 + id: cache + env: + CACHE_NAME: cache-node-modules + with: + path: | + ~/.cache + ~/.npm + ./node_modules + ./dist + ./examples/node_modules + key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm install + npm run build + cd examples && npm i + - run: cd examples && npm run test -- peer-and-content-routing test-pnet-example: needs: check runs-on: ubuntu-latest @@ -97,8 +258,25 @@ jobs: - uses: actions/setup-node@v2 with: node-version: 16 - - run: npm install - - run: cd examples && npm i && npm run test -- pnet + - uses: actions/cache@v2 + id: cache + env: + CACHE_NAME: cache-node-modules + with: + path: | + ~/.cache + ~/.npm + ./node_modules + ./dist + ./examples/node_modules + key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm install + npm run build + cd examples && npm i + - run: cd examples && npm run test -- pnet test-protocol-and-stream-muxing-example: needs: check runs-on: ubuntu-latest @@ -107,8 +285,25 @@ jobs: - uses: actions/setup-node@v2 with: node-version: 16 - - run: npm install - - run: cd examples && npm i && npm run test -- protocol-and-stream-muxing + - uses: actions/cache@v2 + id: cache + env: + CACHE_NAME: cache-node-modules + with: + path: | + ~/.cache + ~/.npm + ./node_modules + ./dist + ./examples/node_modules + key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm install + npm run build + cd examples && npm i + - run: cd examples && npm run test -- protocol-and-stream-muxing test-pubsub-example: needs: check runs-on: ubuntu-latest @@ -117,8 +312,25 @@ jobs: - uses: actions/setup-node@v2 with: node-version: 16 - - run: npm install - - run: cd examples && npm i && npm run test -- pubsub + - uses: actions/cache@v2 + id: cache + env: + CACHE_NAME: cache-node-modules + with: + path: | + ~/.cache + ~/.npm + ./node_modules + ./dist + ./examples/node_modules + key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm install + npm run build + cd examples && npm i + - run: cd examples && npm run test -- pubsub test-transports-example: needs: check runs-on: ubuntu-latest @@ -127,8 +339,25 @@ jobs: - uses: actions/setup-node@v2 with: node-version: 16 - - run: npm install - - run: cd examples && npm i && npm run test -- transports + - uses: actions/cache@v2 + id: cache + env: + CACHE_NAME: cache-node-modules + with: + path: | + ~/.cache + ~/.npm + ./node_modules + ./dist + ./examples/node_modules + key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm install + npm run build + cd examples && npm i + - run: cd examples && npm run test -- transports test-webrtc-direct-example: needs: check runs-on: ubuntu-latest @@ -137,5 +366,22 @@ jobs: - uses: actions/setup-node@v2 with: node-version: 16 - - run: npm install -g @mapbox/node-pre-gyp && npm install - - run: cd examples && npm i && npm run test -- webrtc-direct \ No newline at end of file + - uses: actions/cache@v2 + id: cache + env: + CACHE_NAME: cache-node-modules + with: + path: | + ~/.cache + ~/.npm + ./node_modules + ./dist + ./examples/node_modules + key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm install + npm run build + cd examples && npm i + - run: cd examples && npm run test -- webrtc-direct diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f4ffb7e2ab..e92a69b7b4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,21 +8,65 @@ on: - '**' jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + node: [16] + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: 16 + - uses: actions/cache@v2 + id: cache + env: + CACHE_NAME: cache-node-modules + with: + path: | + ~/.cache + ~/.npm + ./node_modules + ./dist + key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm install + npm run build + check: + needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: 16 - - run: npm install + - uses: actions/cache@v2 + id: cache + env: + CACHE_NAME: cache-node-modules + with: + path: | + ~/.cache + ~/.npm + ./node_modules + ./dist + key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm install + npm run build - run: npx aegir lint - - run: npx aegir build - run: npx aegir dep-check - uses: ipfs/aegir/actions/bundle-size@master name: size with: github_token: ${{ secrets.GITHUB_TOKEN }} + test-node: needs: check runs-on: ${{ matrix.os }} @@ -36,7 +80,23 @@ jobs: - uses: actions/setup-node@v2 with: node-version: ${{ matrix.node }} - - run: npm install + - uses: actions/cache@v2 + id: cache + if: matrix.os != 'windows-latest' + env: + CACHE_NAME: cache-node-modules + with: + path: | + ~/.cache + ~/.npm + ./node_modules + ./dist + key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm install + npm run build - run: npm run test:node -- --cov --bail - uses: codecov/codecov-action@v1 test-chrome: @@ -47,7 +107,22 @@ jobs: - uses: actions/setup-node@v2 with: node-version: lts/* - - run: npm install + - uses: actions/cache@v2 + id: cache + env: + CACHE_NAME: cache-node-modules + with: + path: | + ~/.cache + ~/.npm + ./node_modules + ./dist + key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm install + npm run build - run: npm run test:browser -- -t browser -t webworker --bail test-firefox: needs: check @@ -57,7 +132,22 @@ jobs: - uses: actions/setup-node@v2 with: node-version: lts/* - - run: npm install + - uses: actions/cache@v2 + id: cache + env: + CACHE_NAME: cache-node-modules + with: + path: | + ~/.cache + ~/.npm + ./node_modules + ./dist + key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm install + npm run build - run: npm run test:browser -- -t browser -t webworker --bail -- --browser firefox test-ts: needs: check @@ -67,7 +157,22 @@ jobs: - uses: actions/setup-node@v2 with: node-version: lts/* - - run: npm install + - uses: actions/cache@v2 + id: cache + env: + CACHE_NAME: cache-node-modules + with: + path: | + ~/.cache + ~/.npm + ./node_modules + ./dist + key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm install + npm run build - run: npm run test:ts test-interop: needs: check @@ -77,5 +182,20 @@ jobs: - uses: actions/setup-node@v2 with: node-version: lts/* - - run: npm install + - uses: actions/cache@v2 + id: cache + env: + CACHE_NAME: cache-node-modules + with: + path: | + ~/.cache + ~/.npm + ./node_modules + ./dist + key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm install + npm run build - run: npm run test:interop -- --bail -- --exit diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index 0b912d1e56..a77ab6faee 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -12,6 +12,7 @@ "devDependencies": { "@babel/cli": "^7.13.10", "@babel/core": "^7.13.10", + "@mapbox/node-pre-gyp": "^1.0.8", "babel-plugin-syntax-async-functions": "^6.13.0", "babel-plugin-transform-regenerator": "^6.26.0", "babel-polyfill": "^6.26.0", diff --git a/package.json b/package.json index ce0ff5a28f..ef014f7124 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,7 @@ "libp2p-floodsub": "^0.28.0", "libp2p-gossipsub": "^0.12.1", "libp2p-interfaces-compliance-tests": "^2.0.1", - "libp2p-interop": "^0.5.0", + "libp2p-interop": "^0.6.0", "libp2p-kad-dht": "^0.27.1", "libp2p-mdns": "^0.18.0", "libp2p-mplex": "^0.10.1", diff --git a/test/peer-routing/peer-routing.node.js b/test/peer-routing/peer-routing.node.js index f7835898a6..4c008a1c03 100644 --- a/test/peer-routing/peer-routing.node.js +++ b/test/peer-routing/peer-routing.node.js @@ -75,11 +75,11 @@ describe('peer-routing', () => { it('should use the nodes dht', async () => { sinon.stub(nodes[0]._dht, 'findPeer').callsFake(async function * () { yield { - name: 'PEER_RESPONSE', - closer: [{ + name: 'FINAL_PEER', + peer: { id: nodes[1].peerId, multiaddrs: [] - }] + } } }) @@ -448,10 +448,8 @@ describe('peer-routing', () => { sinon.stub(node._dht, 'findPeer').callsFake(async function * () { yield { - name: 'PEER_RESPONSE', - closer: [ - result - ] + name: 'FINAL_PEER', + peer: result } }) sinon.stub(delegate, 'findPeer').callsFake(async () => { @@ -477,10 +475,8 @@ describe('peer-routing', () => { sinon.stub(node._dht, 'findPeer').callsFake(async function * () { yield { - name: 'PEER_RESPONSE', - closer: [ - result - ] + name: 'FINAL_PEER', + peer: result } }) sinon.stub(delegate, 'findPeer').callsFake(() => {}) From 4cadbad10253b516b0abb79fc0bd887243f77701 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 7 Jan 2022 10:40:05 +0000 Subject: [PATCH 313/447] chore: remove pubsub dev deps (#1107) --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index ef014f7124..0f3ae00a9e 100644 --- a/package.json +++ b/package.json @@ -152,8 +152,6 @@ "libp2p-bootstrap": "^0.14.0", "libp2p-delegated-content-routing": "^0.11.0", "libp2p-delegated-peer-routing": "^0.11.1", - "libp2p-floodsub": "^0.28.0", - "libp2p-gossipsub": "^0.12.1", "libp2p-interfaces-compliance-tests": "^2.0.1", "libp2p-interop": "^0.6.0", "libp2p-kad-dht": "^0.27.1", From 5e5d11ec1979ff3846dae3e114f7ce909495635a Mon Sep 17 00:00:00 2001 From: dadepo Date: Sat, 8 Jan 2022 17:57:37 +0100 Subject: [PATCH 314/447] docs: fix import of datastore-level (#1086) --- doc/CONFIGURATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 89d9197340..3ebf6689b0 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -497,7 +497,7 @@ const Libp2p = require('libp2p') const TCP = require('libp2p-tcp') const MPLEX = require('libp2p-mplex') const { NOISE } = require('libp2p-noise') -const LevelDatastore = require('datastore-level') +const { LevelDatastore } = require('datastore-level') const datastore = new LevelDatastore('path/to/store') await datastore.open() From 96d34613933de3c85d700fe415354fd0a5548562 Mon Sep 17 00:00:00 2001 From: dadepo Date: Mon, 10 Jan 2022 11:56:08 +0100 Subject: [PATCH 315/447] docs: Include loadkeychain in the index (#1087) --- doc/API.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/API.md b/doc/API.md index 9ec8a67610..96fc9fc62b 100644 --- a/doc/API.md +++ b/doc/API.md @@ -3,6 +3,7 @@ * [Static Functions](#static-functions) * [`create`](#create) * [Instance Methods](#libp2p-instance-methods) + * [`loadkeychain`](#loadkeychain) * [`start`](#start) * [`stop`](#stop) * [`dial`](#dial) From c3700f55d5a0b62182d683ca37258887b24065b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Fri, 14 Jan 2022 16:33:17 +0100 Subject: [PATCH 316/447] fix: import uint8arrays package in example (#1083) --- examples/pubsub/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/pubsub/README.md b/examples/pubsub/README.md index 9ea93dc0e4..ca8051cf06 100644 --- a/examples/pubsub/README.md +++ b/examples/pubsub/README.md @@ -41,6 +41,8 @@ const node = await Libp2p.create({ Once that is done, we only need to create a few libp2p nodes, connect them and everything is ready to start using pubsub. ```JavaScript +const { fromString } = require('uint8arrays/from-string') +const { toString } = require('uint8arrays/to-string') const topic = 'news' const node1 = nodes[0] @@ -51,19 +53,19 @@ node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) await node1.dial(node2.peerId) node1.pubsub.on(topic, (msg) => { - console.log(`node1 received: ${uint8ArrayToString(msg.data)}`) + console.log(`node1 received: ${toString(msg.data)}`) }) await node1.pubsub.subscribe(topic) // Will not receive own published messages by default node2.pubsub.on(topic, (msg) => { - console.log(`node2 received: ${uint8ArrayToString(msg.data)}`) + console.log(`node2 received: ${toString(msg.data)}`) }) await node2.pubsub.subscribe(topic) // node2 publishes "news" every second setInterval(() => { - node2.pubsub.publish(topic, uint8ArrayFromString('Bird bird bird, bird is the word!')) + node2.pubsub.publish(topic, fromString('Bird bird bird, bird is the word!')) }, 1000) ``` From 0a4dc54d084c901df47cce1788bd5922090ee037 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 20 Jan 2022 12:02:13 +0000 Subject: [PATCH 317/447] fix: remove abort-controller dep (#1095) The `AbortController` class is supported by browsers and node 14+ - we only support node 16+ (e.g. LTS+Current) so the `abort-controller` module isn't needed any more. --- package.json | 1 - test/dialing/dial-request.spec.js | 1 - test/utils/mockMultiaddrConn.js | 1 - 3 files changed, 3 deletions(-) diff --git a/package.json b/package.json index 0f3ae00a9e..64a358de6d 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,6 @@ }, "dependencies": { "@vascosantos/moving-average": "^1.1.0", - "abort-controller": "^3.0.0", "abortable-iterator": "^3.0.0", "aggregate-error": "^3.1.0", "any-signal": "^2.1.1", diff --git a/test/dialing/dial-request.spec.js b/test/dialing/dial-request.spec.js index 2c72fb7b87..3ec2eba8aa 100644 --- a/test/dialing/dial-request.spec.js +++ b/test/dialing/dial-request.spec.js @@ -5,7 +5,6 @@ const { expect } = require('aegir/utils/chai') const sinon = require('sinon') const { AbortError } = require('libp2p-interfaces/src/transport/errors') -const AbortController = require('abort-controller') const AggregateError = require('aggregate-error') const pDefer = require('p-defer') const delay = require('delay') diff --git a/test/utils/mockMultiaddrConn.js b/test/utils/mockMultiaddrConn.js index bb0cf6df7c..26482dd349 100644 --- a/test/utils/mockMultiaddrConn.js +++ b/test/utils/mockMultiaddrConn.js @@ -2,7 +2,6 @@ const duplexPair = require('it-pair/duplex') const abortable = require('abortable-iterator') -const AbortController = require('abort-controller') /** * Returns both sides of a mocked MultiaddrConnection From 978eb3676fad5d5d50ddb28d1a7868f448cbb20b Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 20 Jan 2022 12:03:35 +0000 Subject: [PATCH 318/447] feat: async peerstore backed by datastores (#1058) We have a peerstore that keeps all data for all observed peers in memory with no eviction. This is fine when you don't discover many peers but when using the DHT you encounter a significant number of peers so our peer storage grows and grows over time. We have a persistent peer store, but it just periodically writes peers into the datastore to be read at startup, still keeping them in memory. It also means a restart doesn't give you any temporary reprieve from the memory leak as the previously observed peer data is read into memory at startup. This change refactors the peerstore to use a datastore by default, reading and writing peer info as it arrives. It can be configured with a MemoryDatastore if desired. It was necessary to change the peerstore and *book interfaces to be asynchronous since the datastore api is asynchronous. BREAKING CHANGE: `libp2p.handle`, `libp2p.registrar.register` and the peerstore methods have become async --- .github/workflows/examples.yml | 400 ++--------- .github/workflows/main.yml | 124 +--- examples/connection-encryption/1.js | 2 +- examples/delegated-routing/package.json | 20 +- examples/delegated-routing/src/App.js | 4 +- .../delegated-routing/src/libp2p-bundle.js | 4 +- examples/discovery-mechanisms/3.js | 2 +- examples/discovery-mechanisms/bootstrapers.js | 4 +- examples/discovery-mechanisms/test-1.js | 26 +- examples/libp2p-in-the-browser/.babelrc | 1 - examples/libp2p-in-the-browser/package.json | 7 +- examples/package.json | 2 +- examples/peer-and-content-routing/1.js | 4 +- examples/peer-and-content-routing/2.js | 4 +- examples/peer-and-content-routing/README.md | 4 +- examples/peer-and-content-routing/test-1.js | 19 +- examples/peer-and-content-routing/test-2.js | 22 +- examples/pnet/index.js | 2 +- examples/protocol-and-stream-muxing/1.js | 2 +- examples/protocol-and-stream-muxing/2.js | 2 +- examples/protocol-and-stream-muxing/3.js | 4 +- examples/protocol-and-stream-muxing/README.md | 2 +- examples/pubsub/1.js | 4 +- examples/pubsub/README.md | 2 +- examples/pubsub/message-filtering/1.js | 6 +- examples/pubsub/message-filtering/README.md | 4 +- examples/transports/2.js | 2 +- examples/transports/3.js | 6 +- examples/transports/README.md | 8 +- examples/utils.js | 2 +- examples/webrtc-direct/listener.js | 1 - examples/webrtc-direct/package.json | 4 +- package.json | 25 +- src/circuit/auto-relay.js | 38 +- src/connection-manager/auto-dialler.js | 9 +- src/connection-manager/index.js | 61 +- src/content-routing/utils.js | 8 +- src/dialer/dial-request.js | 5 +- src/dialer/index.js | 27 +- src/identify/index.js | 51 +- src/index.js | 60 +- src/metrics/index.js | 23 +- src/metrics/tracked-map.js | 54 +- src/peer-routing.js | 5 +- src/peer-store/address-book.js | 469 ++++++------- src/peer-store/book.js | 124 ---- src/peer-store/index.js | 187 +++-- src/peer-store/key-book.js | 153 +++-- src/peer-store/metadata-book.js | 272 +++++--- src/peer-store/pb/peer.d.ts | 222 ++++++ src/peer-store/pb/peer.js | 643 ++++++++++++++++++ src/peer-store/pb/peer.proto | 31 + src/peer-store/persistent/consts.js | 15 - src/peer-store/persistent/index.js | 408 ----------- .../persistent/pb/address-book.d.ts | 198 ------ src/peer-store/persistent/pb/address-book.js | 522 -------------- .../persistent/pb/address-book.proto | 27 - src/peer-store/persistent/pb/proto-book.d.ts | 59 -- src/peer-store/persistent/pb/proto-book.js | 157 ----- src/peer-store/persistent/pb/proto-book.proto | 5 - src/peer-store/proto-book.js | 276 +++++--- src/peer-store/store.js | 250 +++++++ src/peer-store/types.ts | 245 +++++++ src/ping/index.js | 6 + src/record/peer-record/index.js | 2 +- src/record/utils.js | 2 +- src/registrar.js | 8 +- src/upgrader.js | 4 +- test/configuration/protocol-prefix.node.js | 24 +- test/connection-manager/index.node.js | 8 +- test/connection-manager/index.spec.js | 6 +- test/content-routing/content-routing.node.js | 6 +- test/content-routing/dht/operation.node.js | 8 +- test/dialing/direct.node.js | 33 +- test/dialing/direct.spec.js | 9 +- test/identify/index.spec.js | 94 ++- test/metrics/index.spec.js | 6 +- test/peer-routing/peer-routing.node.js | 6 +- test/peer-store/address-book.spec.js | 242 ++++--- test/peer-store/key-book.spec.js | 83 ++- test/peer-store/metadata-book.spec.js | 200 +++--- test/peer-store/peer-store.node.js | 10 +- test/peer-store/peer-store.spec.js | 167 ++--- test/peer-store/persisted-peer-store.spec.js | 608 ----------------- test/peer-store/proto-book.spec.js | 209 +++--- test/registrar/registrar.spec.js | 44 +- test/relay/auto-relay.node.js | 42 +- test/relay/relay.node.js | 8 +- test/transports/transport-manager.node.js | 9 +- test/ts-use/package.json | 14 +- test/ts-use/src/main.ts | 3 +- test/utils/creators/peer.js | 5 +- test/utils/mockConnection.js | 2 +- tsconfig.json | 3 +- 94 files changed, 3212 insertions(+), 3988 deletions(-) delete mode 100644 src/peer-store/book.js create mode 100644 src/peer-store/pb/peer.d.ts create mode 100644 src/peer-store/pb/peer.js create mode 100644 src/peer-store/pb/peer.proto delete mode 100644 src/peer-store/persistent/consts.js delete mode 100644 src/peer-store/persistent/index.js delete mode 100644 src/peer-store/persistent/pb/address-book.d.ts delete mode 100644 src/peer-store/persistent/pb/address-book.js delete mode 100644 src/peer-store/persistent/pb/address-book.proto delete mode 100644 src/peer-store/persistent/pb/proto-book.d.ts delete mode 100644 src/peer-store/persistent/pb/proto-book.js delete mode 100644 src/peer-store/persistent/pb/proto-book.proto create mode 100644 src/peer-store/store.js create mode 100644 src/peer-store/types.ts delete mode 100644 test/peer-store/persisted-peer-store.spec.js diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 0e46e95155..c99a803232 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -8,380 +8,58 @@ on: - '**' jobs: + build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist + directories: | ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - check: - needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: npx aegir lint - - run: npx aegir ts -p check - test-auto-relay-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- auto-relay - test-chat-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- chat - test-connection-encryption-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- connection-encryption - test-discovery-mechanisms-example: - needs: check - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- discovery-mechanisms - test-echo-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- echo - test-libp2p-in-the-browser-example: - needs: check - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- libp2p-in-the-browser - test-peer-and-content-routing-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- peer-and-content-routing - test-pnet-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- pnet - test-protocol-and-stream-muxing-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- protocol-and-stream-muxing - test-pubsub-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- pubsub - test-transports-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- transports - test-webrtc-direct-example: - needs: check + build: | + cd examples + npm i + npx playwright install + cache_name: cache-examples + + test-example: + needs: build runs-on: ubuntu-latest + strategy: + matrix: + example: [ + chat, + connection-encryption, + discovery-mechanisms, + echo, + libp2p-in-the-browser, + peer-and-content-routing, + pnet, + protocol-and-stream-muxing, + pubsub, + transports, + webrtc-direct + ] + fail-fast: true steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist + directories: | ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- webrtc-direct + build: | + cd examples + npm i + npx playwright install + cache_name: cache-examples + - run: | + cd examples + npm run test -- ${{ matrix.example }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e92a69b7b4..3a4f76f00c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,29 +12,14 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-latest] + os: [ubuntu-latest] node: [16] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build + - uses: ipfs/aegir/actions/cache-node-modules@master check: needs: build @@ -43,23 +28,8 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master - run: npx aegir lint - run: npx aegir dep-check - uses: ipfs/aegir/actions/bundle-size@master @@ -80,23 +50,7 @@ jobs: - uses: actions/setup-node@v2 with: node-version: ${{ matrix.node }} - - uses: actions/cache@v2 - id: cache - if: matrix.os != 'windows-latest' - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build + - uses: ipfs/aegir/actions/cache-node-modules@master - run: npm run test:node -- --cov --bail - uses: codecov/codecov-action@v1 test-chrome: @@ -107,22 +61,7 @@ jobs: - uses: actions/setup-node@v2 with: node-version: lts/* - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build + - uses: ipfs/aegir/actions/cache-node-modules@master - run: npm run test:browser -- -t browser -t webworker --bail test-firefox: needs: check @@ -132,22 +71,7 @@ jobs: - uses: actions/setup-node@v2 with: node-version: lts/* - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build + - uses: ipfs/aegir/actions/cache-node-modules@master - run: npm run test:browser -- -t browser -t webworker --bail -- --browser firefox test-ts: needs: check @@ -157,22 +81,7 @@ jobs: - uses: actions/setup-node@v2 with: node-version: lts/* - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build + - uses: ipfs/aegir/actions/cache-node-modules@master - run: npm run test:ts test-interop: needs: check @@ -182,20 +91,5 @@ jobs: - uses: actions/setup-node@v2 with: node-version: lts/* - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build + - uses: ipfs/aegir/actions/cache-node-modules@master - run: npm run test:interop -- --bail -- --exit diff --git a/examples/connection-encryption/1.js b/examples/connection-encryption/1.js index 0b8699152d..fccb75377f 100644 --- a/examples/connection-encryption/1.js +++ b/examples/connection-encryption/1.js @@ -30,7 +30,7 @@ const createNode = async () => { createNode() ]) - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) node2.handle('/a-protocol', ({ stream }) => { pipe( diff --git a/examples/delegated-routing/package.json b/examples/delegated-routing/package.json index 76a06b46ed..6893edc3c4 100644 --- a/examples/delegated-routing/package.json +++ b/examples/delegated-routing/package.json @@ -3,16 +3,16 @@ "version": "0.1.0", "private": true, "dependencies": { - "ipfs": "~0.34.4", - "libp2p": "github:libp2p/js-libp2p#master", - "libp2p-delegated-content-routing": "~0.2.2", - "libp2p-delegated-peer-routing": "~0.2.2", - "libp2p-kad-dht": "^0.26.5", - "libp2p-mplex": "~0.8.5", - "libp2p-secio": "~0.11.1", - "libp2p-webrtc-star": "~0.15.8", - "libp2p-websocket-star": "~0.10.2", - "libp2p-websockets": "~0.12.2", + "@chainsafe/libp2p-noise": "^5.0.2", + "ipfs-core": "^0.13.0", + "libp2p": "../../", + "libp2p-delegated-content-routing": "^0.11.0", + "libp2p-delegated-peer-routing": "^0.11.1", + "libp2p-kad-dht": "^0.28.6", + "libp2p-mplex": "^0.10.4", + "libp2p-webrtc-star": "^0.25.0", + "libp2p-websocket-star": "^0.10.2", + "libp2p-websockets": "^0.16.2", "react": "^16.8.6", "react-dom": "^16.8.6", "react-scripts": "2.1.8" diff --git a/examples/delegated-routing/src/App.js b/examples/delegated-routing/src/App.js index ce6af6777f..830fd758c1 100644 --- a/examples/delegated-routing/src/App.js +++ b/examples/delegated-routing/src/App.js @@ -2,7 +2,7 @@ 'use strict' import React from 'react' -import Ipfs from 'ipfs' +import Ipfs from 'ipfs-core' import libp2pBundle from './libp2p-bundle' const Component = React.Component @@ -70,7 +70,7 @@ class App extends Component { } componentDidMount () { - window.ipfs = this.ipfs = new Ipfs({ + window.ipfs = this.ipfs = Ipfs.create({ config: { Addresses: { Swarm: [] diff --git a/examples/delegated-routing/src/libp2p-bundle.js b/examples/delegated-routing/src/libp2p-bundle.js index 48fb090699..0ff6786eff 100644 --- a/examples/delegated-routing/src/libp2p-bundle.js +++ b/examples/delegated-routing/src/libp2p-bundle.js @@ -6,7 +6,7 @@ const Websockets = require('libp2p-websockets') const WebSocketStar = require('libp2p-websocket-star') const WebRTCStar = require('libp2p-webrtc-star') const MPLEX = require('libp2p-mplex') -const SECIO = require('libp2p-secio') +const { NOISE } = require('@chainsafe/libp2p-noise') const KadDHT = require('libp2p-kad-dht') const DelegatedPeerRouter = require('libp2p-delegated-peer-routing') const DelegatedContentRouter = require('libp2p-delegated-content-routing') @@ -48,7 +48,7 @@ export default function Libp2pBundle ({peerInfo, peerBook}) { MPLEX ], connEncryption: [ - SECIO + NOISE ], dht: KadDHT }, diff --git a/examples/discovery-mechanisms/3.js b/examples/discovery-mechanisms/3.js index 4fb1be1264..645025d4d5 100644 --- a/examples/discovery-mechanisms/3.js +++ b/examples/discovery-mechanisms/3.js @@ -5,7 +5,7 @@ const Libp2p = require('../../') const TCP = require('libp2p-tcp') const Mplex = require('libp2p-mplex') const { NOISE } = require('@chainsafe/libp2p-noise') -const Gossipsub = require('libp2p-gossipsub') +const Gossipsub = require('@achingbrain/libp2p-gossipsub') const Bootstrap = require('libp2p-bootstrap') const PubsubPeerDiscovery = require('libp2p-pubsub-peer-discovery') diff --git a/examples/discovery-mechanisms/bootstrapers.js b/examples/discovery-mechanisms/bootstrapers.js index 384f0b5eec..8ebedb1f93 100644 --- a/examples/discovery-mechanisms/bootstrapers.js +++ b/examples/discovery-mechanisms/bootstrapers.js @@ -1,13 +1,13 @@ 'use strict' -// Find this list at: https://github.com/ipfs/js-ipfs/blob/master/packages/ipfs-core/src/runtime/config-nodejs.js +// Find this list at: https://github.com/ipfs/js-ipfs/blob/master/packages/ipfs-core-config/src/config.js const bootstrapers = [ '/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ', '/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' + '/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt', ] module.exports = bootstrapers diff --git a/examples/discovery-mechanisms/test-1.js b/examples/discovery-mechanisms/test-1.js index 0f76fa2f1b..36f297b3b3 100644 --- a/examples/discovery-mechanisms/test-1.js +++ b/examples/discovery-mechanisms/test-1.js @@ -2,17 +2,9 @@ const path = require('path') const execa = require('execa') -const pWaitFor = require('p-wait-for') const { toString: uint8ArrayToString } = require('uint8arrays/to-string') -const bootstrapers = require('./bootstrapers') - -const discoveredCopy = 'Discovered:' -const connectedCopy = 'Connection established to:' async function test () { - const discoveredNodes = [] - const connectedNodes = [] - process.stdout.write('1.js\n') const proc = execa('node', [path.join(__dirname, '1.js')], { @@ -20,23 +12,17 @@ async function test () { all: true }) + let output = '' + proc.all.on('data', async (data) => { process.stdout.write(data) - const line = uint8ArrayToString(data) + output += uint8ArrayToString(data) - // Discovered or Connected - if (line.includes(discoveredCopy)) { - const id = line.trim().split(discoveredCopy)[1] - discoveredNodes.push(id) - } else if (line.includes(connectedCopy)) { - const id = line.trim().split(connectedCopy)[1] - connectedNodes.push(id) + // Discovered and connected + if (output.includes('Connection established to:')) { + proc.kill() } }) - - await pWaitFor(() => discoveredNodes.length === bootstrapers.length && connectedNodes.length === bootstrapers.length) - - proc.kill() } module.exports = test diff --git a/examples/libp2p-in-the-browser/.babelrc b/examples/libp2p-in-the-browser/.babelrc index 620e785799..2145517d82 100644 --- a/examples/libp2p-in-the-browser/.babelrc +++ b/examples/libp2p-in-the-browser/.babelrc @@ -1,4 +1,3 @@ { - "presets": ["@babel/preset-env"], "plugins": ["syntax-async-functions","transform-regenerator"] } \ No newline at end of file diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json index 9a45c90d51..f515deb44a 100644 --- a/examples/libp2p-in-the-browser/package.json +++ b/examples/libp2p-in-the-browser/package.json @@ -14,12 +14,11 @@ "author": "", "license": "ISC", "dependencies": { - "@babel/preset-env": "^7.13.0", + "@chainsafe/libp2p-noise": "^5.0.2", "libp2p": "../../", - "libp2p-bootstrap": "^0.13.0", + "libp2p-bootstrap": "^0.14.0", "libp2p-mplex": "^0.10.4", - "@chainsafe/libp2p-noise": "^4.1.0", - "libp2p-webrtc-star": "^0.23.0", + "libp2p-webrtc-star": "^0.25.0", "libp2p-websockets": "^0.16.1" }, "devDependencies": { diff --git a/examples/package.json b/examples/package.json index b69c9ed6e8..bee67556b3 100644 --- a/examples/package.json +++ b/examples/package.json @@ -8,12 +8,12 @@ }, "license": "MIT", "dependencies": { + "@achingbrain/libp2p-gossipsub": "^0.12.2", "execa": "^2.1.0", "fs-extra": "^8.1.0", "libp2p": "../src", "libp2p-pubsub-peer-discovery": "^4.0.0", "libp2p-relay-server": "^0.3.0", - "libp2p-gossipsub": "^0.11.0", "p-defer": "^3.0.0", "uint8arrays": "^3.0.0", "which": "^2.0.1" diff --git a/examples/peer-and-content-routing/1.js b/examples/peer-and-content-routing/1.js index bf2c6d1b15..16b2dfbcfd 100644 --- a/examples/peer-and-content-routing/1.js +++ b/examples/peer-and-content-routing/1.js @@ -38,8 +38,8 @@ const createNode = async () => { createNode() ]) - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) - node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) await Promise.all([ node1.dial(node2.peerId), diff --git a/examples/peer-and-content-routing/2.js b/examples/peer-and-content-routing/2.js index 4b528e4ac6..bcad5977e7 100644 --- a/examples/peer-and-content-routing/2.js +++ b/examples/peer-and-content-routing/2.js @@ -40,8 +40,8 @@ const createNode = async () => { createNode() ]) - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) - node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) await Promise.all([ node1.dial(node2.peerId), diff --git a/examples/peer-and-content-routing/README.md b/examples/peer-and-content-routing/README.md index 197ecf32fc..373f10d7a2 100644 --- a/examples/peer-and-content-routing/README.md +++ b/examples/peer-and-content-routing/README.md @@ -43,8 +43,8 @@ const node1 = nodes[0] const node2 = nodes[1] const node3 = nodes[2] -node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) -node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) +await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) +await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) await Promise.all([ node1.dial(node2.peerId), diff --git a/examples/peer-and-content-routing/test-1.js b/examples/peer-and-content-routing/test-1.js index 2a19b10dbc..9e10f7aeee 100644 --- a/examples/peer-and-content-routing/test-1.js +++ b/examples/peer-and-content-routing/test-1.js @@ -2,35 +2,28 @@ const path = require('path') const execa = require('execa') -const pWaitFor = require('p-wait-for') const { toString: uint8ArrayToString } = require('uint8arrays/to-string') async function test() { process.stdout.write('1.js\n') - const addrs = [] - let foundIt = false const proc = execa('node', [path.join(__dirname, '1.js')], { cwd: path.resolve(__dirname), all: true }) + let output = '' + proc.all.on('data', async (data) => { process.stdout.write(data) - const line = uint8ArrayToString(data) + output += uint8ArrayToString(data) - // Discovered peer - if (!foundIt && line.includes('Found it, multiaddrs are:')) { - foundIt = true + // Discovered peers + if (output.includes('Found it, multiaddrs are:')) { + proc.kill() } - - addrs.push(line) }) - - await pWaitFor(() => addrs.length === 2) - - proc.kill() } module.exports = test diff --git a/examples/peer-and-content-routing/test-2.js b/examples/peer-and-content-routing/test-2.js index 82dbcf925f..644820b687 100644 --- a/examples/peer-and-content-routing/test-2.js +++ b/examples/peer-and-content-routing/test-2.js @@ -2,39 +2,27 @@ const path = require('path') const execa = require('execa') -const pDefer = require('p-defer') const { toString: uint8ArrayToString } = require('uint8arrays/to-string') -const providedCopy = 'is providing' -const foundCopy = 'Found provider:' - async function test() { process.stdout.write('2.js\n') - const providedDefer = pDefer() - const foundDefer = pDefer() const proc = execa('node', [path.join(__dirname, '2.js')], { cwd: path.resolve(__dirname), all: true }) + let output = '' + proc.all.on('data', async (data) => { process.stdout.write(data) - const line = uint8ArrayToString(data) + output += uint8ArrayToString(data) - if (line.includes(providedCopy)) { - providedDefer.resolve() - } else if (line.includes(foundCopy)) { - foundDefer.resolve() + if (output.includes('Found provider:')) { + proc.kill() } }) - - await Promise.all([ - providedDefer.promise, - foundDefer.promise - ]) - proc.kill() } module.exports = test diff --git a/examples/pnet/index.js b/examples/pnet/index.js index 1e766d536f..090b9e0711 100644 --- a/examples/pnet/index.js +++ b/examples/pnet/index.js @@ -29,7 +29,7 @@ generate(otherSwarmKey) console.log('nodes started...') // Add node 2 data to node1's PeerStore - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) await node1.dial(node2.peerId) node2.handle('/private', ({ stream }) => { diff --git a/examples/protocol-and-stream-muxing/1.js b/examples/protocol-and-stream-muxing/1.js index 5499ba0b83..3f71e0f5a0 100644 --- a/examples/protocol-and-stream-muxing/1.js +++ b/examples/protocol-and-stream-muxing/1.js @@ -31,7 +31,7 @@ const createNode = async () => { ]) // Add node's 2 data to the PeerStore - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) // exact matching node2.handle('/your-protocol', ({ stream }) => { diff --git a/examples/protocol-and-stream-muxing/2.js b/examples/protocol-and-stream-muxing/2.js index 2fecbe32d7..a7fdb175e2 100644 --- a/examples/protocol-and-stream-muxing/2.js +++ b/examples/protocol-and-stream-muxing/2.js @@ -31,7 +31,7 @@ const createNode = async () => { ]) // Add node's 2 data to the PeerStore - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) node2.handle(['/a', '/b'], ({ protocol, stream }) => { pipe( diff --git a/examples/protocol-and-stream-muxing/3.js b/examples/protocol-and-stream-muxing/3.js index 23cf4e7693..c9a9148352 100644 --- a/examples/protocol-and-stream-muxing/3.js +++ b/examples/protocol-and-stream-muxing/3.js @@ -32,8 +32,8 @@ const createNode = async () => { ]) // Add node's 2 data to the PeerStore - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) - + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + node1.handle('/node-1', ({ stream }) => { pipe( stream, diff --git a/examples/protocol-and-stream-muxing/README.md b/examples/protocol-and-stream-muxing/README.md index 8098391f10..a4df1a8b57 100644 --- a/examples/protocol-and-stream-muxing/README.md +++ b/examples/protocol-and-stream-muxing/README.md @@ -20,7 +20,7 @@ const node1 = nodes[0] const node2 = nodes[1] // Add node's 2 data to the PeerStore -node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) +await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) // Here we are telling libp2p that if someone dials this node to talk with the `/your-protocol` // multicodec, the protocol identifier, please call this handler and give it the stream diff --git a/examples/pubsub/1.js b/examples/pubsub/1.js index f1f60eb9ac..20aa497014 100644 --- a/examples/pubsub/1.js +++ b/examples/pubsub/1.js @@ -5,7 +5,7 @@ const Libp2p = require('../../') const TCP = require('libp2p-tcp') const Mplex = require('libp2p-mplex') const { NOISE } = require('@chainsafe/libp2p-noise') -const Gossipsub = require('libp2p-gossipsub') +const Gossipsub = require('@achingbrain/libp2p-gossipsub') const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') const { toString: uint8ArrayToString } = require('uint8arrays/to-string') @@ -35,7 +35,7 @@ const createNode = async () => { ]) // Add node's 2 data to the PeerStore - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) await node1.dial(node2.peerId) node1.pubsub.on(topic, (msg) => { diff --git a/examples/pubsub/README.md b/examples/pubsub/README.md index ca8051cf06..654dd76d26 100644 --- a/examples/pubsub/README.md +++ b/examples/pubsub/README.md @@ -49,7 +49,7 @@ const node1 = nodes[0] const node2 = nodes[1] // Add node's 2 data to the PeerStore -node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) +await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) await node1.dial(node2.peerId) node1.pubsub.on(topic, (msg) => { diff --git a/examples/pubsub/message-filtering/1.js b/examples/pubsub/message-filtering/1.js index fce8d041c2..81a7830d4d 100644 --- a/examples/pubsub/message-filtering/1.js +++ b/examples/pubsub/message-filtering/1.js @@ -5,7 +5,7 @@ const Libp2p = require('../../../') const TCP = require('libp2p-tcp') const Mplex = require('libp2p-mplex') const { NOISE } = require('@chainsafe/libp2p-noise') -const Gossipsub = require('libp2p-gossipsub') +const Gossipsub = require('@achingbrain/libp2p-gossipsub') const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') const { toString: uint8ArrayToString } = require('uint8arrays/to-string') @@ -36,10 +36,10 @@ const createNode = async () => { ]) // node1 conect to node2 and node2 conect to node3 - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) await node1.dial(node2.peerId) - node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) + await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) await node2.dial(node3.peerId) //subscribe diff --git a/examples/pubsub/message-filtering/README.md b/examples/pubsub/message-filtering/README.md index df99043051..eb554afac1 100644 --- a/examples/pubsub/message-filtering/README.md +++ b/examples/pubsub/message-filtering/README.md @@ -32,10 +32,10 @@ const [node1, node2, node3] = await Promise.all([ createNode(), ]) -node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) +await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) await node1.dial(node2.peerId) -node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) +await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) await node2.dial(node3.peerId) ``` diff --git a/examples/transports/2.js b/examples/transports/2.js index ee1f1c83ab..2962a062a1 100644 --- a/examples/transports/2.js +++ b/examples/transports/2.js @@ -49,7 +49,7 @@ function printAddrs (node, number) { console.log(result.toString()) }) - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) const { stream } = await node1.dialProtocol(node2.peerId, '/print') await pipe( diff --git a/examples/transports/3.js b/examples/transports/3.js index b3843b15af..d9a83697c3 100644 --- a/examples/transports/3.js +++ b/examples/transports/3.js @@ -60,9 +60,9 @@ function print ({ stream }) { node2.handle('/print', print) node3.handle('/print', print) - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) - node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) - node3.peerStore.addressBook.set(node1.peerId, node1.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) + await node3.peerStore.addressBook.set(node1.peerId, node1.multiaddrs) // node 1 (TCP) dials to node 2 (TCP+WebSockets) const { stream } = await node1.dialProtocol(node2.peerId, '/print') diff --git a/examples/transports/README.md b/examples/transports/README.md index 18f5975bc7..e2f41e3ae4 100644 --- a/examples/transports/README.md +++ b/examples/transports/README.md @@ -140,7 +140,7 @@ Then add, console.log(result.toString()) }) - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) const { stream } = await node1.dialProtocol(node2.peerId, '/print') await pipe( @@ -224,9 +224,9 @@ node1.handle('/print', print) node2.handle('/print', print) node3.handle('/print', print) -node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) -node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) -node3.peerStore.addressBook.set(node1.peerId, node1.multiaddrs) +await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) +await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) +await node3.peerStore.addressBook.set(node1.peerId, node1.multiaddrs) // node 1 (TCP) dials to node 2 (TCP+WebSockets) const { stream } = await node1.dialProtocol(node2.peerId, '/print') diff --git a/examples/utils.js b/examples/utils.js index 95b509ceb4..1f6e571c97 100644 --- a/examples/utils.js +++ b/examples/utils.js @@ -30,7 +30,7 @@ async function waitForOutput (expectedOutput, command, args = [], opts = {}) { const proc = execa(command, args, opts) let output = '' - let time = 120000 + let time = 600000 let timeout = setTimeout(() => { throw new Error(`Did not see "${expectedOutput}" in output from "${[command].concat(args).join(' ')}" after ${time/1000}s`) diff --git a/examples/webrtc-direct/listener.js b/examples/webrtc-direct/listener.js index a8ac6cd9ae..b0b9cb4354 100644 --- a/examples/webrtc-direct/listener.js +++ b/examples/webrtc-direct/listener.js @@ -40,5 +40,4 @@ const PeerId = require('peer-id') console.log('Listening on:') node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`)) - })() diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index a77ab6faee..6eb9b4b0db 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -20,10 +20,10 @@ "util": "^0.12.3" }, "dependencies": { + "@chainsafe/libp2p-noise": "^5.0.2", "libp2p": "../../", - "libp2p-bootstrap": "^0.13.0", + "libp2p-bootstrap": "^0.14.0", "libp2p-mplex": "^0.10.4", - "@chainsafe/libp2p-noise": "^4.1.0", "libp2p-webrtc-direct": "^0.7.0", "peer-id": "^0.16.0" }, diff --git a/package.json b/package.json index 64a358de6d..24f32a78c5 100644 --- a/package.json +++ b/package.json @@ -20,20 +20,18 @@ "scripts": { "lint": "aegir lint", "build": "aegir build", - "build:proto": "npm run build:proto:circuit && npm run build:proto:identify && npm run build:proto:plaintext && npm run build:proto:address-book && npm run build:proto:proto-book && npm run build:proto:peer-record && npm run build:proto:envelope", + "build:proto": "npm run build:proto:circuit && npm run build:proto:identify && npm run build:proto:plaintext && npm run build:proto:address-book && npm run build:proto:proto-book && npm run build:proto:peer && npm run build:proto:peer-record && npm run build:proto:envelope", "build:proto:circuit": "pbjs -t static-module -w commonjs -r libp2p-circuit --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/circuit/protocol/index.js ./src/circuit/protocol/index.proto", "build:proto:identify": "pbjs -t static-module -w commonjs -r libp2p-identify --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/identify/message.js ./src/identify/message.proto", "build:proto:plaintext": "pbjs -t static-module -w commonjs -r libp2p-plaintext --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/insecure/proto.js ./src/insecure/proto.proto", - "build:proto:address-book": "pbjs -t static-module -w commonjs -r libp2p-address-book --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/peer-store/persistent/pb/address-book.js ./src/peer-store/persistent/pb/address-book.proto", - "build:proto:proto-book": "pbjs -t static-module -w commonjs -r libp2p-proto-book --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/peer-store/persistent/pb/proto-book.js ./src/peer-store/persistent/pb/proto-book.proto", + "build:proto:peer": "pbjs -t static-module -w commonjs -r libp2p-peer --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/peer-store/pb/peer.js ./src/peer-store/pb/peer.proto", "build:proto:peer-record": "pbjs -t static-module -w commonjs -r libp2p-peer-record --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/record/peer-record/peer-record.js ./src/record/peer-record/peer-record.proto", "build:proto:envelope": "pbjs -t static-module -w commonjs -r libp2p-envelope --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/record/envelope/envelope.js ./src/record/envelope/envelope.proto", - "build:proto-types": "npm run build:proto-types:circuit && npm run build:proto-types:identify && npm run build:proto-types:plaintext && npm run build:proto-types:address-book && npm run build:proto-types:proto-book && npm run build:proto-types:peer-record && npm run build:proto-types:envelope", + "build:proto-types": "npm run build:proto-types:circuit && npm run build:proto-types:identify && npm run build:proto-types:plaintext && npm run build:proto-types:address-book && npm run build:proto-types:proto-book && npm run build:proto-types:peer && npm run build:proto-types:peer-record && npm run build:proto-types:envelope", "build:proto-types:circuit": "pbts -o src/circuit/protocol/index.d.ts src/circuit/protocol/index.js", "build:proto-types:identify": "pbts -o src/identify/message.d.ts src/identify/message.js", "build:proto-types:plaintext": "pbts -o src/insecure/proto.d.ts src/insecure/proto.js", - "build:proto-types:address-book": "pbts -o src/peer-store/persistent/pb/address-book.d.ts src/peer-store/persistent/pb/address-book.js", - "build:proto-types:proto-book": "pbts -o src/peer-store/persistent/pb/proto-book.d.ts src/peer-store/persistent/pb/proto-book.js", + "build:proto-types:peer": "pbts -o src/peer-store/pb/peer.d.ts src/peer-store/pb/peer.js", "build:proto-types:peer-record": "pbts -o src/record/peer-record/peer-record.d.ts src/record/peer-record/peer-record.js", "build:proto-types:envelope": "pbts -o src/record/envelope/envelope.d.ts src/record/envelope/envelope.js", "test": "aegir test", @@ -86,6 +84,7 @@ "any-signal": "^2.1.1", "bignumber.js": "^9.0.1", "class-is": "^1.1.0", + "datastore-core": "^7.0.0", "debug": "^4.3.1", "err-code": "^3.0.0", "es6-promisify": "^7.0.0", @@ -103,11 +102,12 @@ "it-merge": "^1.0.0", "it-pipe": "^1.1.0", "it-take": "^1.0.0", - "libp2p-crypto": "^0.21.0", - "libp2p-interfaces": "^2.0.1", + "libp2p-crypto": "^0.21.1", + "libp2p-interfaces": "^4.0.0", "libp2p-utils": "^0.4.0", "mafmt": "^10.0.0", "merge-options": "^3.0.4", + "mortice": "^2.0.1", "multiaddr": "^10.0.0", "multiformats": "^9.0.0", "multistream-select": "^2.0.0", @@ -140,7 +140,6 @@ "@types/varint": "^6.0.0", "aegir": "^36.0.0", "buffer": "^6.0.3", - "datastore-core": "^6.0.7", "delay": "^5.0.0", "into-stream": "^6.0.0", "ipfs-http-client": "^54.0.2", @@ -151,11 +150,11 @@ "libp2p-bootstrap": "^0.14.0", "libp2p-delegated-content-routing": "^0.11.0", "libp2p-delegated-peer-routing": "^0.11.1", - "libp2p-interfaces-compliance-tests": "^2.0.1", - "libp2p-interop": "^0.6.0", - "libp2p-kad-dht": "^0.27.1", + "libp2p-interfaces-compliance-tests": "^4.0.8", + "libp2p-interop": "^0.7.1", + "libp2p-kad-dht": "^0.28.6", "libp2p-mdns": "^0.18.0", - "libp2p-mplex": "^0.10.1", + "libp2p-mplex": "^0.10.4", "libp2p-tcp": "^0.17.0", "libp2p-webrtc-star": "^0.25.0", "libp2p-websockets": "^0.16.0", diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js index 1c238f54a2..91d150ec2c 100644 --- a/src/circuit/auto-relay.js +++ b/src/circuit/auto-relay.js @@ -8,7 +8,6 @@ const log = Object.assign(debug('libp2p:auto-relay'), { const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') const { toString: uint8ArrayToString } = require('uint8arrays/to-string') const { Multiaddr } = require('multiaddr') -const PeerId = require('peer-id') const { relay: multicodec } = require('./multicodec') const { canHop } = require('./circuit/hop') @@ -22,7 +21,8 @@ const { /** * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection - * @typedef {import('../peer-store/address-book').Address} Address + * @typedef {import('../peer-store/types').Address} Address + * @typedef {import('peer-id')} PeerId */ /** @@ -91,7 +91,7 @@ class AutoRelay { // If no protocol, check if we were keeping the peer before as a listenRelay if (!hasProtocol && this._listenRelays.has(id)) { - this._removeListenRelay(id) + await this._removeListenRelay(id) return } else if (!hasProtocol || this._listenRelays.has(id)) { return @@ -113,7 +113,7 @@ class AutoRelay { const supportsHop = await canHop({ connection }) if (supportsHop) { - this._peerStore.metadataBook.set(peerId, HOP_METADATA_KEY, uint8ArrayFromString(HOP_METADATA_VALUE)) + await this._peerStore.metadataBook.setValue(peerId, HOP_METADATA_KEY, uint8ArrayFromString(HOP_METADATA_VALUE)) await this._addListenRelay(connection, id) } } catch (/** @type {any} */ err) { @@ -125,7 +125,6 @@ class AutoRelay { * Peer disconnects. * * @param {Connection} connection - connection to the peer - * @returns {void} */ _onPeerDisconnected (connection) { const peerId = connection.remotePeer @@ -136,7 +135,9 @@ class AutoRelay { return } - this._removeListenRelay(id) + this._removeListenRelay(id).catch(err => { + log.error(err) + }) } /** @@ -154,7 +155,7 @@ class AutoRelay { } // Get peer known addresses and sort them per public addresses first - const remoteAddrs = this._peerStore.addressBook.getMultiaddrsForPeer( + const remoteAddrs = await this._peerStore.addressBook.getMultiaddrsForPeer( connection.remotePeer, this._addressSorter ) @@ -180,12 +181,11 @@ class AutoRelay { * * @private * @param {string} id - peer identifier string. - * @returns {void} */ - _removeListenRelay (id) { + async _removeListenRelay (id) { if (this._listenRelays.delete(id)) { // TODO: this should be responsibility of the connMgr - this._listenOnAvailableHopRelays([id]) + await this._listenOnAvailableHopRelays([id]) } } @@ -197,7 +197,6 @@ class AutoRelay { * 3. Search the network. * * @param {string[]} [peersToIgnore] - * @returns {Promise} */ async _listenOnAvailableHopRelays (peersToIgnore = []) { // TODO: The peer redial issue on disconnect should be handled by connection gating @@ -209,29 +208,30 @@ class AutoRelay { 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()) { + for await (const { id, metadata } of this._peerStore.getPeers()) { + const idStr = id.toB58String() + // Continue to next if listening on this or peer to ignore - if (this._listenRelays.has(id) || peersToIgnore.includes(id)) { + if (this._listenRelays.has(idStr) || peersToIgnore.includes(idStr)) { continue } - const supportsHop = metadataMap.get(HOP_METADATA_KEY) + const supportsHop = metadata.get(HOP_METADATA_KEY) // Continue to next if it does not support Hop if (!supportsHop || uint8ArrayToString(supportsHop) !== HOP_METADATA_VALUE) { continue } - const peerId = PeerId.createFromB58String(id) - const connection = this._connectionManager.get(peerId) + const connection = this._connectionManager.get(id) // If not connected, store for possible later use. if (!connection) { - knownHopsToDial.push(peerId) + knownHopsToDial.push(id) continue } - await this._addListenRelay(connection, id) + await this._addListenRelay(connection, idStr) // Check if already listening on enough relays if (this._listenRelays.size >= this.maxListeners) { @@ -258,7 +258,7 @@ class AutoRelay { } const peerId = provider.id - this._peerStore.addressBook.add(peerId, provider.multiaddrs) + await this._peerStore.addressBook.add(peerId, provider.multiaddrs) await this._tryToListenOnRelay(peerId) diff --git a/src/connection-manager/auto-dialler.js b/src/connection-manager/auto-dialler.js index 5e71d7edcb..d09273875d 100644 --- a/src/connection-manager/auto-dialler.js +++ b/src/connection-manager/auto-dialler.js @@ -4,6 +4,7 @@ const debug = require('debug') const mergeOptions = require('merge-options') // @ts-ignore retimer does not have types const retimer = require('retimer') +const all = require('it-all') const log = Object.assign(debug('libp2p:connection-manager:auto-dialler'), { error: debug('libp2p:connection-manager:auto-dialler:err') @@ -50,7 +51,7 @@ class AutoDialler { /** * Starts the auto dialer */ - start () { + async start () { if (!this._options.enabled) { log('not enabled') return @@ -86,8 +87,10 @@ class AutoDialler { return } - // Sort peers on wether we know protocols of public keys for them - const peers = Array.from(this._libp2p.peerStore.peers.values()) + // Sort peers on whether we know protocols of public keys for them + // TODO: assuming the `peerStore.getPeers()` order is stable this will mean + // we keep trying to connect to the same peers? + const peers = (await all(this._libp2p.peerStore.getPeers())) .sort((a, b) => { if (b.protocols && b.protocols.length && (!a.protocols || !a.protocols.length)) { return 1 diff --git a/src/connection-manager/index.js b/src/connection-manager/index.js index ebdab62335..553455fd3e 100644 --- a/src/connection-manager/index.js +++ b/src/connection-manager/index.js @@ -87,14 +87,22 @@ class ConnectionManager extends EventEmitter { * * @type {Map} */ - this._peerValues = trackedMap(METRICS_COMPONENT, METRICS_PEER_VALUES, this._libp2p.metrics) + this._peerValues = trackedMap({ + component: METRICS_COMPONENT, + metric: METRICS_PEER_VALUES, + metrics: this._libp2p.metrics + }) /** * Map of connections per peer * * @type {Map} */ - this.connections = trackedMap(METRICS_COMPONENT, METRICS_PEER_CONNECTIONS, this._libp2p.metrics) + this.connections = trackedMap({ + component: METRICS_COMPONENT, + metric: METRICS_PEER_CONNECTIONS, + metrics: this._libp2p.metrics + }) this._started = false this._timer = null @@ -187,19 +195,22 @@ class ConnectionManager extends EventEmitter { * * @private */ - _checkMetrics () { + async _checkMetrics () { if (this._libp2p.metrics) { - const movingAverages = this._libp2p.metrics.global.movingAverages - // @ts-ignore moving averages object types - const received = movingAverages.dataReceived[this._options.movingAverageInterval].movingAverage() - this._checkMaxLimit('maxReceivedData', received) - // @ts-ignore moving averages object types - const sent = movingAverages.dataSent[this._options.movingAverageInterval].movingAverage() - this._checkMaxLimit('maxSentData', sent) - const total = received + sent - this._checkMaxLimit('maxData', total) - log('metrics update', total) - this._timer = retimer(this._checkMetrics, this._options.pollInterval) + try { + const movingAverages = this._libp2p.metrics.global.movingAverages + // @ts-ignore moving averages object types + const received = movingAverages.dataReceived[this._options.movingAverageInterval].movingAverage() + await this._checkMaxLimit('maxReceivedData', received) + // @ts-ignore moving averages object types + const sent = movingAverages.dataSent[this._options.movingAverageInterval].movingAverage() + await this._checkMaxLimit('maxSentData', sent) + const total = received + sent + await this._checkMaxLimit('maxData', total) + log('metrics update', total) + } finally { + this._timer = retimer(this._checkMetrics, this._options.pollInterval) + } } } @@ -207,9 +218,8 @@ class ConnectionManager extends EventEmitter { * Tracks the incoming connection and check the connection limit * * @param {Connection} connection - * @returns {void} */ - onConnect (connection) { + async onConnect (connection) { const peerId = connection.remotePeer const peerIdStr = peerId.toB58String() const storedConn = this.connections.get(peerIdStr) @@ -222,13 +232,13 @@ class ConnectionManager extends EventEmitter { this.connections.set(peerIdStr, [connection]) } - this._libp2p.peerStore.keyBook.set(peerId, peerId.pubKey) + await this._libp2p.peerStore.keyBook.set(peerId, peerId.pubKey) if (!this._peerValues.has(peerIdStr)) { this._peerValues.set(peerIdStr, this._options.defaultPeerValue) } - this._checkMaxLimit('maxConnections', this.size) + await this._checkMaxLimit('maxConnections', this.size) } /** @@ -296,6 +306,9 @@ class ConnectionManager extends EventEmitter { */ _onLatencyMeasure (summary) { this._checkMaxLimit('maxEventLoopDelay', summary.avgMs) + .catch(err => { + log.error(err) + }) } /** @@ -305,12 +318,12 @@ 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 */ - _checkMaxLimit (name, value) { + async _checkMaxLimit (name, value) { const limit = this._options[name] log('checking limit of %s. current value: %d of %d', name, value, limit) if (value > limit) { log('%s: limit exceeded: %s, %d', this._peerId, name, value) - this._maybeDisconnectOne() + await this._maybeDisconnectOne() } } @@ -320,7 +333,7 @@ class ConnectionManager extends EventEmitter { * * @private */ - _maybeDisconnectOne () { + async _maybeDisconnectOne () { if (this._options.minConnections < this.connections.size) { const peerValues = Array.from(new Map([...this._peerValues.entries()].sort((a, b) => a[1] - b[1]))) log('%s: sorted peer values: %j', this._peerId, peerValues) @@ -331,7 +344,11 @@ class ConnectionManager extends EventEmitter { 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() + connections[0].close().catch(err => { + log.error(err) + }) + // TODO: should not need to invoke this manually + this.onDisconnect(connections[0]) break } } diff --git a/src/content-routing/utils.js b/src/content-routing/utils.js index c43ec9a98e..adcf8f2c07 100644 --- a/src/content-routing/utils.js +++ b/src/content-routing/utils.js @@ -14,12 +14,12 @@ const take = require('it-take') * Store the multiaddrs from every peer in the passed peer store * * @param {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} source - * @param {import('../peer-store')} peerStore + * @param {import('../peer-store/types').PeerStore} peerStore */ -function storeAddresses (source, peerStore) { - return map(source, (peer) => { +async function * storeAddresses (source, peerStore) { + yield * map(source, async (peer) => { // ensure we have the addresses for a given peer - peerStore.addressBook.add(peer.id, peer.multiaddrs) + await peerStore.addressBook.add(peer.id, peer.multiaddrs) return peer }) diff --git a/src/dialer/dial-request.js b/src/dialer/dial-request.js index 397fc784c4..810cdfee69 100644 --- a/src/dialer/dial-request.js +++ b/src/dialer/dial-request.js @@ -63,7 +63,10 @@ class DialRequest { tokens.forEach(token => tokenHolder.push(token)) const dialAbortControllers = this.addrs.map(() => { const controller = new AbortController() - setMaxListeners && setMaxListeners(Infinity, controller.signal) + try { + // fails on node < 15.4 + setMaxListeners && setMaxListeners(Infinity, controller.signal) + } catch {} return controller }) diff --git a/src/dialer/index.js b/src/dialer/index.js index e6f3a0d020..06fd4d0c65 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -30,8 +30,8 @@ const METRICS_PENDING_DIAL_TARGETS = 'pending-dial-targets' /** * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection * @typedef {import('peer-id')} PeerId - * @typedef {import('../peer-store')} PeerStore - * @typedef {import('../peer-store/address-book').Address} Address + * @typedef {import('../peer-store/types').PeerStore} PeerStore + * @typedef {import('../peer-store/types').Address} Address * @typedef {import('../transport-manager')} TransportManager */ @@ -88,10 +88,18 @@ class Dialer { this.tokens = [...new Array(maxParallelDials)].map((_, index) => index) /** @type {Map} */ - this._pendingDials = trackedMap(METRICS_COMPONENT, METRICS_PENDING_DIALS, metrics) + this._pendingDials = trackedMap({ + component: METRICS_COMPONENT, + metric: METRICS_PENDING_DIALS, + metrics + }) /** @type {Map void, reject: (err: Error) => void}>} */ - this._pendingDialTargets = trackedMap(METRICS_COMPONENT, METRICS_PENDING_DIAL_TARGETS, metrics) + this._pendingDialTargets = trackedMap({ + component: METRICS_COMPONENT, + metric: METRICS_PENDING_DIAL_TARGETS, + metrics + }) for (const [key, value] of Object.entries(resolvers)) { Multiaddr.resolvers.set(key, value) @@ -192,10 +200,10 @@ class Dialer { const { id, multiaddrs } = getPeer(peer) if (multiaddrs) { - this.peerStore.addressBook.add(id, multiaddrs) + await this.peerStore.addressBook.add(id, multiaddrs) } - let knownAddrs = this.peerStore.addressBook.getMultiaddrsForPeer(id, this.addressSorter) || [] + let knownAddrs = await this.peerStore.addressBook.getMultiaddrsForPeer(id, this.addressSorter) || [] // 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. @@ -215,7 +223,7 @@ class Dialer { const supportedAddrs = addrs.filter(a => this.transportManager.transportForMultiaddr(a)) if (supportedAddrs.length > this.maxAddrsToDial) { - this.peerStore.delete(id) + await this.peerStore.delete(id) throw errCode(new Error('dial with more addresses than allowed'), codes.ERR_TOO_MANY_ADDRESSES) } @@ -259,7 +267,10 @@ class Dialer { // this signal will potentially be used while dialing lots of // peers so prevent MaxListenersExceededWarning appearing in the console - setMaxListeners && setMaxListeners(Infinity, signal) + try { + // fails on node < 15.4 + setMaxListeners && setMaxListeners(Infinity, signal) + } catch {} const pendingDial = { dialRequest, diff --git a/src/identify/index.js b/src/identify/index.js index d08d11963b..0836551652 100644 --- a/src/identify/index.js +++ b/src/identify/index.js @@ -77,8 +77,6 @@ class IdentifyService { ...libp2p._options.host } - this.peerStore.metadataBook.set(this.peerId, 'AgentVersion', uint8ArrayFromString(this._host.agentVersion)) - this.peerStore.metadataBook.set(this.peerId, 'ProtocolVersion', uint8ArrayFromString(this._host.protocolVersion)) // When a new connection happens, trigger identify this.connectionManager.on('peer:connect', (connection) => { this.identify(connection).catch(log.error) @@ -87,18 +85,27 @@ class IdentifyService { // When self multiaddrs change, trigger identify-push this.peerStore.on('change:multiaddrs', ({ peerId }) => { if (peerId.toString() === this.peerId.toString()) { - this.pushToPeerStore() + this.pushToPeerStore().catch(err => log.error(err)) } }) // When self protocols change, trigger identify-push this.peerStore.on('change:protocols', ({ peerId }) => { if (peerId.toString() === this.peerId.toString()) { - this.pushToPeerStore() + this.pushToPeerStore().catch(err => log.error(err)) } }) } + async start () { + await this.peerStore.metadataBook.setValue(this.peerId, 'AgentVersion', uint8ArrayFromString(this._host.agentVersion)) + await this.peerStore.metadataBook.setValue(this.peerId, 'ProtocolVersion', uint8ArrayFromString(this._host.protocolVersion)) + } + + async stop () { + + } + /** * Send an Identify Push update to the list of connections * @@ -108,7 +115,7 @@ class IdentifyService { async push (connections) { const signedPeerRecord = await this.peerStore.addressBook.getRawEnvelope(this.peerId) const listenAddrs = this._libp2p.multiaddrs.map((ma) => ma.bytes) - const protocols = this.peerStore.protoBook.get(this.peerId) || [] + const protocols = await this.peerStore.protoBook.get(this.peerId) const pushes = connections.map(async connection => { try { @@ -135,10 +142,8 @@ class IdentifyService { /** * Calls `push` for all peers in the `peerStore` that are connected - * - * @returns {void} */ - pushToPeerStore () { + async pushToPeerStore () { // Do not try to push if libp2p node is not running if (!this._libp2p.isStarted()) { return @@ -146,13 +151,13 @@ class IdentifyService { const connections = [] let connection - for (const peer of this.peerStore.peers.values()) { + for await (const peer of this.peerStore.getPeers()) { if (peer.protocols.includes(this.identifyPushProtocolStr) && (connection = this.connectionManager.get(peer.id))) { connections.push(connection) } } - this.push(connections) + await this.push(connections) } /** @@ -205,10 +210,10 @@ class IdentifyService { try { const envelope = await Envelope.openAndCertify(signedPeerRecord, PeerRecord.DOMAIN) - if (this.peerStore.addressBook.consumePeerRecord(envelope)) { - this.peerStore.protoBook.set(id, protocols) - this.peerStore.metadataBook.set(id, 'AgentVersion', uint8ArrayFromString(message.agentVersion)) - this.peerStore.metadataBook.set(id, 'ProtocolVersion', uint8ArrayFromString(message.protocolVersion)) + if (await this.peerStore.addressBook.consumePeerRecord(envelope)) { + await this.peerStore.protoBook.set(id, protocols) + await this.peerStore.metadataBook.setValue(id, 'AgentVersion', uint8ArrayFromString(message.agentVersion)) + await this.peerStore.metadataBook.setValue(id, 'ProtocolVersion', uint8ArrayFromString(message.protocolVersion)) return } } catch (/** @type {any} */ err) { @@ -217,14 +222,14 @@ class IdentifyService { // LEGACY: Update peers data in PeerStore try { - this.peerStore.addressBook.set(id, listenAddrs.map((addr) => new Multiaddr(addr))) + await this.peerStore.addressBook.set(id, listenAddrs.map((addr) => new Multiaddr(addr))) } catch (/** @type {any} */ err) { log.error('received invalid addrs', err) } - this.peerStore.protoBook.set(id, protocols) - this.peerStore.metadataBook.set(id, 'AgentVersion', uint8ArrayFromString(message.agentVersion)) - this.peerStore.metadataBook.set(id, 'ProtocolVersion', uint8ArrayFromString(message.protocolVersion)) + await this.peerStore.protoBook.set(id, protocols) + await this.peerStore.metadataBook.setValue(id, 'AgentVersion', uint8ArrayFromString(message.agentVersion)) + await this.peerStore.metadataBook.setValue(id, 'ProtocolVersion', uint8ArrayFromString(message.protocolVersion)) // TODO: Add and score our observed addr log('received observed address of %s', cleanObservedAddr) @@ -268,7 +273,7 @@ class IdentifyService { } const signedPeerRecord = await this.peerStore.addressBook.getRawEnvelope(this.peerId) - const protocols = this.peerStore.protoBook.get(this.peerId) || [] + const protocols = await this.peerStore.protoBook.get(this.peerId) const message = Message.Identify.encode({ protocolVersion: this._host.protocolVersion, @@ -321,8 +326,8 @@ class IdentifyService { try { const envelope = await Envelope.openAndCertify(message.signedPeerRecord, PeerRecord.DOMAIN) - if (this.peerStore.addressBook.consumePeerRecord(envelope)) { - this.peerStore.protoBook.set(id, message.protocols) + if (await this.peerStore.addressBook.consumePeerRecord(envelope)) { + await this.peerStore.protoBook.set(id, message.protocols) return } } catch (/** @type {any} */ err) { @@ -331,14 +336,14 @@ class IdentifyService { // LEGACY: Update peers data in PeerStore try { - this.peerStore.addressBook.set(id, + await this.peerStore.addressBook.set(id, message.listenAddrs.map((addr) => new Multiaddr(addr))) } catch (/** @type {any} */ err) { log.error('received invalid addrs', err) } // Update the protocols - this.peerStore.protoBook.set(id, message.protocols) + await this.peerStore.protoBook.set(id, message.protocols) } /** diff --git a/src/index.js b/src/index.js index f9f2b6c37f..edb715442b 100644 --- a/src/index.js +++ b/src/index.js @@ -9,7 +9,7 @@ const { EventEmitter } = require('events') const errCode = require('err-code') const PeerId = require('peer-id') const { Multiaddr } = require('multiaddr') - +const { MemoryDatastore } = require('datastore-core/memory') const PeerRouting = require('./peer-routing') const ContentRouting = require('./content-routing') const getPeer = require('./get-peer') @@ -28,7 +28,6 @@ const TransportManager = require('./transport-manager') const Upgrader = require('./upgrader') const PeerStore = require('./peer-store') const PubsubAdapter = require('./pubsub-adapter') -const PersistentPeerStore = require('./peer-store/persistent') const Registrar = require('./registrar') const ping = require('./ping') const IdentifyService = require('./identify') @@ -112,7 +111,7 @@ const { updateSelfPeerRecord } = require('./record/utils') * @property {KeychainOptions & import('./keychain/index').KeychainOptions} [keychain] * @property {MetricsOptions & import('./metrics').MetricsOptions} [metrics] * @property {import('./peer-routing').PeerRoutingOptions} [peerRouting] - * @property {PeerStoreOptions & PersistentPeerStoreOptions} [peerStore] + * @property {PeerStoreOptions} [peerStore] * @property {import('./transport-manager').TransportManagerOptions} [transportManager] * @property {Libp2pConfig} [config] * @@ -172,13 +171,11 @@ class Libp2p extends EventEmitter { this.metrics = metrics } - this.peerStore = (this.datastore && this._options.peerStore.persistence) - ? new PersistentPeerStore({ - peerId: this.peerId, - datastore: this.datastore, - ...this._options.peerStore - }) - : new PeerStore({ peerId: this.peerId }) + /** @type {import('./peer-store/types').PeerStore} */ + this.peerStore = new PeerStore({ + peerId: this.peerId, + datastore: (this.datastore && this._options.peerStore.persistence) ? this.datastore : new MemoryDatastore() + }) // Addresses {listen, announce, noAnnounce} this.addresses = this._options.addresses @@ -290,7 +287,6 @@ class Libp2p extends EventEmitter { // Add the identify service since we can multiplex this.identifyService = new IdentifyService({ libp2p: this }) - this.handle(Object.values(IdentifyService.getProtocolStr(this)), this.identifyService.handleMessage) } // Attach private network protector @@ -356,6 +352,10 @@ class Libp2p extends EventEmitter { async start () { log('libp2p is starting') + if (this.identifyService) { + await this.handle(Object.values(IdentifyService.getProtocolStr(this)), this.identifyService.handleMessage) + } + try { await this._onStarting() await this._onDidStart() @@ -380,9 +380,13 @@ class Libp2p extends EventEmitter { try { this._isStarted = false + if (this.identifyService) { + await this.identifyService.stop() + } + this.relay && this.relay.stop() this.peerRouting.stop() - this._autodialler.stop() + await this._autodialler.stop() await (this._dht && this._dht.stop()) for (const service of this._discovery.values()) { @@ -393,7 +397,6 @@ class Libp2p extends EventEmitter { this._discovery = new Map() - await this.peerStore.stop() await this.connectionManager.stop() await Promise.all([ @@ -499,7 +502,7 @@ class Libp2p extends EventEmitter { if (!connection) { connection = await this.dialer.connectToPeer(peer, options) } else if (multiaddrs) { - this.peerStore.addressBook.add(id, multiaddrs) + await this.peerStore.addressBook.add(id, multiaddrs) } return connection @@ -579,14 +582,14 @@ class Libp2p extends EventEmitter { * @param {string[]|string} protocols * @param {(props: HandlerProps) => void} handler */ - handle (protocols, handler) { + async handle (protocols, handler) { protocols = Array.isArray(protocols) ? protocols : [protocols] protocols.forEach(protocol => { this.upgrader.protocols.set(protocol, handler) }) // Add new protocols to self protocols in the Protobook - this.peerStore.protoBook.add(this.peerId, protocols) + await this.peerStore.protoBook.add(this.peerId, protocols) } /** @@ -595,14 +598,14 @@ class Libp2p extends EventEmitter { * * @param {string[]|string} protocols */ - unhandle (protocols) { + async unhandle (protocols) { protocols = Array.isArray(protocols) ? protocols : [protocols] protocols.forEach(protocol => { this.upgrader.protocols.delete(protocol) }) // Remove protocols from self protocols in the Protobook - this.peerStore.protoBook.remove(this.peerId, protocols) + await this.peerStore.protoBook.remove(this.peerId, protocols) } async _onStarting () { @@ -613,11 +616,8 @@ class Libp2p extends EventEmitter { // Manage your NATs this.natManager.start() - // Start PeerStore - await this.peerStore.start() - if (this._config.pubsub.enabled) { - this.pubsub && this.pubsub.start() + this.pubsub && await this.pubsub.start() } // DHT subsystem @@ -631,6 +631,10 @@ class Libp2p extends EventEmitter { // Start metrics if present this.metrics && this.metrics.start() + + if (this.identifyService) { + await this.identifyService.start() + } } /** @@ -643,17 +647,19 @@ class Libp2p extends EventEmitter { this.peerStore.on('peer', peerId => { this.emit('peer:discovery', peerId) - this._maybeConnect(peerId) + this._maybeConnect(peerId).catch(err => { + log.error(err) + }) }) // 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()) { + for await (const peer of this.peerStore.getPeers()) { this.emit('peer:discovery', peer.id) } this.connectionManager.start() - this._autodialler.start() + await this._autodialler.start() // Peer discovery await this._setupPeerDiscovery() @@ -677,8 +683,8 @@ class Libp2p extends EventEmitter { return } - peer.multiaddrs && this.peerStore.addressBook.add(peer.id, peer.multiaddrs) - peer.protocols && this.peerStore.protoBook.set(peer.id, peer.protocols) + peer.multiaddrs && this.peerStore.addressBook.add(peer.id, peer.multiaddrs).catch(err => log.error(err)) + peer.protocols && this.peerStore.protoBook.set(peer.id, peer.protocols).catch(err => log.error(err)) } /** diff --git a/src/metrics/index.js b/src/metrics/index.js index 5ea3f96af8..2b65c28974 100644 --- a/src/metrics/index.js +++ b/src/metrics/index.js @@ -44,7 +44,7 @@ class Metrics { this._oldPeers = oldPeerLRU(this._options.maxOldPeersRetention) this._running = false this._onMessage = this._onMessage.bind(this) - this._componentMetrics = new Map() + this._systems = new Map() } /** @@ -89,19 +89,26 @@ class Metrics { } /** - * @returns {Map} + * @returns {Map>>} */ getComponentMetrics () { - return this._componentMetrics + return this._systems } - updateComponentMetric (component, metric, value) { - if (!this._componentMetrics.has(component)) { - this._componentMetrics.set(component, new Map()) + updateComponentMetric ({ system = 'libp2p', component, metric, value }) { + if (!this._systems.has(system)) { + this._systems.set(system, new Map()) } - const map = this._componentMetrics.get(component) - map.set(metric, value) + const systemMetrics = this._systems.get(system) + + if (!systemMetrics.has(component)) { + systemMetrics.set(component, new Map()) + } + + const componentMetrics = systemMetrics.get(component) + + componentMetrics.set(metric, value) } /** diff --git a/src/metrics/tracked-map.js b/src/metrics/tracked-map.js index d37587a663..beb0fa7280 100644 --- a/src/metrics/tracked-map.js +++ b/src/metrics/tracked-map.js @@ -6,18 +6,27 @@ */ class TrackedMap extends Map { /** - * @param {string} component - * @param {string} name - * @param {import('.')} metrics + * @param {object} options + * @param {string} options.system + * @param {string} options.component + * @param {string} options.metric + * @param {import('.')} options.metrics */ - constructor (component, name, metrics) { + constructor (options) { super() + const { system, component, metric, metrics } = options + this._system = system this._component = component - this._name = name + this._metric = metric this._metrics = metrics - this._metrics.updateComponentMetric(this._component, this._name, this.size) + this._metrics.updateComponentMetric({ + system: this._system, + component: this._component, + metric: this._metric, + value: this.size + }) } /** @@ -26,7 +35,12 @@ class TrackedMap extends Map { */ set (key, value) { super.set(key, value) - this._metrics.updateComponentMetric(this._component, this._name, this.size) + this._metrics.updateComponentMetric({ + system: this._system, + component: this._component, + metric: this._metric, + value: this.size + }) return this } @@ -35,31 +49,43 @@ class TrackedMap extends Map { */ delete (key) { const deleted = super.delete(key) - this._metrics.updateComponentMetric(this._component, this._name, this.size) + this._metrics.updateComponentMetric({ + system: this._system, + component: this._component, + metric: this._metric, + value: this.size + }) return deleted } clear () { super.clear() - this._metrics.updateComponentMetric(this._component, this._name, this.size) + this._metrics.updateComponentMetric({ + system: this._system, + component: this._component, + metric: this._metric, + value: this.size + }) } } /** * @template K * @template V - * @param {string} component - * @param {string} name - * @param {import('.')} [metrics] + * @param {object} options + * @param {string} [options.system] + * @param {string} options.component + * @param {string} options.metric + * @param {import('.')} [options.metrics] * @returns {Map} */ -module.exports = (component, name, metrics) => { +module.exports = ({ system = 'libp2p', component, metric, metrics }) => { /** @type {Map} */ let map if (metrics) { - map = new TrackedMap(component, name, metrics) + map = new TrackedMap({ system, component, metric, metrics }) } else { map = new Map() } diff --git a/src/peer-routing.js b/src/peer-routing.js index fd6f99205b..b525920f9a 100644 --- a/src/peer-routing.js +++ b/src/peer-routing.js @@ -154,7 +154,10 @@ class PeerRouting { const controller = new TimeoutController(options.timeout) // this controller will potentially be used while dialing lots of // peers so prevent MaxListenersExceededWarning appearing in the console - setMaxListeners && setMaxListeners(Infinity, controller.signal) + try { + // fails on node < 15.4 + setMaxListeners && setMaxListeners(Infinity, controller.signal) + } catch {} options.signal = controller.signal } diff --git a/src/peer-store/address-book.js b/src/peer-store/address-book.js index 4e28eca923..05581a2eb0 100644 --- a/src/peer-store/address-book.js +++ b/src/peer-store/address-book.js @@ -1,74 +1,36 @@ 'use strict' const debug = require('debug') -const log = Object.assign(debug('libp2p:peer-store:address-book'), { - error: debug('libp2p:peer-store:address-book:err') -}) const errcode = require('err-code') - const { Multiaddr } = require('multiaddr') const PeerId = require('peer-id') - -const Book = require('./book') +const { codes } = require('../errors') const PeerRecord = require('../record/peer-record') - -const { - codes: { ERR_INVALID_PARAMETERS } -} = require('../errors') const Envelope = require('../record/envelope') /** - * @typedef {import('./')} PeerStore + * @typedef {import('./types').PeerStore} PeerStore + * @typedef {import('./types').Address} Address + * @typedef {import('./types').AddressBook} AddressBook */ -/** - * @typedef {Object} Address - * @property {Multiaddr} multiaddr peer multiaddr. - * @property {boolean} isCertified obtained from a signed peer record. - * - * @typedef {Object} CertifiedRecord - * @property {Uint8Array} raw raw envelope. - * @property {number} seqNumber seq counter. - * - * @typedef {Object} Entry - * @property {Address[]} addresses peer Addresses. - * @property {CertifiedRecord} record certified peer record. - */ +const log = Object.assign(debug('libp2p:peer-store:address-book'), { + error: debug('libp2p:peer-store:address-book:err') +}) + +const EVENT_NAME = 'change:multiaddrs' /** - * @extends {Book} + * @implements {AddressBook} */ -class AddressBook extends Book { +class PeerStoreAddressBook { /** - * The AddressBook is responsible for keeping the known multiaddrs of a peer. - * - * @class - * @param {PeerStore} peerStore + * @param {PeerStore["emit"]} emit + * @param {import('./types').Store} store */ - constructor (peerStore) { - /** - * PeerStore Event emitter, used by the AddressBook to emit: - * "peer" - emitted when a peer is discovered by the node. - * "change:multiaddrs" - emitted when the known multiaddrs of a peer change. - */ - super({ - peerStore, - eventName: 'change:multiaddrs', - eventProperty: 'multiaddrs', - eventTransformer: (data) => { - if (!data.addresses) { - return [] - } - return data.addresses.map((/** @type {Address} */ address) => address.multiaddr) - } - }) - - /** - * Map known peers to their known Address Entries. - * - * @type {Map} - */ - this.data = new Map() + constructor (emit, store) { + this._emit = emit + this._store = store } /** @@ -77,69 +39,90 @@ class AddressBook extends Book { * into the AddressBook. * * @param {Envelope} envelope - * @returns {boolean} */ - consumePeerRecord (envelope) { - let peerRecord + async consumePeerRecord (envelope) { + log('consumePeerRecord await write lock') + const release = await this._store.lock.writeLock() + log('consumePeerRecord got write lock') + + let peerId + let updatedPeer + try { - peerRecord = PeerRecord.createFromProtobuf(envelope.payload) - } catch (/** @type {any} */ err) { - log.error('invalid peer record received') - return false - } + let peerRecord + try { + peerRecord = PeerRecord.createFromProtobuf(envelope.payload) + } catch (/** @type {any} */ err) { + log.error('invalid peer record received') + return false + } - // Verify peerId - if (!peerRecord.peerId.equals(envelope.peerId)) { - log('signing key does not match PeerId in the PeerRecord') - return false - } + peerId = peerRecord.peerId + const multiaddrs = peerRecord.multiaddrs - // ensure the record has multiaddrs - if (!peerRecord.multiaddrs || !peerRecord.multiaddrs.length) { - return false - } + // Verify peerId + if (!peerId.equals(envelope.peerId)) { + log('signing key does not match PeerId in the PeerRecord') + return false + } - const peerId = peerRecord.peerId - const id = peerId.toB58String() - const entry = this.data.get(id) || { record: undefined } - const storedRecord = entry.record + // ensure the record has multiaddrs + if (!multiaddrs || !multiaddrs.length) { + return false + } - // ensure seq is greater than, or equal to, the last received - if (storedRecord && storedRecord.seqNumber >= peerRecord.seqNumber) { - return false - } + if (await this._store.has(peerId)) { + const peer = await this._store.load(peerId) - const addresses = this._toAddresses(peerRecord.multiaddrs, true) + if (peer.peerRecordEnvelope) { + const storedEnvelope = await Envelope.createFromProtobuf(peer.peerRecordEnvelope) + const storedRecord = PeerRecord.createFromProtobuf(storedEnvelope.payload) - // Replace unsigned addresses by the new ones from the record - // TODO: Once we have ttls for the addresses, we should merge these in. - this._setData(peerId, { - addresses, - record: { - raw: envelope.marshal(), - seqNumber: peerRecord.seqNumber + // ensure seq is greater than, or equal to, the last received + if (storedRecord.seqNumber >= peerRecord.seqNumber) { + return false + } + } } - }) - log(`stored provided peer record for ${id}`) + + // Replace unsigned addresses by the new ones from the record + // TODO: Once we have ttls for the addresses, we should merge these in + updatedPeer = await this._store.patchOrCreate(peerId, { + addresses: convertMultiaddrsToAddresses(multiaddrs, true), + peerRecordEnvelope: envelope.marshal() + }) + + log(`stored provided peer record for ${peerRecord.peerId.toB58String()}`) + } finally { + log('consumePeerRecord release write lock') + release() + } + + this._emit(EVENT_NAME, { peerId, multiaddrs: updatedPeer.addresses.map(({ multiaddr }) => multiaddr) }) return true } /** - * Get the raw Envelope for a peer. Returns - * undefined if no Envelope is found. - * * @param {PeerId} peerId - * @returns {Uint8Array|undefined} */ - getRawEnvelope (peerId) { - const entry = this.data.get(peerId.toB58String()) + async getRawEnvelope (peerId) { + log('getRawEnvelope await read lock') + const release = await this._store.lock.readLock() + log('getRawEnvelope got read lock') - if (!entry || !entry.record || !entry.record.raw) { - return undefined - } + try { + const peer = await this._store.load(peerId) - return entry.record.raw + return peer.peerRecordEnvelope + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } finally { + log('getRawEnvelope release read lock') + release() + } } /** @@ -147,10 +130,9 @@ class AddressBook extends Book { * Returns undefined if no record exists. * * @param {PeerId} peerId - * @returns {Promise|undefined} */ - getPeerRecord (peerId) { - const raw = this.getRawEnvelope(peerId) + async getPeerRecord (peerId) { + const raw = await this.getRawEnvelope(peerId) if (!raw) { return undefined @@ -160,186 +142,189 @@ class AddressBook extends Book { } /** - * Set known multiaddrs of a provided peer. - * 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 {Multiaddr[]} multiaddrs - * @returns {AddressBook} */ - set (peerId, multiaddrs) { + async get (peerId) { if (!PeerId.isPeerId(peerId)) { log.error('peerId must be an instance of peer-id to store data') - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) - } - - const addresses = this._toAddresses(multiaddrs) - - // Not replace multiaddrs - if (!addresses.length) { - return this + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } - const id = peerId.toB58String() - const entry = this.data.get(id) + log('get wait for read lock') + const release = await this._store.lock.readLock() + log('get got read lock') - // Already knows the peer - if (entry && entry.addresses && entry.addresses.length === addresses.length) { - const intersection = entry.addresses.filter((addr) => addresses.some((newAddr) => addr.multiaddr.equals(newAddr.multiaddr))) + try { + const peer = await this._store.load(peerId) - // Are new addresses equal to the old ones? - // If yes, no changes needed! - if (intersection.length === entry.addresses.length) { - log(`the addresses provided to store are equal to the already stored for ${id}`) - return this + return peer.addresses + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err } + } finally { + log('get release read lock') + release() } - this._setData(peerId, { - addresses, - record: entry && entry.record - }) - log(`stored provided multiaddrs for ${id}`) - - // Notify the existance of a new peer - if (!entry) { - this._ps.emit('peer', peerId) - } - - return this + return [] } /** - * Add known addresses of a provided peer. - * If the peer is not known, it is set with the given addresses. - * * @param {PeerId} peerId * @param {Multiaddr[]} multiaddrs - * @returns {AddressBook} */ - add (peerId, multiaddrs) { + async set (peerId, multiaddrs) { if (!PeerId.isPeerId(peerId)) { log.error('peerId must be an instance of peer-id to store data') - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } - const addresses = this._toAddresses(multiaddrs) - const id = peerId.toB58String() + log('set await write lock') + const release = await this._store.lock.writeLock() + log('set got write lock') - // No addresses to be added - if (!addresses.length) { - return this - } - - const entry = this.data.get(id) + let hasPeer = false + let updatedPeer - if (entry && entry.addresses) { - // Add recorded uniquely to the new array (Union) - entry.addresses.forEach((addr) => { - if (!addresses.find(r => r.multiaddr.equals(addr.multiaddr))) { - addresses.push(addr) - } - }) + try { + const addresses = convertMultiaddrsToAddresses(multiaddrs) - // If the recorded length is equal to the new after the unique union - // The content is the same, no need to update. - if (entry.addresses.length === addresses.length) { - log(`the addresses provided to store are already stored for ${id}`) - return this + // No valid addresses found + if (!addresses.length) { + return } - } - this._setData(peerId, { - addresses, - record: entry && entry.record - }) + try { + const peer = await this._store.load(peerId) + hasPeer = true + + if (new Set([ + ...addresses.map(({ multiaddr }) => multiaddr.toString()), + ...peer.addresses.map(({ multiaddr }) => multiaddr.toString()) + ]).size === peer.addresses.length && addresses.length === peer.addresses.length) { + // not changing anything, no need to update + return + } + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } - log(`added provided multiaddrs for ${id}`) + updatedPeer = await this._store.patchOrCreate(peerId, { addresses }) - // Notify the existance of a new peer - if (!(entry && entry.addresses)) { - this._ps.emit('peer', peerId) + log(`set multiaddrs for ${peerId.toB58String()}`) + } finally { + log('set release write lock') + release() } - return this + this._emit(EVENT_NAME, { peerId, multiaddrs: updatedPeer.addresses.map(addr => addr.multiaddr) }) + + // Notify the existence of a new peer + if (!hasPeer) { + this._emit('peer', peerId) + } } /** - * Get the known data of a provided peer. - * - * @override * @param {PeerId} peerId - * @returns {Address[]|undefined} + * @param {Multiaddr[]} multiaddrs */ - get (peerId) { + async add (peerId, multiaddrs) { if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + 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'), codes.ERR_INVALID_PARAMETERS) } - const entry = this.data.get(peerId.toB58String()) + log('add await write lock') + const release = await this._store.lock.writeLock() + log('add got write lock') - return entry && entry.addresses ? [...entry.addresses] : undefined - } + let hasPeer + let updatedPeer - /** - * Transforms received multiaddrs into Address. - * - * @private - * @param {Multiaddr[]} multiaddrs - * @param {boolean} [isCertified] - * @returns {Address[]} - */ - _toAddresses (multiaddrs, isCertified = false) { - if (!multiaddrs) { - log.error('multiaddrs must be provided to store data') - throw errcode(new Error('multiaddrs must be provided'), ERR_INVALID_PARAMETERS) - } + try { + const addresses = convertMultiaddrsToAddresses(multiaddrs) - // create Address for each address - /** @type {Address[]} */ - const addresses = [] - multiaddrs.forEach((addr) => { - if (!Multiaddr.isMultiaddr(addr)) { - log.error(`multiaddr ${addr} must be an instance of multiaddr`) - throw errcode(new Error(`multiaddr ${addr} must be an instance of multiaddr`), ERR_INVALID_PARAMETERS) + // No valid addresses found + if (!addresses.length) { + return } - // Guarantee no replicates - if (!addresses.find((a) => a.multiaddr.equals(addr))) { - addresses.push({ - multiaddr: addr, - isCertified - }) + try { + const peer = await this._store.load(peerId) + hasPeer = true + + if (new Set([ + ...addresses.map(({ multiaddr }) => multiaddr.toString()), + ...peer.addresses.map(({ multiaddr }) => multiaddr.toString()) + ]).size === peer.addresses.length) { + return + } + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } } - }) - return addresses + updatedPeer = await this._store.mergeOrCreate(peerId, { addresses }) + + log(`added multiaddrs for ${peerId}`) + } finally { + log('set release write lock') + release() + } + + this._emit(EVENT_NAME, { peerId, multiaddrs: updatedPeer.addresses.map(addr => addr.multiaddr) }) + + // Notify the existence of a new peer + if (!hasPeer) { + this._emit('peer', peerId) + } } /** - * 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 - * @param {(addresses: Address[]) => Address[]} [addressSorter] - * @returns {Multiaddr[]|undefined} */ - getMultiaddrsForPeer (peerId, addressSorter = (ms) => ms) { + async delete (peerId) { if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + 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'), codes.ERR_INVALID_PARAMETERS) } - const entry = this.data.get(peerId.toB58String()) - if (!entry || !entry.addresses) { - return undefined + log('delete await write lock') + const release = await this._store.lock.writeLock() + log('delete got write lock') + + let has + + try { + has = await this._store.has(peerId) + + await this._store.patchOrCreate(peerId, { + addresses: [] + }) + } finally { + log('delete release write lock') + release() } + if (has) { + this._emit(EVENT_NAME, { peerId, multiaddrs: [] }) + } + } + + /** + * @param {PeerId} peerId + * @param {(addresses: Address[]) => Address[]} [addressSorter] + */ + async getMultiaddrsForPeer (peerId, addressSorter = (ms) => ms) { + const addresses = await this.get(peerId) + return addressSorter( - entry.addresses || [] + addresses ).map((address) => { const multiaddr = address.multiaddr @@ -351,4 +336,34 @@ class AddressBook extends Book { } } -module.exports = AddressBook +/** + * Transforms received multiaddrs into Address. + * + * @private + * @param {Multiaddr[]} multiaddrs + * @param {boolean} [isCertified] + * @returns {Address[]} + */ +function convertMultiaddrsToAddresses (multiaddrs, isCertified = false) { + if (!multiaddrs) { + log.error('multiaddrs must be provided to store data') + throw errcode(new Error('multiaddrs must be provided'), codes.ERR_INVALID_PARAMETERS) + } + + // create Address for each address with no duplicates + return Array.from( + new Set(multiaddrs.map(ma => ma.toString())) + ) + .map(addr => { + try { + return { + multiaddr: new Multiaddr(addr), + isCertified + } + } catch (err) { + throw errcode(err, codes.ERR_INVALID_PARAMETERS) + } + }) +} + +module.exports = PeerStoreAddressBook diff --git a/src/peer-store/book.js b/src/peer-store/book.js deleted file mode 100644 index 3de0459486..0000000000 --- a/src/peer-store/book.js +++ /dev/null @@ -1,124 +0,0 @@ -'use strict' - -const errcode = require('err-code') -const PeerId = require('peer-id') -const { codes } = require('../errors') - -/** - * @param {any} data - */ -const passthrough = data => data - -/** - * @typedef {import('./')} PeerStore - */ - -class Book { - /** - * The Book is the skeleton for the PeerStore books. - * - * @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 {(data: any) => any[]} [properties.eventTransformer] - Transformer function of the provided data for being emitted. - */ - constructor ({ peerStore, eventName, eventProperty, eventTransformer = passthrough }) { - this._ps = peerStore - this.eventName = eventName - this.eventProperty = eventProperty - this.eventTransformer = eventTransformer - - /** - * Map known peers to their data. - * - * @type {Map} - */ - this.data = new Map() - } - - /** - * Set known data of a provided peer. - * - * @param {PeerId} peerId - * @param {any[]|any} data - */ - set (peerId, data) { - throw errcode(new Error('set must be implemented by the subclass'), codes.ERR_NOT_IMPLEMENTED) - } - - /** - * Set data into the datastructure, persistence and emit it using the provided transformers. - * - * @protected - * @param {PeerId} peerId - peerId of the data to store - * @param {any} 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() - - // Store data in memory - this.data.set(b58key, data) - - // Emit event - emit && this._emit(peerId, data) - } - - /** - * Emit data. - * - * @protected - * @param {PeerId} peerId - * @param {any} [data] - */ - _emit (peerId, data) { - this._ps.emit(this.eventName, { - peerId, - [this.eventProperty]: this.eventTransformer(data) - }) - } - - /** - * Get the known data of a provided peer. - * Returns `undefined` if there is no available data for the given peer. - * - * @param {PeerId} peerId - * @returns {any[]|any|undefined} - */ - get (peerId) { - if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) - } - - const rec = this.data.get(peerId.toB58String()) - - // @ts-ignore - return rec ? [...rec] : undefined - } - - /** - * Deletes the provided peer from the book. - * - * @param {PeerId} peerId - * @returns {boolean} - */ - delete (peerId) { - if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) - } - - if (!this.data.delete(peerId.toB58String())) { - return false - } - - this._emit(peerId, []) - - return true - } -} - -module.exports = Book diff --git a/src/peer-store/index.js b/src/peer-store/index.js index 72fd9043aa..d17a9f2dc2 100644 --- a/src/peer-store/index.js +++ b/src/peer-store/index.js @@ -1,152 +1,119 @@ 'use strict' -const errcode = require('err-code') - +const debug = require('debug') const { EventEmitter } = require('events') -const PeerId = require('peer-id') - const AddressBook = require('./address-book') const KeyBook = require('./key-book') const MetadataBook = require('./metadata-book') const ProtoBook = require('./proto-book') - -const { - codes: { ERR_INVALID_PARAMETERS } -} = require('../errors') +const Store = require('./store') /** - * @typedef {import('./address-book').Address} Address + * @typedef {import('./types').PeerStore} PeerStore + * @typedef {import('./types').Peer} Peer + * @typedef {import('peer-id')} PeerId */ +const log = Object.assign(debug('libp2p:peer-store'), { + error: debug('libp2p:peer-store:err') +}) + /** - * @extends {EventEmitter} + * An implementation of PeerStore that stores data in a Datastore * - * @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. - * @fires PeerStore#change:pubkey Emitted emitted when a peer's public key is known. - * @fires PeerStore#change:metadata Emitted when the known metadata of a peer change. + * @implements {PeerStore} */ -class PeerStore extends EventEmitter { +class DefaultPeerStore extends EventEmitter { /** - * Peer object - * - * @typedef {Object} Peer - * @property {PeerId} id peer's peer-id instance. - * @property {Address[]} addresses peer's addresses containing its multiaddrs and metadata. - * @property {string[]} protocols peer's supported protocols. - * @property {Map|undefined} metadata peer's metadata map. + * @param {object} properties + * @param {PeerId} properties.peerId + * @param {import('interface-datastore').Datastore} properties.datastore */ - - /** - * Responsible for managing known peers, as well as their addresses, protocols and metadata. - * - * @param {object} options - * @param {PeerId} options.peerId - * @class - */ - constructor ({ peerId }) { + constructor ({ peerId, datastore }) { super() this._peerId = peerId + this._store = new Store(datastore) - /** - * AddressBook containing a map of peerIdStr to Address. - */ - this.addressBook = new AddressBook(this) - - /** - * KeyBook containing a map of peerIdStr to their PeerId with public keys. - */ - this.keyBook = new KeyBook(this) - - /** - * MetadataBook containing a map of peerIdStr to their metadata Map. - */ - this.metadataBook = new MetadataBook(this) - - /** - * ProtoBook containing a map of peerIdStr to supported protocols. - */ - this.protoBook = new ProtoBook(this) + this.addressBook = new AddressBook(this.emit.bind(this), this._store) + this.keyBook = new KeyBook(this.emit.bind(this), this._store) + this.metadataBook = new MetadataBook(this.emit.bind(this), this._store) + this.protoBook = new ProtoBook(this.emit.bind(this), this._store) } - /** - * Start the PeerStore. - */ - start () {} - - /** - * Stop the PeerStore. - */ - stop () {} + async * getPeers () { + log('getPeers await read lock') + const release = await this._store.lock.readLock() + log('getPeers got read lock') + + try { + for await (const peer of this._store.all()) { + if (peer.id.toB58String() === this._peerId.toB58String()) { + // Remove self peer if present + continue + } + + yield peer + } + } finally { + log('getPeers release read lock') + release() + } + } /** - * Get all the stored information of every peer known. + * Delete the information of the given peer in every book * - * @returns {Map} + * @param {PeerId} peerId */ - get peers () { - const storedPeers = new Set([ - ...this.addressBook.data.keys(), - ...this.keyBook.data.keys(), - ...this.protoBook.data.keys(), - ...this.metadataBook.data.keys() - ]) - - // Remove self peer if present - this._peerId && storedPeers.delete(this._peerId.toB58String()) - - const peersData = new Map() - storedPeers.forEach((idStr) => { - peersData.set(idStr, this.get(PeerId.createFromB58String(idStr))) - }) - - return peersData + async delete (peerId) { + log('delete await write lock') + const release = await this._store.lock.writeLock() + log('delete got write lock') + + try { + await this._store.delete(peerId) + } finally { + log('delete release write lock') + release() + } } /** - * Delete the information of the given peer in every book. + * Get the stored information of a given peer * * @param {PeerId} peerId - * @returns {boolean} true if found and removed */ - delete (peerId) { - const addressesDeleted = this.addressBook.delete(peerId) - const keyDeleted = this.keyBook.delete(peerId) - const protocolsDeleted = this.protoBook.delete(peerId) - const metadataDeleted = this.metadataBook.delete(peerId) - - return addressesDeleted || keyDeleted || protocolsDeleted || metadataDeleted + async get (peerId) { + log('get await read lock') + const release = await this._store.lock.readLock() + log('get got read lock') + + try { + return this._store.load(peerId) + } finally { + log('get release read lock') + release() + } } /** - * Get the stored information of a given peer. + * Returns true if we have a record of the peer * * @param {PeerId} peerId - * @returns {Peer|undefined} */ - get (peerId) { - if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) - } - - const id = this.keyBook.data.get(peerId.toB58String()) - const addresses = this.addressBook.get(peerId) - const metadata = this.metadataBook.get(peerId) - const protocols = this.protoBook.get(peerId) - - if (!id && !addresses && !metadata && !protocols) { - return undefined - } - - return { - id: id || peerId, - addresses: addresses || [], - protocols: protocols || [], - metadata: metadata + async has (peerId) { + log('has await read lock') + const release = await this._store.lock.readLock() + log('has got read lock') + + try { + return this._store.has(peerId) + } finally { + log('has release read lock') + release() } } } -module.exports = PeerStore +module.exports = DefaultPeerStore diff --git a/src/peer-store/key-book.js b/src/peer-store/key-book.js index 356c81866e..dfd4c1511e 100644 --- a/src/peer-store/key-book.js +++ b/src/peer-store/key-book.js @@ -1,96 +1,141 @@ 'use strict' const debug = require('debug') -const log = Object.assign(debug('libp2p:peer-store:key-book'), { - error: debug('libp2p:peer-store:key-book:err') -}) const errcode = require('err-code') - +const { codes } = require('../errors') const PeerId = require('peer-id') - -const Book = require('./book') - -const { - codes: { ERR_INVALID_PARAMETERS } -} = require('../errors') +const { equals: uint8arrayEquals } = require('uint8arrays/equals') /** - * @typedef {import('./')} PeerStore - * @typedef {import('libp2p-crypto').PublicKey} PublicKey + * @typedef {import('./types').PeerStore} PeerStore + * @typedef {import('./types').KeyBook} KeyBook + * @typedef {import('libp2p-interfaces/src/keys/types').PublicKey} PublicKey */ +const log = Object.assign(debug('libp2p:peer-store:key-book'), { + error: debug('libp2p:peer-store:key-book:err') +}) + +const EVENT_NAME = 'change:pubkey' + /** - * @extends {Book} + * @implements {KeyBook} */ -class KeyBook extends Book { +class PeerStoreKeyBook { /** * The KeyBook is responsible for keeping the known public keys of a peer. * - * @class - * @param {PeerStore} peerStore + * @param {PeerStore["emit"]} emit + * @param {import('./types').Store} store */ - constructor (peerStore) { - super({ - peerStore, - eventName: 'change:pubkey', - eventProperty: 'pubkey', - eventTransformer: (data) => data.pubKey - }) - - /** - * Map known peers to their known Public Key. - * - * @type {Map} - */ - this.data = new Map() + constructor (emit, store) { + this._emit = emit + this._store = store } /** - * Set the Peer public key. + * Set the Peer public key * - * @override * @param {PeerId} peerId * @param {PublicKey} publicKey - * @returns {KeyBook} */ - set (peerId, publicKey) { + async set (peerId, publicKey) { if (!PeerId.isPeerId(peerId)) { log.error('peerId must be an instance of peer-id to store data') - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } - const id = peerId.toB58String() - const recPeerId = this.data.get(id) - - // If no record available, and this is valid - if (!recPeerId && publicKey) { - // This might be unecessary, but we want to store the PeerId - // to avoid an async operation when reconstructing the PeerId - peerId.pubKey = publicKey + if (!publicKey) { + log.error('publicKey must be an instance of PublicKey to store data') + throw errcode(new Error('publicKey must be an instance of PublicKey'), codes.ERR_INVALID_PARAMETERS) + } - this._setData(peerId, peerId) - log(`stored provided public key for ${id}`) + log('set await write lock') + const release = await this._store.lock.writeLock() + log('set got write lock') + + let updatedKey = false + + try { + try { + const existing = await this._store.load(peerId) + + if (existing.pubKey && uint8arrayEquals(existing.pubKey.bytes, publicKey.bytes)) { + return + } + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } + + await this._store.patchOrCreate(peerId, { + pubKey: publicKey + }) + updatedKey = true + } finally { + log('set release write lock') + release() } - return this + if (updatedKey) { + this._emit(EVENT_NAME, { peerId, pubKey: publicKey }) + } } /** - * Get Public key of the given PeerId, if stored. + * Get Public key of the given PeerId, if stored * - * @override * @param {PeerId} peerId - * @returns {PublicKey | undefined} */ - get (peerId) { + async get (peerId) { if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + 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'), codes.ERR_INVALID_PARAMETERS) } - const rec = this.data.get(peerId.toB58String()) + log('get await write lock') + const release = await this._store.lock.readLock() + log('get got write lock') + + try { + const peer = await this._store.load(peerId) + + return peer.pubKey + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } finally { + log('get release write lock') + release() + } + } + + /** + * @param {PeerId} peerId + */ + async delete (peerId) { + 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'), codes.ERR_INVALID_PARAMETERS) + } + + log('delete await write lock') + const release = await this._store.lock.writeLock() + log('delete got write lock') + + try { + await this._store.patchOrCreate(peerId, { + pubKey: undefined + }) + } finally { + log('delete release write lock') + release() + } - return rec ? rec.pubKey : undefined + this._emit(EVENT_NAME, { peerId, pubKey: undefined }) } } -module.exports = KeyBook +module.exports = PeerStoreKeyBook diff --git a/src/peer-store/metadata-book.js b/src/peer-store/metadata-book.js index 3eb5b38ed9..7176a78afb 100644 --- a/src/peer-store/metadata-book.js +++ b/src/peer-store/metadata-book.js @@ -1,180 +1,250 @@ 'use strict' const debug = require('debug') -const log = Object.assign(debug('libp2p:peer-store:proto-book'), { - error: debug('libp2p:peer-store:proto-book:err') -}) const errcode = require('err-code') -const { equals: uint8ArrayEquals } = require('uint8arrays/equals') - +const { codes } = require('../errors') const PeerId = require('peer-id') +const { equals: uint8ArrayEquals } = require('uint8arrays/equals') -const Book = require('./book') - -const { - codes: { ERR_INVALID_PARAMETERS } -} = require('../errors') +const log = Object.assign(debug('libp2p:peer-store:metadata-book'), { + error: debug('libp2p:peer-store:metadata-book:err') +}) /** - * @typedef {import('./')} PeerStore + * @typedef {import('./types').PeerStore} PeerStore + * @typedef {import('./types').MetadataBook} MetadataBook */ +const EVENT_NAME = 'change:metadata' + /** - * @extends {Book} - * - * @fires MetadataBook#change:metadata + * @implements {MetadataBook} */ -class MetadataBook extends Book { +class PeerStoreMetadataBook { /** * The MetadataBook is responsible for keeping the known supported - * protocols of a peer. + * protocols of a peer * - * @class - * @param {PeerStore} peerStore + * @param {PeerStore["emit"]} emit + * @param {import('./types').Store} store */ - constructor (peerStore) { - /** - * PeerStore Event emitter, used by the MetadataBook to emit: - * "change:metadata" - emitted when the known metadata of a peer change. - */ - super({ - peerStore, - eventName: 'change:metadata', - eventProperty: 'metadata' - }) - - /** - * Map known peers to their known protocols. - * - * @type {Map>} - */ - this.data = new Map() + constructor (emit, store) { + this._emit = emit + this._store = store } /** - * Set metadata key and value of a provided peer. + * Get the known data of a provided peer * - * @override * @param {PeerId} peerId - * @param {string} key - metadata key - * @param {Uint8Array} value - metadata value - * @returns {MetadataBook} */ - // @ts-ignore override with more then the parameters expected in Book - set (peerId, key, value) { + async get (peerId) { 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) + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } - if (typeof key !== 'string' || !(value instanceof Uint8Array)) { - log.error('valid key and value must be provided to store data') - throw errcode(new Error('valid key and value must be provided'), ERR_INVALID_PARAMETERS) + log('get await read lock') + const release = await this._store.lock.readLock() + log('get got read lock') + + try { + const peer = await this._store.load(peerId) + + return peer.metadata + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } finally { + log('get release read lock') + release() } - this._setValue(peerId, key, value) - - return this + return new Map() } /** - * Set data into the datastructure + * Get specific metadata value, if it exists * * @param {PeerId} peerId * @param {string} key - * @param {Uint8Array} value - * @param {object} [opts] - * @param {boolean} [opts.emit] */ - _setValue (peerId, key, value, { emit = true } = {}) { - const id = peerId.toB58String() - const rec = this.data.get(id) || new Map() - const recMap = rec.get(key) - - // Already exists and is equal - if (recMap && uint8ArrayEquals(value, recMap)) { - log(`the metadata provided to store is equal to the already stored for ${id} on ${key}`) - return + async getValue (peerId, key) { + 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'), codes.ERR_INVALID_PARAMETERS) } - rec.set(key, value) - this.data.set(id, rec) - - emit && this._emit(peerId, key) + log('getValue await read lock') + const release = await this._store.lock.readLock() + log('getValue got read lock') + + try { + const peer = await this._store.load(peerId) + + return peer.metadata.get(key) + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } finally { + log('getValue release write lock') + release() + } } /** - * Get the known data of a provided peer. - * * @param {PeerId} peerId - * @returns {Map|undefined} + * @param {Map} metadata */ - get (peerId) { + async set (peerId, metadata) { if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + 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'), codes.ERR_INVALID_PARAMETERS) + } + + if (!metadata || !(metadata instanceof Map)) { + log.error('valid metadata must be provided to store data') + throw errcode(new Error('valid metadata must be provided'), codes.ERR_INVALID_PARAMETERS) } - return this.data.get(peerId.toB58String()) + log('set await write lock') + const release = await this._store.lock.writeLock() + log('set got write lock') + + try { + await this._store.mergeOrCreate(peerId, { + metadata + }) + } finally { + log('set release write lock') + release() + } + + this._emit(EVENT_NAME, { peerId, metadata }) } /** - * Get specific metadata value, if it exists + * Set metadata key and value of a provided peer * * @param {PeerId} peerId - * @param {string} key - * @returns {Uint8Array | undefined} + * @param {string} key - metadata key + * @param {Uint8Array} value - metadata value */ - getValue (peerId, key) { + async setValue (peerId, key, value) { if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + 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'), codes.ERR_INVALID_PARAMETERS) + } + + if (typeof key !== 'string' || !(value instanceof Uint8Array)) { + log.error('valid key and value must be provided to store data') + throw errcode(new Error('valid key and value must be provided'), codes.ERR_INVALID_PARAMETERS) + } + + log('setValue await write lock') + const release = await this._store.lock.writeLock() + log('setValue got write lock') + + let updatedPeer + + try { + try { + const existingPeer = await this._store.load(peerId) + const existingValue = existingPeer.metadata.get(key) + + if (existingValue != null && uint8ArrayEquals(value, existingValue)) { + return + } + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } + + updatedPeer = await this._store.mergeOrCreate(peerId, { + metadata: new Map([[key, value]]) + }) + } finally { + log('setValue release write lock') + release() } - const rec = this.data.get(peerId.toB58String()) - return rec && rec.get(key) + this._emit(EVENT_NAME, { peerId, metadata: updatedPeer.metadata }) } /** - * Deletes the provided peer from the book. - * * @param {PeerId} peerId - * @returns {boolean} */ - delete (peerId) { + async delete (peerId) { if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + 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'), codes.ERR_INVALID_PARAMETERS) } - if (!this.data.delete(peerId.toB58String())) { - return false - } + log('delete await write lock') + const release = await this._store.lock.writeLock() + log('delete got write lock') + + let has + + try { + has = await this._store.has(peerId) - this._emit(peerId) + if (has) { + await this._store.patch(peerId, { + metadata: new Map() + }) + } + } finally { + log('delete release write lock') + release() + } - return true + if (has) { + this._emit(EVENT_NAME, { peerId, metadata: new Map() }) + } } /** - * Deletes the provided peer metadata key from the book. - * * @param {PeerId} peerId * @param {string} key - * @returns {boolean} */ - deleteValue (peerId, key) { + async deleteValue (peerId, key) { if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + 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'), codes.ERR_INVALID_PARAMETERS) } - const rec = this.data.get(peerId.toB58String()) + log('deleteValue await write lock') + const release = await this._store.lock.writeLock() + log('deleteValue got write lock') - if (!rec || !rec.delete(key)) { - return false - } + let metadata + + try { + const peer = await this._store.load(peerId) + metadata = peer.metadata - this._emit(peerId, key) + metadata.delete(key) - return true + await this._store.patch(peerId, { + metadata + }) + } catch (/** @type {any} **/ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } finally { + log('deleteValue release write lock') + release() + } + + if (metadata) { + this._emit(EVENT_NAME, { peerId, metadata }) + } } } -module.exports = MetadataBook +module.exports = PeerStoreMetadataBook diff --git a/src/peer-store/pb/peer.d.ts b/src/peer-store/pb/peer.d.ts new file mode 100644 index 0000000000..e6ccdfe6c1 --- /dev/null +++ b/src/peer-store/pb/peer.d.ts @@ -0,0 +1,222 @@ +import * as $protobuf from "protobufjs"; +/** Properties of a Peer. */ +export interface IPeer { + + /** Peer addresses */ + addresses?: (IAddress[]|null); + + /** Peer protocols */ + protocols?: (string[]|null); + + /** Peer metadata */ + metadata?: (IMetadata[]|null); + + /** Peer pubKey */ + pubKey?: (Uint8Array|null); + + /** Peer peerRecordEnvelope */ + peerRecordEnvelope?: (Uint8Array|null); +} + +/** Represents a Peer. */ +export class Peer implements IPeer { + + /** + * Constructs a new Peer. + * @param [p] Properties to set + */ + constructor(p?: IPeer); + + /** Peer addresses. */ + public addresses: IAddress[]; + + /** Peer protocols. */ + public protocols: string[]; + + /** Peer metadata. */ + public metadata: IMetadata[]; + + /** Peer pubKey. */ + public pubKey?: (Uint8Array|null); + + /** Peer peerRecordEnvelope. */ + public peerRecordEnvelope?: (Uint8Array|null); + + /** Peer _pubKey. */ + public _pubKey?: "pubKey"; + + /** Peer _peerRecordEnvelope. */ + public _peerRecordEnvelope?: "peerRecordEnvelope"; + + /** + * Encodes the specified Peer message. Does not implicitly {@link Peer.verify|verify} messages. + * @param m Peer message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: IPeer, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a Peer message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns Peer + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Peer; + + /** + * Creates a Peer message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns Peer + */ + public static fromObject(d: { [k: string]: any }): Peer; + + /** + * Creates a plain object from a Peer message. Also converts values to other types if specified. + * @param m Peer + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: Peer, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Peer to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; +} + +/** Properties of an Address. */ +export interface IAddress { + + /** Address multiaddr */ + multiaddr?: (Uint8Array|null); + + /** Address isCertified */ + isCertified?: (boolean|null); +} + +/** Represents an Address. */ +export class Address implements IAddress { + + /** + * Constructs a new Address. + * @param [p] Properties to set + */ + constructor(p?: IAddress); + + /** Address multiaddr. */ + public multiaddr: Uint8Array; + + /** Address isCertified. */ + public isCertified?: (boolean|null); + + /** Address _isCertified. */ + public _isCertified?: "isCertified"; + + /** + * Encodes the specified Address message. Does not implicitly {@link Address.verify|verify} messages. + * @param m Address message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: IAddress, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes an Address message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns Address + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Address; + + /** + * Creates an Address message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns Address + */ + public static fromObject(d: { [k: string]: any }): Address; + + /** + * Creates a plain object from an Address message. Also converts values to other types if specified. + * @param m Address + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: Address, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Address to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; +} + +/** Properties of a Metadata. */ +export interface IMetadata { + + /** Metadata key */ + key?: (string|null); + + /** Metadata value */ + value?: (Uint8Array|null); +} + +/** Represents a Metadata. */ +export class Metadata implements IMetadata { + + /** + * Constructs a new Metadata. + * @param [p] Properties to set + */ + constructor(p?: IMetadata); + + /** Metadata key. */ + public key: string; + + /** Metadata value. */ + public value: Uint8Array; + + /** + * Encodes the specified Metadata message. Does not implicitly {@link Metadata.verify|verify} messages. + * @param m Metadata message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: IMetadata, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a Metadata message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns Metadata + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Metadata; + + /** + * Creates a Metadata message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns Metadata + */ + public static fromObject(d: { [k: string]: any }): Metadata; + + /** + * Creates a plain object from a Metadata message. Also converts values to other types if specified. + * @param m Metadata + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: Metadata, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Metadata to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; +} diff --git a/src/peer-store/pb/peer.js b/src/peer-store/pb/peer.js new file mode 100644 index 0000000000..866911d8a4 --- /dev/null +++ b/src/peer-store/pb/peer.js @@ -0,0 +1,643 @@ +/*eslint-disable*/ +"use strict"; + +var $protobuf = require("protobufjs/minimal"); + +// Common aliases +var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; + +// Exported root namespace +var $root = $protobuf.roots["libp2p-peer"] || ($protobuf.roots["libp2p-peer"] = {}); + +$root.Peer = (function() { + + /** + * Properties of a Peer. + * @exports IPeer + * @interface IPeer + * @property {Array.|null} [addresses] Peer addresses + * @property {Array.|null} [protocols] Peer protocols + * @property {Array.|null} [metadata] Peer metadata + * @property {Uint8Array|null} [pubKey] Peer pubKey + * @property {Uint8Array|null} [peerRecordEnvelope] Peer peerRecordEnvelope + */ + + /** + * Constructs a new Peer. + * @exports Peer + * @classdesc Represents a Peer. + * @implements IPeer + * @constructor + * @param {IPeer=} [p] Properties to set + */ + function Peer(p) { + this.addresses = []; + this.protocols = []; + this.metadata = []; + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + /** + * Peer addresses. + * @member {Array.} addresses + * @memberof Peer + * @instance + */ + Peer.prototype.addresses = $util.emptyArray; + + /** + * Peer protocols. + * @member {Array.} protocols + * @memberof Peer + * @instance + */ + Peer.prototype.protocols = $util.emptyArray; + + /** + * Peer metadata. + * @member {Array.} metadata + * @memberof Peer + * @instance + */ + Peer.prototype.metadata = $util.emptyArray; + + /** + * Peer pubKey. + * @member {Uint8Array|null|undefined} pubKey + * @memberof Peer + * @instance + */ + Peer.prototype.pubKey = null; + + /** + * Peer peerRecordEnvelope. + * @member {Uint8Array|null|undefined} peerRecordEnvelope + * @memberof Peer + * @instance + */ + Peer.prototype.peerRecordEnvelope = null; + + // OneOf field names bound to virtual getters and setters + var $oneOfFields; + + /** + * Peer _pubKey. + * @member {"pubKey"|undefined} _pubKey + * @memberof Peer + * @instance + */ + Object.defineProperty(Peer.prototype, "_pubKey", { + get: $util.oneOfGetter($oneOfFields = ["pubKey"]), + set: $util.oneOfSetter($oneOfFields) + }); + + /** + * Peer _peerRecordEnvelope. + * @member {"peerRecordEnvelope"|undefined} _peerRecordEnvelope + * @memberof Peer + * @instance + */ + Object.defineProperty(Peer.prototype, "_peerRecordEnvelope", { + get: $util.oneOfGetter($oneOfFields = ["peerRecordEnvelope"]), + set: $util.oneOfSetter($oneOfFields) + }); + + /** + * Encodes the specified Peer message. Does not implicitly {@link Peer.verify|verify} messages. + * @function encode + * @memberof Peer + * @static + * @param {IPeer} m Peer message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Peer.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.addresses != null && m.addresses.length) { + for (var i = 0; i < m.addresses.length; ++i) + $root.Address.encode(m.addresses[i], w.uint32(10).fork()).ldelim(); + } + if (m.protocols != null && m.protocols.length) { + for (var i = 0; i < m.protocols.length; ++i) + w.uint32(18).string(m.protocols[i]); + } + if (m.metadata != null && m.metadata.length) { + for (var i = 0; i < m.metadata.length; ++i) + $root.Metadata.encode(m.metadata[i], w.uint32(26).fork()).ldelim(); + } + if (m.pubKey != null && Object.hasOwnProperty.call(m, "pubKey")) + w.uint32(34).bytes(m.pubKey); + if (m.peerRecordEnvelope != null && Object.hasOwnProperty.call(m, "peerRecordEnvelope")) + w.uint32(42).bytes(m.peerRecordEnvelope); + return w; + }; + + /** + * Decodes a Peer message from the specified reader or buffer. + * @function decode + * @memberof Peer + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {Peer} Peer + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Peer.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.Peer(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + if (!(m.addresses && m.addresses.length)) + m.addresses = []; + m.addresses.push($root.Address.decode(r, r.uint32())); + break; + case 2: + if (!(m.protocols && m.protocols.length)) + m.protocols = []; + m.protocols.push(r.string()); + break; + case 3: + if (!(m.metadata && m.metadata.length)) + m.metadata = []; + m.metadata.push($root.Metadata.decode(r, r.uint32())); + break; + case 4: + m.pubKey = r.bytes(); + break; + case 5: + m.peerRecordEnvelope = r.bytes(); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates a Peer message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof Peer + * @static + * @param {Object.} d Plain object + * @returns {Peer} Peer + */ + Peer.fromObject = function fromObject(d) { + if (d instanceof $root.Peer) + return d; + var m = new $root.Peer(); + if (d.addresses) { + if (!Array.isArray(d.addresses)) + throw TypeError(".Peer.addresses: array expected"); + m.addresses = []; + for (var i = 0; i < d.addresses.length; ++i) { + if (typeof d.addresses[i] !== "object") + throw TypeError(".Peer.addresses: object expected"); + m.addresses[i] = $root.Address.fromObject(d.addresses[i]); + } + } + if (d.protocols) { + if (!Array.isArray(d.protocols)) + throw TypeError(".Peer.protocols: array expected"); + m.protocols = []; + for (var i = 0; i < d.protocols.length; ++i) { + m.protocols[i] = String(d.protocols[i]); + } + } + if (d.metadata) { + if (!Array.isArray(d.metadata)) + throw TypeError(".Peer.metadata: array expected"); + m.metadata = []; + for (var i = 0; i < d.metadata.length; ++i) { + if (typeof d.metadata[i] !== "object") + throw TypeError(".Peer.metadata: object expected"); + m.metadata[i] = $root.Metadata.fromObject(d.metadata[i]); + } + } + if (d.pubKey != null) { + if (typeof d.pubKey === "string") + $util.base64.decode(d.pubKey, m.pubKey = $util.newBuffer($util.base64.length(d.pubKey)), 0); + else if (d.pubKey.length) + m.pubKey = d.pubKey; + } + if (d.peerRecordEnvelope != null) { + if (typeof d.peerRecordEnvelope === "string") + $util.base64.decode(d.peerRecordEnvelope, m.peerRecordEnvelope = $util.newBuffer($util.base64.length(d.peerRecordEnvelope)), 0); + else if (d.peerRecordEnvelope.length) + m.peerRecordEnvelope = d.peerRecordEnvelope; + } + return m; + }; + + /** + * Creates a plain object from a Peer message. Also converts values to other types if specified. + * @function toObject + * @memberof Peer + * @static + * @param {Peer} m Peer + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + Peer.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.arrays || o.defaults) { + d.addresses = []; + d.protocols = []; + d.metadata = []; + } + if (m.addresses && m.addresses.length) { + d.addresses = []; + for (var j = 0; j < m.addresses.length; ++j) { + d.addresses[j] = $root.Address.toObject(m.addresses[j], o); + } + } + if (m.protocols && m.protocols.length) { + d.protocols = []; + for (var j = 0; j < m.protocols.length; ++j) { + d.protocols[j] = m.protocols[j]; + } + } + if (m.metadata && m.metadata.length) { + d.metadata = []; + for (var j = 0; j < m.metadata.length; ++j) { + d.metadata[j] = $root.Metadata.toObject(m.metadata[j], o); + } + } + if (m.pubKey != null && m.hasOwnProperty("pubKey")) { + d.pubKey = o.bytes === String ? $util.base64.encode(m.pubKey, 0, m.pubKey.length) : o.bytes === Array ? Array.prototype.slice.call(m.pubKey) : m.pubKey; + if (o.oneofs) + d._pubKey = "pubKey"; + } + if (m.peerRecordEnvelope != null && m.hasOwnProperty("peerRecordEnvelope")) { + d.peerRecordEnvelope = o.bytes === String ? $util.base64.encode(m.peerRecordEnvelope, 0, m.peerRecordEnvelope.length) : o.bytes === Array ? Array.prototype.slice.call(m.peerRecordEnvelope) : m.peerRecordEnvelope; + if (o.oneofs) + d._peerRecordEnvelope = "peerRecordEnvelope"; + } + return d; + }; + + /** + * Converts this Peer to JSON. + * @function toJSON + * @memberof Peer + * @instance + * @returns {Object.} JSON object + */ + Peer.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return Peer; +})(); + +$root.Address = (function() { + + /** + * Properties of an Address. + * @exports IAddress + * @interface IAddress + * @property {Uint8Array|null} [multiaddr] Address multiaddr + * @property {boolean|null} [isCertified] Address isCertified + */ + + /** + * Constructs a new Address. + * @exports Address + * @classdesc Represents an Address. + * @implements IAddress + * @constructor + * @param {IAddress=} [p] Properties to set + */ + function Address(p) { + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + /** + * Address multiaddr. + * @member {Uint8Array} multiaddr + * @memberof Address + * @instance + */ + Address.prototype.multiaddr = $util.newBuffer([]); + + /** + * Address isCertified. + * @member {boolean|null|undefined} isCertified + * @memberof Address + * @instance + */ + Address.prototype.isCertified = null; + + // OneOf field names bound to virtual getters and setters + var $oneOfFields; + + /** + * Address _isCertified. + * @member {"isCertified"|undefined} _isCertified + * @memberof Address + * @instance + */ + Object.defineProperty(Address.prototype, "_isCertified", { + get: $util.oneOfGetter($oneOfFields = ["isCertified"]), + set: $util.oneOfSetter($oneOfFields) + }); + + /** + * Encodes the specified Address message. Does not implicitly {@link Address.verify|verify} messages. + * @function encode + * @memberof Address + * @static + * @param {IAddress} m Address message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Address.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.multiaddr != null && Object.hasOwnProperty.call(m, "multiaddr")) + w.uint32(10).bytes(m.multiaddr); + if (m.isCertified != null && Object.hasOwnProperty.call(m, "isCertified")) + w.uint32(16).bool(m.isCertified); + return w; + }; + + /** + * Decodes an Address message from the specified reader or buffer. + * @function decode + * @memberof Address + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {Address} Address + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Address.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.Address(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + m.multiaddr = r.bytes(); + break; + case 2: + m.isCertified = r.bool(); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates an Address message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof Address + * @static + * @param {Object.} d Plain object + * @returns {Address} Address + */ + Address.fromObject = function fromObject(d) { + if (d instanceof $root.Address) + return d; + var m = new $root.Address(); + if (d.multiaddr != null) { + if (typeof d.multiaddr === "string") + $util.base64.decode(d.multiaddr, m.multiaddr = $util.newBuffer($util.base64.length(d.multiaddr)), 0); + else if (d.multiaddr.length) + m.multiaddr = d.multiaddr; + } + if (d.isCertified != null) { + m.isCertified = Boolean(d.isCertified); + } + return m; + }; + + /** + * Creates a plain object from an Address message. Also converts values to other types if specified. + * @function toObject + * @memberof Address + * @static + * @param {Address} m Address + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + Address.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.defaults) { + if (o.bytes === String) + d.multiaddr = ""; + else { + d.multiaddr = []; + if (o.bytes !== Array) + d.multiaddr = $util.newBuffer(d.multiaddr); + } + } + if (m.multiaddr != null && m.hasOwnProperty("multiaddr")) { + d.multiaddr = o.bytes === String ? $util.base64.encode(m.multiaddr, 0, m.multiaddr.length) : o.bytes === Array ? Array.prototype.slice.call(m.multiaddr) : m.multiaddr; + } + if (m.isCertified != null && m.hasOwnProperty("isCertified")) { + d.isCertified = m.isCertified; + if (o.oneofs) + d._isCertified = "isCertified"; + } + return d; + }; + + /** + * Converts this Address to JSON. + * @function toJSON + * @memberof Address + * @instance + * @returns {Object.} JSON object + */ + Address.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return Address; +})(); + +$root.Metadata = (function() { + + /** + * Properties of a Metadata. + * @exports IMetadata + * @interface IMetadata + * @property {string|null} [key] Metadata key + * @property {Uint8Array|null} [value] Metadata value + */ + + /** + * Constructs a new Metadata. + * @exports Metadata + * @classdesc Represents a Metadata. + * @implements IMetadata + * @constructor + * @param {IMetadata=} [p] Properties to set + */ + function Metadata(p) { + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + /** + * Metadata key. + * @member {string} key + * @memberof Metadata + * @instance + */ + Metadata.prototype.key = ""; + + /** + * Metadata value. + * @member {Uint8Array} value + * @memberof Metadata + * @instance + */ + Metadata.prototype.value = $util.newBuffer([]); + + /** + * Encodes the specified Metadata message. Does not implicitly {@link Metadata.verify|verify} messages. + * @function encode + * @memberof Metadata + * @static + * @param {IMetadata} m Metadata message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Metadata.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.key != null && Object.hasOwnProperty.call(m, "key")) + w.uint32(10).string(m.key); + if (m.value != null && Object.hasOwnProperty.call(m, "value")) + w.uint32(18).bytes(m.value); + return w; + }; + + /** + * Decodes a Metadata message from the specified reader or buffer. + * @function decode + * @memberof Metadata + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {Metadata} Metadata + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Metadata.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.Metadata(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + m.key = r.string(); + break; + case 2: + m.value = r.bytes(); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates a Metadata message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof Metadata + * @static + * @param {Object.} d Plain object + * @returns {Metadata} Metadata + */ + Metadata.fromObject = function fromObject(d) { + if (d instanceof $root.Metadata) + return d; + var m = new $root.Metadata(); + if (d.key != null) { + m.key = String(d.key); + } + if (d.value != null) { + if (typeof d.value === "string") + $util.base64.decode(d.value, m.value = $util.newBuffer($util.base64.length(d.value)), 0); + else if (d.value.length) + m.value = d.value; + } + return m; + }; + + /** + * Creates a plain object from a Metadata message. Also converts values to other types if specified. + * @function toObject + * @memberof Metadata + * @static + * @param {Metadata} m Metadata + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + Metadata.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.defaults) { + d.key = ""; + if (o.bytes === String) + d.value = ""; + else { + d.value = []; + if (o.bytes !== Array) + d.value = $util.newBuffer(d.value); + } + } + if (m.key != null && m.hasOwnProperty("key")) { + d.key = m.key; + } + if (m.value != null && m.hasOwnProperty("value")) { + d.value = o.bytes === String ? $util.base64.encode(m.value, 0, m.value.length) : o.bytes === Array ? Array.prototype.slice.call(m.value) : m.value; + } + return d; + }; + + /** + * Converts this Metadata to JSON. + * @function toJSON + * @memberof Metadata + * @instance + * @returns {Object.} JSON object + */ + Metadata.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return Metadata; +})(); + +module.exports = $root; diff --git a/src/peer-store/pb/peer.proto b/src/peer-store/pb/peer.proto new file mode 100644 index 0000000000..1c9cc166c4 --- /dev/null +++ b/src/peer-store/pb/peer.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +message Peer { + // Multiaddrs we know about + repeated Address addresses = 1; + + // The protocols the peer supports + repeated string protocols = 2; + + // Any peer metadata + repeated Metadata metadata = 3; + + // The public key of the peer + optional bytes pub_key = 4; + + // The most recently received signed PeerRecord + optional bytes peer_record_envelope = 5; +} + +// Address represents a single multiaddr +message Address { + bytes multiaddr = 1; + + // Flag to indicate if the address comes from a certified source + optional bool isCertified = 2; +} + +message Metadata { + string key = 1; + bytes value = 2; +} diff --git a/src/peer-store/persistent/consts.js b/src/peer-store/persistent/consts.js deleted file mode 100644 index 591c267e66..0000000000 --- a/src/peer-store/persistent/consts.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict' - -module.exports.NAMESPACE_COMMON = '/peers/' - -// /peers/protos/ -module.exports.NAMESPACE_ADDRESS = '/peers/addrs/' - -// /peers/keys/ -module.exports.NAMESPACE_KEYS = '/peers/keys/' - -// /peers/metadata// -module.exports.NAMESPACE_METADATA = '/peers/metadata/' - -// /peers/addrs/ -module.exports.NAMESPACE_PROTOCOL = '/peers/protos/' diff --git a/src/peer-store/persistent/index.js b/src/peer-store/persistent/index.js deleted file mode 100644 index 5655d20743..0000000000 --- a/src/peer-store/persistent/index.js +++ /dev/null @@ -1,408 +0,0 @@ -'use strict' - -const debug = require('debug') -const log = Object.assign(debug('libp2p:persistent-peer-store'), { - error: debug('libp2p:persistent-peer-store:err') -}) -const { Key } = require('interface-datastore/key') -const { Multiaddr } = require('multiaddr') -const PeerId = require('peer-id') -const { base32 } = require('multiformats/bases/base32') - -const PeerStore = require('..') - -const { - NAMESPACE_ADDRESS, - NAMESPACE_COMMON, - NAMESPACE_KEYS, - NAMESPACE_METADATA, - NAMESPACE_PROTOCOL -} = require('./consts') - -const { Addresses } = require('./pb/address-book') -const { Protocols } = require('./pb/proto-book') - -/** - * @typedef {import('interface-datastore').Batch} Batch - * @typedef {import('../address-book.js').Address} Address - */ - -/** - * @typedef {Object} PersistentPeerStoreProperties - * @property {PeerId} peerId - * @property {import('interface-datastore').Datastore} datastore - * - * @typedef {Object} PersistentPeerStoreOptions - * @property {number} [threshold = 5] - Number of dirty peers allowed before commit data. - */ - -/** - * Responsible for managing the persistence of data in the PeerStore. - */ -class PersistentPeerStore extends PeerStore { - /** - * @class - * @param {PersistentPeerStoreProperties & PersistentPeerStoreOptions} properties - */ - constructor ({ peerId, datastore, threshold = 5 }) { - super({ peerId }) - - /** - * Backend datastore used to persist data. - */ - this._datastore = datastore - - /** - * Peers modified after the latest data persisted. - */ - this._dirtyPeers = new Set() - - /** - * Peers metadata changed mapping peer identifers to metadata changed. - * - * @type {Map>} - */ - this._dirtyMetadata = new Map() - - this.threshold = threshold - this._addDirtyPeer = this._addDirtyPeer.bind(this) - } - - /** - * Start Persistent PeerStore. - * - * @returns {Promise} - */ - async start () { - log('PeerStore is starting') - - // Handlers for dirty peers - this.on('change:protocols', this._addDirtyPeer) - this.on('change:multiaddrs', this._addDirtyPeer) - this.on('change:pubkey', this._addDirtyPeerKey) - this.on('change:metadata', this._addDirtyPeerMetadata) - - // Load data - for await (const entry of this._datastore.query({ prefix: NAMESPACE_COMMON })) { - await this._processDatastoreEntry(entry) - } - - log('PeerStore started') - } - - /** - * Stop Persistent PeerStore. - * - * @returns {Promise} - */ - async stop () { - log('PeerStore is stopping') - this.removeAllListeners() - await this._commitData() - log('PeerStore stopped') - } - - /** - * Add modified peer to the dirty set - * - * @private - * @param {Object} params - * @param {PeerId} params.peerId - */ - _addDirtyPeer ({ peerId }) { - const peerIdstr = peerId.toB58String() - - log('add dirty peer', peerIdstr) - this._dirtyPeers.add(peerIdstr) - - if (this._dirtyPeers.size >= this.threshold) { - // Commit current data - this._commitData().catch(err => { - log.error('error committing data', err) - }) - } - } - - /** - * Add modified 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 - * @param {string} params.metadata - */ - _addDirtyPeerMetadata ({ peerId, metadata }) { - const peerIdstr = peerId.toB58String() - - log('add dirty metadata peer', peerIdstr) - this._dirtyPeers.add(peerIdstr) - - // Add dirty metadata key - const mData = this._dirtyMetadata.get(peerIdstr) || new Set() - mData.add(metadata) - this._dirtyMetadata.set(peerIdstr, mData) - - if (this._dirtyPeers.size >= this.threshold) { - // Commit current data - this._commitData().catch(err => { - log.error('error committing data', err) - }) - } - } - - /** - * Add all the peers current data to a datastore batch and commit it. - * - * @private - * @returns {Promise} - */ - async _commitData () { - const commitPeers = Array.from(this._dirtyPeers) - - if (!commitPeers.length) { - return - } - - // Clear Dirty Peers set - this._dirtyPeers.clear() - - log('create batch commit') - const batch = this._datastore.batch() - for (const peerIdStr of commitPeers) { - // PeerId - const peerId = this.keyBook.data.get(peerIdStr) || PeerId.createFromB58String(peerIdStr) - - // Address Book - this._batchAddressBook(peerId, batch) - - // Key Book - !peerId.hasInlinePublicKey() && this._batchKeyBook(peerId, batch) - - // Metadata Book - this._batchMetadataBook(peerId, batch) - - // Proto Book - this._batchProtoBook(peerId, batch) - } - - await batch.commit() - log('batch committed') - } - - /** - * Add address book data of the peer to the batch. - * - * @private - * @param {PeerId} peerId - * @param {Batch} batch - */ - _batchAddressBook (peerId, batch) { - const b32key = peerId.toString() - const key = new Key(`${NAMESPACE_ADDRESS}${b32key}`) - - const entry = this.addressBook.data.get(peerId.toB58String()) - - try { - // Deleted from the book - if (!entry) { - batch.delete(key) - return - } - - const encodedData = Addresses.encode({ - addrs: entry.addresses.map((address) => ({ - multiaddr: address.multiaddr.bytes, - isCertified: address.isCertified - })), - certifiedRecord: entry.record - ? { - seq: entry.record.seqNumber, - raw: entry.record.raw - } - : undefined - }).finish() - - batch.put(key, encodedData) - } catch (/** @type {any} */ err) { - log.error(err) - } - } - - /** - * Add Key book data of the peer to the batch. - * - * @private - * @param {PeerId} peerId - * @param {Batch} batch - */ - _batchKeyBook (peerId, batch) { - const b32key = peerId.toString() - const key = new Key(`${NAMESPACE_KEYS}${b32key}`) - - try { - // Deleted from the book - if (!peerId.pubKey) { - batch.delete(key) - return - } - - const encodedData = peerId.marshalPubKey() - - batch.put(key, encodedData) - } catch (/** @type {any} */ err) { - log.error(err) - } - } - - /** - * Add metadata book data of the peer to the batch. - * - * @private - * @param {PeerId} peerId - * @param {Batch} batch - */ - _batchMetadataBook (peerId, batch) { - const b32key = peerId.toString() - const dirtyMetada = this._dirtyMetadata.get(peerId.toB58String()) || [] - - try { - dirtyMetada.forEach((/** @type {string} */ dirtyKey) => { - const key = new Key(`${NAMESPACE_METADATA}${b32key}/${dirtyKey}`) - const dirtyValue = this.metadataBook.getValue(peerId, dirtyKey) - - if (dirtyValue) { - batch.put(key, dirtyValue) - } else { - batch.delete(key) - } - }) - } catch (/** @type {any} */ err) { - log.error(err) - } - } - - /** - * Add proto book data of the peer to the batch. - * - * @private - * @param {PeerId} peerId - * @param {Batch} batch - */ - _batchProtoBook (peerId, batch) { - const b32key = peerId.toString() - const key = new Key(`${NAMESPACE_PROTOCOL}${b32key}`) - - const protocols = this.protoBook.get(peerId) - - try { - // Deleted from the book - if (!protocols) { - batch.delete(key) - return - } - - const encodedData = Protocols.encode({ protocols }).finish() - - batch.put(key, encodedData) - } catch (/** @type {any} */ err) { - log.error(err) - } - } - - /** - * Process datastore entry and add its data to the correct book. - * - * @private - * @param {Object} params - * @param {Key} params.key - datastore key - * @param {Uint8Array} params.value - datastore value stored - * @returns {Promise} - */ - async _processDatastoreEntry ({ key, value }) { - try { - const keyParts = key.toString().split('/') - const peerId = PeerId.createFromBytes(base32.decode(keyParts[3])) - - let decoded - switch (keyParts[2]) { - case 'addrs': - decoded = Addresses.decode(value) - - // @ts-ignore protected function - this.addressBook._setData( - peerId, - { - addresses: decoded.addrs.map((address) => ({ - multiaddr: new Multiaddr(address.multiaddr), - isCertified: Boolean(address.isCertified) - })), - record: decoded.certifiedRecord - ? { - raw: decoded.certifiedRecord.raw, - seqNumber: decoded.certifiedRecord.seq - } - : undefined - }, - { emit: false }) - break - case 'keys': - decoded = await PeerId.createFromPubKey(value) - - // @ts-ignore protected function - this.keyBook._setData( - decoded, - decoded, - { emit: false }) - break - case 'metadata': - this.metadataBook._setValue( - peerId, - keyParts[4], - value, - { emit: false }) - break - case 'protos': - decoded = Protocols.decode(value) - - // @ts-ignore protected function - this.protoBook._setData( - peerId, - new Set(decoded.protocols), - { emit: false }) - break - default: - log('invalid data persisted for: ', key.toString()) - } - } catch (/** @type {any} */ err) { - log.error(err) - } - } -} - -module.exports = PersistentPeerStore diff --git a/src/peer-store/persistent/pb/address-book.d.ts b/src/peer-store/persistent/pb/address-book.d.ts deleted file mode 100644 index 0080a6390c..0000000000 --- a/src/peer-store/persistent/pb/address-book.d.ts +++ /dev/null @@ -1,198 +0,0 @@ -import * as $protobuf from "protobufjs"; -/** Properties of an Addresses. */ -export interface IAddresses { - - /** Addresses addrs */ - addrs?: (Addresses.IAddress[]|null); - - /** Addresses certifiedRecord */ - certifiedRecord?: (Addresses.ICertifiedRecord|null); -} - -/** Represents an Addresses. */ -export class Addresses implements IAddresses { - - /** - * Constructs a new Addresses. - * @param [p] Properties to set - */ - constructor(p?: IAddresses); - - /** Addresses addrs. */ - public addrs: Addresses.IAddress[]; - - /** Addresses certifiedRecord. */ - public certifiedRecord?: (Addresses.ICertifiedRecord|null); - - /** - * Encodes the specified Addresses message. Does not implicitly {@link Addresses.verify|verify} messages. - * @param m Addresses message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: IAddresses, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes an Addresses message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns Addresses - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Addresses; - - /** - * Creates an Addresses message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns Addresses - */ - public static fromObject(d: { [k: string]: any }): Addresses; - - /** - * Creates a plain object from an Addresses message. Also converts values to other types if specified. - * @param m Addresses - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: Addresses, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this Addresses to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; -} - -export namespace Addresses { - - /** Properties of an Address. */ - interface IAddress { - - /** Address multiaddr */ - multiaddr?: (Uint8Array|null); - - /** Address isCertified */ - isCertified?: (boolean|null); - } - - /** Represents an Address. */ - class Address implements IAddress { - - /** - * Constructs a new Address. - * @param [p] Properties to set - */ - constructor(p?: Addresses.IAddress); - - /** Address multiaddr. */ - public multiaddr: Uint8Array; - - /** Address isCertified. */ - public isCertified: boolean; - - /** - * Encodes the specified Address message. Does not implicitly {@link Addresses.Address.verify|verify} messages. - * @param m Address message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: Addresses.IAddress, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes an Address message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns Address - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Addresses.Address; - - /** - * Creates an Address message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns Address - */ - public static fromObject(d: { [k: string]: any }): Addresses.Address; - - /** - * Creates a plain object from an Address message. Also converts values to other types if specified. - * @param m Address - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: Addresses.Address, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this Address to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } - - /** Properties of a CertifiedRecord. */ - interface ICertifiedRecord { - - /** CertifiedRecord seq */ - seq?: (number|null); - - /** CertifiedRecord raw */ - raw?: (Uint8Array|null); - } - - /** Represents a CertifiedRecord. */ - class CertifiedRecord implements ICertifiedRecord { - - /** - * Constructs a new CertifiedRecord. - * @param [p] Properties to set - */ - constructor(p?: Addresses.ICertifiedRecord); - - /** CertifiedRecord seq. */ - public seq: number; - - /** CertifiedRecord raw. */ - public raw: Uint8Array; - - /** - * Encodes the specified CertifiedRecord message. Does not implicitly {@link Addresses.CertifiedRecord.verify|verify} messages. - * @param m CertifiedRecord message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: Addresses.ICertifiedRecord, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a CertifiedRecord message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns CertifiedRecord - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Addresses.CertifiedRecord; - - /** - * Creates a CertifiedRecord message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns CertifiedRecord - */ - public static fromObject(d: { [k: string]: any }): Addresses.CertifiedRecord; - - /** - * Creates a plain object from a CertifiedRecord message. Also converts values to other types if specified. - * @param m CertifiedRecord - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: Addresses.CertifiedRecord, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this CertifiedRecord to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } -} diff --git a/src/peer-store/persistent/pb/address-book.js b/src/peer-store/persistent/pb/address-book.js deleted file mode 100644 index f45bc94214..0000000000 --- a/src/peer-store/persistent/pb/address-book.js +++ /dev/null @@ -1,522 +0,0 @@ -/*eslint-disable*/ -"use strict"; - -var $protobuf = require("protobufjs/minimal"); - -// Common aliases -var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; - -// Exported root namespace -var $root = $protobuf.roots["libp2p-address-book"] || ($protobuf.roots["libp2p-address-book"] = {}); - -$root.Addresses = (function() { - - /** - * Properties of an Addresses. - * @exports IAddresses - * @interface IAddresses - * @property {Array.|null} [addrs] Addresses addrs - * @property {Addresses.ICertifiedRecord|null} [certifiedRecord] Addresses certifiedRecord - */ - - /** - * Constructs a new Addresses. - * @exports Addresses - * @classdesc Represents an Addresses. - * @implements IAddresses - * @constructor - * @param {IAddresses=} [p] Properties to set - */ - function Addresses(p) { - this.addrs = []; - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * Addresses addrs. - * @member {Array.} addrs - * @memberof Addresses - * @instance - */ - Addresses.prototype.addrs = $util.emptyArray; - - /** - * Addresses certifiedRecord. - * @member {Addresses.ICertifiedRecord|null|undefined} certifiedRecord - * @memberof Addresses - * @instance - */ - Addresses.prototype.certifiedRecord = null; - - /** - * Encodes the specified Addresses message. Does not implicitly {@link Addresses.verify|verify} messages. - * @function encode - * @memberof Addresses - * @static - * @param {IAddresses} m Addresses message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Addresses.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.addrs != null && m.addrs.length) { - for (var i = 0; i < m.addrs.length; ++i) - $root.Addresses.Address.encode(m.addrs[i], w.uint32(10).fork()).ldelim(); - } - if (m.certifiedRecord != null && Object.hasOwnProperty.call(m, "certifiedRecord")) - $root.Addresses.CertifiedRecord.encode(m.certifiedRecord, w.uint32(18).fork()).ldelim(); - return w; - }; - - /** - * Decodes an Addresses message from the specified reader or buffer. - * @function decode - * @memberof Addresses - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {Addresses} Addresses - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Addresses.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.Addresses(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - if (!(m.addrs && m.addrs.length)) - m.addrs = []; - m.addrs.push($root.Addresses.Address.decode(r, r.uint32())); - break; - case 2: - m.certifiedRecord = $root.Addresses.CertifiedRecord.decode(r, r.uint32()); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates an Addresses message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof Addresses - * @static - * @param {Object.} d Plain object - * @returns {Addresses} Addresses - */ - Addresses.fromObject = function fromObject(d) { - if (d instanceof $root.Addresses) - return d; - var m = new $root.Addresses(); - if (d.addrs) { - if (!Array.isArray(d.addrs)) - throw TypeError(".Addresses.addrs: array expected"); - m.addrs = []; - for (var i = 0; i < d.addrs.length; ++i) { - if (typeof d.addrs[i] !== "object") - throw TypeError(".Addresses.addrs: object expected"); - m.addrs[i] = $root.Addresses.Address.fromObject(d.addrs[i]); - } - } - if (d.certifiedRecord != null) { - if (typeof d.certifiedRecord !== "object") - throw TypeError(".Addresses.certifiedRecord: object expected"); - m.certifiedRecord = $root.Addresses.CertifiedRecord.fromObject(d.certifiedRecord); - } - return m; - }; - - /** - * Creates a plain object from an Addresses message. Also converts values to other types if specified. - * @function toObject - * @memberof Addresses - * @static - * @param {Addresses} m Addresses - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - Addresses.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.arrays || o.defaults) { - d.addrs = []; - } - if (o.defaults) { - d.certifiedRecord = null; - } - if (m.addrs && m.addrs.length) { - d.addrs = []; - for (var j = 0; j < m.addrs.length; ++j) { - d.addrs[j] = $root.Addresses.Address.toObject(m.addrs[j], o); - } - } - if (m.certifiedRecord != null && m.hasOwnProperty("certifiedRecord")) { - d.certifiedRecord = $root.Addresses.CertifiedRecord.toObject(m.certifiedRecord, o); - } - return d; - }; - - /** - * Converts this Addresses to JSON. - * @function toJSON - * @memberof Addresses - * @instance - * @returns {Object.} JSON object - */ - Addresses.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - Addresses.Address = (function() { - - /** - * Properties of an Address. - * @memberof Addresses - * @interface IAddress - * @property {Uint8Array|null} [multiaddr] Address multiaddr - * @property {boolean|null} [isCertified] Address isCertified - */ - - /** - * Constructs a new Address. - * @memberof Addresses - * @classdesc Represents an Address. - * @implements IAddress - * @constructor - * @param {Addresses.IAddress=} [p] Properties to set - */ - function Address(p) { - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * Address multiaddr. - * @member {Uint8Array} multiaddr - * @memberof Addresses.Address - * @instance - */ - Address.prototype.multiaddr = $util.newBuffer([]); - - /** - * Address isCertified. - * @member {boolean} isCertified - * @memberof Addresses.Address - * @instance - */ - Address.prototype.isCertified = false; - - /** - * Encodes the specified Address message. Does not implicitly {@link Addresses.Address.verify|verify} messages. - * @function encode - * @memberof Addresses.Address - * @static - * @param {Addresses.IAddress} m Address message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Address.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.multiaddr != null && Object.hasOwnProperty.call(m, "multiaddr")) - w.uint32(10).bytes(m.multiaddr); - if (m.isCertified != null && Object.hasOwnProperty.call(m, "isCertified")) - w.uint32(16).bool(m.isCertified); - return w; - }; - - /** - * Decodes an Address message from the specified reader or buffer. - * @function decode - * @memberof Addresses.Address - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {Addresses.Address} Address - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Address.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.Addresses.Address(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.multiaddr = r.bytes(); - break; - case 2: - m.isCertified = r.bool(); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates an Address message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof Addresses.Address - * @static - * @param {Object.} d Plain object - * @returns {Addresses.Address} Address - */ - Address.fromObject = function fromObject(d) { - if (d instanceof $root.Addresses.Address) - return d; - var m = new $root.Addresses.Address(); - if (d.multiaddr != null) { - if (typeof d.multiaddr === "string") - $util.base64.decode(d.multiaddr, m.multiaddr = $util.newBuffer($util.base64.length(d.multiaddr)), 0); - else if (d.multiaddr.length) - m.multiaddr = d.multiaddr; - } - if (d.isCertified != null) { - m.isCertified = Boolean(d.isCertified); - } - return m; - }; - - /** - * Creates a plain object from an Address message. Also converts values to other types if specified. - * @function toObject - * @memberof Addresses.Address - * @static - * @param {Addresses.Address} m Address - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - Address.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.defaults) { - if (o.bytes === String) - d.multiaddr = ""; - else { - d.multiaddr = []; - if (o.bytes !== Array) - d.multiaddr = $util.newBuffer(d.multiaddr); - } - d.isCertified = false; - } - if (m.multiaddr != null && m.hasOwnProperty("multiaddr")) { - d.multiaddr = o.bytes === String ? $util.base64.encode(m.multiaddr, 0, m.multiaddr.length) : o.bytes === Array ? Array.prototype.slice.call(m.multiaddr) : m.multiaddr; - } - if (m.isCertified != null && m.hasOwnProperty("isCertified")) { - d.isCertified = m.isCertified; - } - return d; - }; - - /** - * Converts this Address to JSON. - * @function toJSON - * @memberof Addresses.Address - * @instance - * @returns {Object.} JSON object - */ - Address.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return Address; - })(); - - Addresses.CertifiedRecord = (function() { - - /** - * Properties of a CertifiedRecord. - * @memberof Addresses - * @interface ICertifiedRecord - * @property {number|null} [seq] CertifiedRecord seq - * @property {Uint8Array|null} [raw] CertifiedRecord raw - */ - - /** - * Constructs a new CertifiedRecord. - * @memberof Addresses - * @classdesc Represents a CertifiedRecord. - * @implements ICertifiedRecord - * @constructor - * @param {Addresses.ICertifiedRecord=} [p] Properties to set - */ - function CertifiedRecord(p) { - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * CertifiedRecord seq. - * @member {number} seq - * @memberof Addresses.CertifiedRecord - * @instance - */ - CertifiedRecord.prototype.seq = $util.Long ? $util.Long.fromBits(0,0,true) : 0; - - /** - * CertifiedRecord raw. - * @member {Uint8Array} raw - * @memberof Addresses.CertifiedRecord - * @instance - */ - CertifiedRecord.prototype.raw = $util.newBuffer([]); - - /** - * Encodes the specified CertifiedRecord message. Does not implicitly {@link Addresses.CertifiedRecord.verify|verify} messages. - * @function encode - * @memberof Addresses.CertifiedRecord - * @static - * @param {Addresses.ICertifiedRecord} m CertifiedRecord message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - CertifiedRecord.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.seq != null && Object.hasOwnProperty.call(m, "seq")) - w.uint32(8).uint64(m.seq); - if (m.raw != null && Object.hasOwnProperty.call(m, "raw")) - w.uint32(18).bytes(m.raw); - return w; - }; - - /** - * Decodes a CertifiedRecord message from the specified reader or buffer. - * @function decode - * @memberof Addresses.CertifiedRecord - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {Addresses.CertifiedRecord} CertifiedRecord - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - CertifiedRecord.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.Addresses.CertifiedRecord(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.seq = r.uint64(); - break; - case 2: - m.raw = r.bytes(); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a CertifiedRecord message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof Addresses.CertifiedRecord - * @static - * @param {Object.} d Plain object - * @returns {Addresses.CertifiedRecord} CertifiedRecord - */ - CertifiedRecord.fromObject = function fromObject(d) { - if (d instanceof $root.Addresses.CertifiedRecord) - return d; - var m = new $root.Addresses.CertifiedRecord(); - if (d.seq != null) { - if ($util.Long) - (m.seq = $util.Long.fromValue(d.seq)).unsigned = true; - else if (typeof d.seq === "string") - m.seq = parseInt(d.seq, 10); - else if (typeof d.seq === "number") - m.seq = d.seq; - else if (typeof d.seq === "object") - m.seq = new $util.LongBits(d.seq.low >>> 0, d.seq.high >>> 0).toNumber(true); - } - if (d.raw != null) { - if (typeof d.raw === "string") - $util.base64.decode(d.raw, m.raw = $util.newBuffer($util.base64.length(d.raw)), 0); - else if (d.raw.length) - m.raw = d.raw; - } - return m; - }; - - /** - * Creates a plain object from a CertifiedRecord message. Also converts values to other types if specified. - * @function toObject - * @memberof Addresses.CertifiedRecord - * @static - * @param {Addresses.CertifiedRecord} m CertifiedRecord - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - CertifiedRecord.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.defaults) { - if ($util.Long) { - var n = new $util.Long(0, 0, true); - d.seq = o.longs === String ? n.toString() : o.longs === Number ? n.toNumber() : n; - } else - d.seq = o.longs === String ? "0" : 0; - if (o.bytes === String) - d.raw = ""; - else { - d.raw = []; - if (o.bytes !== Array) - d.raw = $util.newBuffer(d.raw); - } - } - if (m.seq != null && m.hasOwnProperty("seq")) { - if (typeof m.seq === "number") - d.seq = o.longs === String ? String(m.seq) : m.seq; - else - d.seq = o.longs === String ? $util.Long.prototype.toString.call(m.seq) : o.longs === Number ? new $util.LongBits(m.seq.low >>> 0, m.seq.high >>> 0).toNumber(true) : m.seq; - } - if (m.raw != null && m.hasOwnProperty("raw")) { - d.raw = o.bytes === String ? $util.base64.encode(m.raw, 0, m.raw.length) : o.bytes === Array ? Array.prototype.slice.call(m.raw) : m.raw; - } - return d; - }; - - /** - * Converts this CertifiedRecord to JSON. - * @function toJSON - * @memberof Addresses.CertifiedRecord - * @instance - * @returns {Object.} JSON object - */ - CertifiedRecord.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return CertifiedRecord; - })(); - - return Addresses; -})(); - -module.exports = $root; diff --git a/src/peer-store/persistent/pb/address-book.proto b/src/peer-store/persistent/pb/address-book.proto deleted file mode 100644 index d154bf0de7..0000000000 --- a/src/peer-store/persistent/pb/address-book.proto +++ /dev/null @@ -1,27 +0,0 @@ -syntax = "proto3"; - -message Addresses { - // Address represents a single multiaddr. - message Address { - bytes multiaddr = 1; - - // Flag to indicate if the address comes from a certified source. - optional bool isCertified = 2; - } - - // CertifiedRecord contains a serialized signed PeerRecord used to - // populate the signedAddrs list. - message CertifiedRecord { - // The Seq counter from the signed PeerRecord envelope - uint64 seq = 1; - - // The serialized bytes of the SignedEnvelope containing the PeerRecord. - bytes raw = 2; - } - - // The known multiaddrs. - repeated Address addrs = 1; - - // The most recently received signed PeerRecord. - CertifiedRecord certified_record = 2; -} \ No newline at end of file diff --git a/src/peer-store/persistent/pb/proto-book.d.ts b/src/peer-store/persistent/pb/proto-book.d.ts deleted file mode 100644 index f3590f878c..0000000000 --- a/src/peer-store/persistent/pb/proto-book.d.ts +++ /dev/null @@ -1,59 +0,0 @@ -import * as $protobuf from "protobufjs"; -/** Properties of a Protocols. */ -export interface IProtocols { - - /** Protocols protocols */ - protocols?: (string[]|null); -} - -/** Represents a Protocols. */ -export class Protocols implements IProtocols { - - /** - * Constructs a new Protocols. - * @param [p] Properties to set - */ - constructor(p?: IProtocols); - - /** Protocols protocols. */ - public protocols: string[]; - - /** - * Encodes the specified Protocols message. Does not implicitly {@link Protocols.verify|verify} messages. - * @param m Protocols message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: IProtocols, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a Protocols message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns Protocols - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Protocols; - - /** - * Creates a Protocols message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns Protocols - */ - public static fromObject(d: { [k: string]: any }): Protocols; - - /** - * Creates a plain object from a Protocols message. Also converts values to other types if specified. - * @param m Protocols - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: Protocols, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this Protocols to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; -} diff --git a/src/peer-store/persistent/pb/proto-book.js b/src/peer-store/persistent/pb/proto-book.js deleted file mode 100644 index fc3633ddd7..0000000000 --- a/src/peer-store/persistent/pb/proto-book.js +++ /dev/null @@ -1,157 +0,0 @@ -/*eslint-disable*/ -"use strict"; - -var $protobuf = require("protobufjs/minimal"); - -// Common aliases -var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; - -// Exported root namespace -var $root = $protobuf.roots["libp2p-proto-book"] || ($protobuf.roots["libp2p-proto-book"] = {}); - -$root.Protocols = (function() { - - /** - * Properties of a Protocols. - * @exports IProtocols - * @interface IProtocols - * @property {Array.|null} [protocols] Protocols protocols - */ - - /** - * Constructs a new Protocols. - * @exports Protocols - * @classdesc Represents a Protocols. - * @implements IProtocols - * @constructor - * @param {IProtocols=} [p] Properties to set - */ - function Protocols(p) { - this.protocols = []; - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * Protocols protocols. - * @member {Array.} protocols - * @memberof Protocols - * @instance - */ - Protocols.prototype.protocols = $util.emptyArray; - - /** - * Encodes the specified Protocols message. Does not implicitly {@link Protocols.verify|verify} messages. - * @function encode - * @memberof Protocols - * @static - * @param {IProtocols} m Protocols message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Protocols.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.protocols != null && m.protocols.length) { - for (var i = 0; i < m.protocols.length; ++i) - w.uint32(10).string(m.protocols[i]); - } - return w; - }; - - /** - * Decodes a Protocols message from the specified reader or buffer. - * @function decode - * @memberof Protocols - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {Protocols} Protocols - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Protocols.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.Protocols(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - if (!(m.protocols && m.protocols.length)) - m.protocols = []; - m.protocols.push(r.string()); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a Protocols message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof Protocols - * @static - * @param {Object.} d Plain object - * @returns {Protocols} Protocols - */ - Protocols.fromObject = function fromObject(d) { - if (d instanceof $root.Protocols) - return d; - var m = new $root.Protocols(); - if (d.protocols) { - if (!Array.isArray(d.protocols)) - throw TypeError(".Protocols.protocols: array expected"); - m.protocols = []; - for (var i = 0; i < d.protocols.length; ++i) { - m.protocols[i] = String(d.protocols[i]); - } - } - return m; - }; - - /** - * Creates a plain object from a Protocols message. Also converts values to other types if specified. - * @function toObject - * @memberof Protocols - * @static - * @param {Protocols} m Protocols - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - Protocols.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.arrays || o.defaults) { - d.protocols = []; - } - if (m.protocols && m.protocols.length) { - d.protocols = []; - for (var j = 0; j < m.protocols.length; ++j) { - d.protocols[j] = m.protocols[j]; - } - } - return d; - }; - - /** - * Converts this Protocols to JSON. - * @function toJSON - * @memberof Protocols - * @instance - * @returns {Object.} JSON object - */ - Protocols.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return Protocols; -})(); - -module.exports = $root; diff --git a/src/peer-store/persistent/pb/proto-book.proto b/src/peer-store/persistent/pb/proto-book.proto deleted file mode 100644 index a452f0caf9..0000000000 --- a/src/peer-store/persistent/pb/proto-book.proto +++ /dev/null @@ -1,5 +0,0 @@ -syntax = "proto3"; - -message Protocols { - repeated string protocols = 1; -} \ No newline at end of file diff --git a/src/peer-store/proto-book.js b/src/peer-store/proto-book.js index 3ce6d306e0..686957ec7a 100644 --- a/src/peer-store/proto-book.js +++ b/src/peer-store/proto-book.js @@ -1,171 +1,237 @@ 'use strict' const debug = require('debug') -const log = Object.assign(debug('libp2p:peer-store:proto-book'), { - error: debug('libp2p:peer-store:proto-book:err') -}) const errcode = require('err-code') +const { codes } = require('../errors') const PeerId = require('peer-id') -const Book = require('./book') - -const { - codes: { ERR_INVALID_PARAMETERS } -} = require('../errors') - /** - * @typedef {import('./')} PeerStore + * @typedef {import('./types').PeerStore} PeerStore + * @typedef {import('./types').ProtoBook} ProtoBook */ +const log = Object.assign(debug('libp2p:peer-store:proto-book'), { + error: debug('libp2p:peer-store:proto-book:err') +}) + +const EVENT_NAME = 'change:protocols' + /** - * @extends {Book} - * - * @fires ProtoBook#change:protocols + * @implements {ProtoBook} */ -class ProtoBook extends Book { +class PersistentProtoBook { /** - * The ProtoBook is responsible for keeping the known supported - * protocols of a peer. - * - * @class - * @param {PeerStore} peerStore + * @param {PeerStore["emit"]} emit + * @param {import('./types').Store} store */ - constructor (peerStore) { - /** - * PeerStore Event emitter, used by the ProtoBook to emit: - * "change:protocols" - emitted when the known protocols of a peer change. - */ - super({ - peerStore, - eventName: 'change:protocols', - eventProperty: 'protocols', - eventTransformer: (data) => Array.from(data) - }) - - /** - * Map known peers to their known protocols. - * - * @type {Map>} - */ - this.data = new Map() + constructor (emit, store) { + this._emit = emit + this._store = store + } + + /** + * @param {PeerId} peerId + */ + async get (peerId) { + log('get wait for read lock') + const release = await this._store.lock.readLock() + log('get got read lock') + + try { + const peer = await this._store.load(peerId) + + return peer.protocols + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } finally { + log('get release read lock') + release() + } + + return [] } /** - * Set known protocols of a provided peer. - * If the peer was not known before, it will be added. - * - * @override * @param {PeerId} peerId * @param {string[]} protocols - * @returns {ProtoBook} */ - set (peerId, protocols) { + async set (peerId, protocols) { if (!PeerId.isPeerId(peerId)) { log.error('peerId must be an instance of peer-id to store data') - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } - if (!protocols) { + if (!Array.isArray(protocols)) { log.error('protocols must be provided to store data') - throw errcode(new Error('protocols must be provided'), ERR_INVALID_PARAMETERS) + throw errcode(new Error('protocols must be provided'), codes.ERR_INVALID_PARAMETERS) } - const id = peerId.toB58String() - const recSet = this.data.get(id) - const newSet = new Set(protocols) - - /** - * @param {Set} a - * @param {Set} b - */ - const isSetEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value)) - - // Already knows the peer and the recorded protocols are the same? - // If yes, no changes needed! - if (recSet && isSetEqual(recSet, newSet)) { - log(`the protocols provided to store are equal to the already stored for ${id}`) - return this - } + log('set await write lock') + const release = await this._store.lock.writeLock() + log('set got write lock') + + let updatedPeer + + try { + try { + const peer = await this._store.load(peerId) + + if (new Set([ + ...protocols + ]).size === peer.protocols.length) { + return + } + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } + + updatedPeer = await this._store.patchOrCreate(peerId, { + protocols + }) - this._setData(peerId, newSet) - log(`stored provided protocols for ${id}`) + log(`stored provided protocols for ${peerId.toB58String()}`) + } finally { + log('set release write lock') + release() + } - return this + this._emit(EVENT_NAME, { peerId, protocols: updatedPeer.protocols }) } /** - * Adds known protocols of a provided peer. - * If the peer was not known before, it will be added. - * * @param {PeerId} peerId * @param {string[]} protocols - * @returns {ProtoBook} */ - add (peerId, protocols) { + async add (peerId, protocols) { if (!PeerId.isPeerId(peerId)) { log.error('peerId must be an instance of peer-id to store data') - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } - if (!protocols) { + if (!Array.isArray(protocols)) { log.error('protocols must be provided to store data') - throw errcode(new Error('protocols must be provided'), ERR_INVALID_PARAMETERS) + throw errcode(new Error('protocols must be provided'), codes.ERR_INVALID_PARAMETERS) } - const id = peerId.toB58String() - const recSet = this.data.get(id) || new Set() - const newSet = new Set([...recSet, ...protocols]) // Set Union + log('add await write lock') + const release = await this._store.lock.writeLock() + log('add got write lock') + + let updatedPeer + + try { + try { + const peer = await this._store.load(peerId) + + if (new Set([ + ...peer.protocols, + ...protocols + ]).size === peer.protocols.length) { + return + } + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } - // Any new protocol added? - if (recSet.size === newSet.size) { - log(`the protocols provided to store are already stored for ${id}`) - return this - } + updatedPeer = await this._store.mergeOrCreate(peerId, { + protocols + }) - this._setData(peerId, newSet) - log(`added provided protocols for ${id}`) + log(`added provided protocols for ${peerId.toB58String()}`) + } finally { + log('add release write lock') + release() + } - return this + this._emit(EVENT_NAME, { peerId, protocols: updatedPeer.protocols }) } /** - * Removes known protocols of a provided peer. - * If the protocols did not exist before, nothing will be done. - * * @param {PeerId} peerId * @param {string[]} protocols - * @returns {ProtoBook} */ - remove (peerId, protocols) { + async 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) + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } - if (!protocols) { + if (!Array.isArray(protocols)) { log.error('protocols must be provided to store data') - throw errcode(new Error('protocols must be provided'), ERR_INVALID_PARAMETERS) + throw errcode(new Error('protocols must be provided'), codes.ERR_INVALID_PARAMETERS) } - const id = peerId.toB58String() - const recSet = this.data.get(id) + log('remove await write lock') + const release = await this._store.lock.writeLock() + log('remove got write lock') + + let updatedPeer + + try { + try { + const peer = await this._store.load(peerId) + const protocolSet = new Set(peer.protocols) + + for (const protocol of protocols) { + protocolSet.delete(protocol) + } - if (recSet) { - const newSet = new Set([ - ...recSet - ].filter((p) => !protocols.includes(p))) + if (peer.protocols.length === protocolSet.size) { + return + } - // Any protocol removed? - if (recSet.size === newSet.size) { - return this + protocols = Array.from(protocolSet) + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } } - this._setData(peerId, newSet) - log(`removed provided protocols for ${id}`) + updatedPeer = await this._store.patchOrCreate(peerId, { + protocols + }) + } finally { + log('remove release write lock') + release() } - return this + this._emit(EVENT_NAME, { peerId, protocols: updatedPeer.protocols }) + } + + /** + * @param {PeerId} peerId + */ + async delete (peerId) { + log('delete await write lock') + const release = await this._store.lock.writeLock() + log('delete got write lock') + let has + + try { + has = await this._store.has(peerId) + + await this._store.patchOrCreate(peerId, { + protocols: [] + }) + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } finally { + log('delete release write lock') + release() + } + + if (has) { + this._emit(EVENT_NAME, { peerId, protocols: [] }) + } } } -module.exports = ProtoBook +module.exports = PersistentProtoBook diff --git a/src/peer-store/store.js b/src/peer-store/store.js new file mode 100644 index 0000000000..8b3d29f429 --- /dev/null +++ b/src/peer-store/store.js @@ -0,0 +1,250 @@ +'use strict' + +const debug = require('debug') +const PeerId = require('peer-id') +const errcode = require('err-code') +const { codes } = require('../errors') +const { Key } = require('interface-datastore/key') +const { base32 } = require('multiformats/bases/base32') +const { keys: { unmarshalPublicKey, marshalPublicKey } } = require('libp2p-crypto') +const { Multiaddr } = require('multiaddr') +const { Peer: PeerPB } = require('./pb/peer') +// @ts-expect-error no types +const mortice = require('mortice') +const { equals: uint8arrayEquals } = require('uint8arrays/equals') + +const log = Object.assign(debug('libp2p:peer-store:store'), { + error: debug('libp2p:peer-store:store:err') +}) + +/** + * @typedef {import('./types').PeerStore} PeerStore + * @typedef {import('./types').EventName} EventName + * @typedef {import('./types').Peer} Peer + */ + +const NAMESPACE_COMMON = '/peers/' + +class PersistentStore { + /** + * @param {import('interface-datastore').Datastore} datastore + */ + constructor (datastore) { + this._datastore = datastore + this.lock = mortice('peer-store', { + singleProcess: true + }) + } + + /** + * @param {PeerId} peerId + * @returns {Key} + */ + _peerIdToDatastoreKey (peerId) { + 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'), codes.ERR_INVALID_PARAMETERS) + } + + const b32key = peerId.toString() + return new Key(`${NAMESPACE_COMMON}${b32key}`) + } + + /** + * @param {PeerId} peerId + */ + async has (peerId) { + return this._datastore.has(this._peerIdToDatastoreKey(peerId)) + } + + /** + * @param {PeerId} peerId + */ + async delete (peerId) { + await this._datastore.delete(this._peerIdToDatastoreKey(peerId)) + } + + /** + * @param {PeerId} peerId + * @returns {Promise} peer + */ + async load (peerId) { + const buf = await this._datastore.get(this._peerIdToDatastoreKey(peerId)) + const peer = PeerPB.decode(buf) + const pubKey = peer.pubKey ? unmarshalPublicKey(peer.pubKey) : peerId.pubKey + const metadata = new Map() + + for (const meta of peer.metadata) { + metadata.set(meta.key, meta.value) + } + + return { + ...peer, + id: peerId, + pubKey, + addresses: peer.addresses.map(({ multiaddr, isCertified }) => ({ + multiaddr: new Multiaddr(multiaddr), + isCertified: isCertified || false + })), + metadata, + peerRecordEnvelope: peer.peerRecordEnvelope || undefined + } + } + + /** + * @param {Peer} peer + */ + async save (peer) { + if (peer.pubKey != null && peer.id.pubKey != null && !uint8arrayEquals(peer.pubKey.bytes, peer.id.pubKey.bytes)) { + log.error('peer publicKey bytes do not match peer id publicKey bytes') + throw errcode(new Error('publicKey bytes do not match peer id publicKey bytes'), codes.ERR_INVALID_PARAMETERS) + } + + const buf = PeerPB.encode({ + addresses: peer.addresses.sort((a, b) => { + return a.multiaddr.toString().localeCompare(b.multiaddr.toString()) + }).map(({ multiaddr, isCertified }) => ({ + multiaddr: multiaddr.bytes, + isCertified + })), + protocols: peer.protocols.sort(), + pubKey: peer.pubKey ? marshalPublicKey(peer.pubKey) : undefined, + metadata: [...peer.metadata.keys()].sort().map(key => ({ key, value: peer.metadata.get(key) })), + peerRecordEnvelope: peer.peerRecordEnvelope + }).finish() + + await this._datastore.put(this._peerIdToDatastoreKey(peer.id), buf) + + return this.load(peer.id) + } + + /** + * @param {PeerId} peerId + * @param {Partial} data + */ + async patch (peerId, data) { + const peer = await this.load(peerId) + + return await this._patch(peerId, data, peer) + } + + /** + * @param {PeerId} peerId + * @param {Partial} data + */ + async patchOrCreate (peerId, data) { + /** @type {Peer} */ + let peer + + try { + peer = await this.load(peerId) + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + + peer = { id: peerId, addresses: [], protocols: [], metadata: new Map() } + } + + return await this._patch(peerId, data, peer) + } + + /** + * @param {PeerId} peerId + * @param {Partial} data + * @param {Peer} peer + */ + async _patch (peerId, data, peer) { + return await this.save({ + ...peer, + ...data, + id: peerId + }) + } + + /** + * @param {PeerId} peerId + * @param {Partial} data + */ + async merge (peerId, data) { + const peer = await this.load(peerId) + + return this._merge(peerId, data, peer) + } + + /** + * @param {PeerId} peerId + * @param {Partial} data + */ + async mergeOrCreate (peerId, data) { + /** @type {Peer} */ + let peer + + try { + peer = await this.load(peerId) + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + + peer = { id: peerId, addresses: [], protocols: [], metadata: new Map() } + } + + return await this._merge(peerId, data, peer) + } + + /** + * @param {PeerId} peerId + * @param {Partial} data + * @param {Peer} peer + */ + async _merge (peerId, data, peer) { + // if the peer has certified addresses, use those in + // favour of the supplied versions + /** @type {Map} */ + const addresses = new Map() + + ;(data.addresses || []).forEach(addr => { + addresses.set(addr.multiaddr.toString(), addr.isCertified) + }) + + peer.addresses.forEach(({ multiaddr, isCertified }) => { + const addrStr = multiaddr.toString() + addresses.set(addrStr, Boolean(addresses.get(addrStr) || isCertified)) + }) + + return await this.save({ + id: peerId, + addresses: Array.from(addresses.entries()).map(([addrStr, isCertified]) => { + return { + multiaddr: new Multiaddr(addrStr), + isCertified + } + }), + protocols: Array.from(new Set([ + ...(peer.protocols || []), + ...(data.protocols || []) + ])), + metadata: new Map([ + ...(peer.metadata ? peer.metadata.entries() : []), + ...(data.metadata ? data.metadata.entries() : []) + ]), + pubKey: data.pubKey || (peer != null ? peer.pubKey : undefined), + peerRecordEnvelope: data.peerRecordEnvelope || (peer != null ? peer.peerRecordEnvelope : undefined) + }) + } + + async * all () { + for await (const key of this._datastore.queryKeys({ + prefix: NAMESPACE_COMMON + })) { + // /peers/${peer-id-as-libp2p-key-cid-string-in-base-32} + const base32Str = key.toString().split('/')[2] + const buf = base32.decode(base32Str) + + yield this.load(PeerId.createFromBytes(buf)) + } + } +} + +module.exports = PersistentStore diff --git a/src/peer-store/types.ts b/src/peer-store/types.ts new file mode 100644 index 0000000000..0ae16b460d --- /dev/null +++ b/src/peer-store/types.ts @@ -0,0 +1,245 @@ +import type PeerId from 'peer-id' +import type { Multiaddr } from 'multiaddr' +import type Envelope from '../record/envelope' +import type { PublicKey } from 'libp2p-interfaces/src/keys/types' + +export interface Address { + /** + * Peer multiaddr + */ + multiaddr: Multiaddr + + /** + * Obtained from a signed peer record + */ + isCertified: boolean +} + +export interface Peer { + /** + * Peer's peer-id instance + */ + id: PeerId + + /** + * Peer's addresses containing its multiaddrs and metadata + */ + addresses: Address[] + + /** + * Peer's supported protocols + */ + protocols: string[] + + /** + * Peer's metadata map + */ + metadata: Map + + /** + * May be set if the key that this Peer has is an RSA key + */ + pubKey?: PublicKey + + /** + * The last peer record envelope received + */ + peerRecordEnvelope?: Uint8Array +} + +export interface CertifiedRecord { + raw: Uint8Array + seqNumber: number +} + +export interface AddressBookEntry { + addresses: Address[] + record: CertifiedRecord +} + +export interface Book { + /** + * Get the known data of a peer + */ + get: (peerId: PeerId) => Promise + + /** + * Set the known data of a peer + */ + set: (peerId: PeerId, data: Type) => Promise + + /** + * Remove the known data of a peer + */ + delete: (peerId: PeerId) => Promise +} + +/** + * AddressBook containing a map of peerIdStr to Address. + */ +export interface AddressBook { + /** + * 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 + */ + consumePeerRecord: (envelope: Envelope) => Promise + + /** + * Get the raw Envelope for a peer. Returns + * undefined if no Envelope is found + */ + getRawEnvelope: (peerId: PeerId) => Promise + + /** + * Get an Envelope containing a PeerRecord for the given peer. + * Returns undefined if no record exists. + */ + getPeerRecord: (peerId: PeerId) => Promise + + /** + * Add known addresses of a provided peer. + * If the peer is not known, it is set with the given addresses. + */ + add: (peerId: PeerId, multiaddrs: Multiaddr[]) => Promise + + /** + * Set the known addresses of a peer + */ + set: (peerId: PeerId, data: Multiaddr[]) => Promise + + /** + * Return the known addresses of a peer + */ + get: (peerId: PeerId) => Promise + + /** + * Get the known multiaddrs for a given peer. All returned multiaddrs + * will include the encapsulated `PeerId` of the peer. + */ + getMultiaddrsForPeer: (peerId: PeerId, addressSorter?: (ms: Address[]) => Address[]) => Promise +} + +/** + * KeyBook containing a map of peerIdStr to their PeerId with public keys. + */ +export interface KeyBook { + /** + * Get the known data of a peer + */ + get: (peerId: PeerId) => Promise + + /** + * Set the known data of a peer + */ + set: (peerId: PeerId, data: PublicKey) => Promise + + /** + * Remove the known data of a peer + */ + delete: (peerId: PeerId) => Promise +} + +/** + * MetadataBook containing a map of peerIdStr to their metadata Map. + */ +export interface MetadataBook extends Book> { + /** + * Set a specific metadata value + */ + setValue: (peerId: PeerId, key: string, value: Uint8Array) => Promise + + /** + * Get specific metadata value, if it exists + */ + getValue: (peerId: PeerId, key: string) => Promise + + /** + * Deletes the provided peer metadata key from the book + */ + deleteValue: (peerId: PeerId, key: string) => Promise +} + +/** + * ProtoBook containing a map of peerIdStr to supported protocols. + */ +export interface ProtoBook extends Book { + /** + * Adds known protocols of a provided peer. + * If the peer was not known before, it will be added. + */ + add: (peerId: PeerId, protocols: string[]) => Promise + + /** + * Removes known protocols of a provided peer. + * If the protocols did not exist before, nothing will be done. + */ + remove: (peerId: PeerId, protocols: string[]) => Promise +} + +export interface PeerProtocolsChangeEvent { + peerId: PeerId + protocols: string[] +} + +export interface PeerMultiaddrsChangeEvent { + peerId: PeerId + multiaddrs: Multiaddr[] +} + +export interface PeerPublicKeyChangeEvent { + peerId: PeerId + pubKey?: PublicKey +} + +export interface PeerMetadataChangeEvent { + peerId: PeerId + metadata: Map +} + +export type EventName = 'peer' | 'change:protocols' | 'change:multiaddrs' | 'change:pubkey' | 'change:metadata' + +export interface PeerStoreEvents { + 'peer': (event: PeerId) => void + 'change:protocols': (event: PeerProtocolsChangeEvent) => void + 'change:multiaddrs': (event: PeerMultiaddrsChangeEvent) => void + 'change:pubkey': (event: PeerPublicKeyChangeEvent) => void + 'change:metadata': (event: PeerMetadataChangeEvent) => void +} + +export interface PeerStore { + addressBook: AddressBook + keyBook: KeyBook + metadataBook: MetadataBook + protoBook: ProtoBook + + getPeers: () => AsyncIterable + delete: (peerId: PeerId) => Promise + has: (peerId: PeerId) => Promise + get: (peerId: PeerId) => Promise + on: ( + event: U, listener: PeerStoreEvents[U] + ) => this + once: ( + event: U, listener: PeerStoreEvents[U] + ) => this + emit: ( + event: U, ...args: Parameters + ) => boolean +} + +export interface Store { + has: (peerId: PeerId) => Promise + save: (peer: Peer) => Promise + load: (peerId: PeerId) => Promise + merge: (peerId: PeerId, data: Partial) => Promise + mergeOrCreate: (peerId: PeerId, data: Partial) => Promise + patch: (peerId: PeerId, data: Partial) => Promise + patchOrCreate: (peerId: PeerId, data: Partial) => Promise + all: () => AsyncIterable + + lock: { + readLock: () => Promise<() => void> + writeLock: () => Promise<() => void> + } +} diff --git a/src/ping/index.js b/src/ping/index.js index 15b2692399..75e13b9a53 100644 --- a/src/ping/index.js +++ b/src/ping/index.js @@ -63,6 +63,9 @@ async function ping (node, peer) { */ function mount (node) { node.handle(`/${node._config.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}`, ({ stream }) => pipe(stream, stream)) + .catch(err => { + log.error(err) + }) } /** @@ -72,6 +75,9 @@ function mount (node) { */ function unmount (node) { node.unhandle(`/${node._config.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}`) + .catch(err => { + log.error(err) + }) } exports = module.exports = ping diff --git a/src/record/peer-record/index.js b/src/record/peer-record/index.js index dcdc7b6285..bcf637b87a 100644 --- a/src/record/peer-record/index.js +++ b/src/record/peer-record/index.js @@ -11,7 +11,7 @@ const { } = require('./consts') /** - * @typedef {import('../../peer-store/address-book.js').Address} Address + * @typedef {import('../../peer-store/types').Address} Address * @typedef {import('libp2p-interfaces/src/record/types').Record} Record */ diff --git a/src/record/utils.js b/src/record/utils.js index 0a92ade177..512d62a258 100644 --- a/src/record/utils.js +++ b/src/record/utils.js @@ -19,7 +19,7 @@ async function updateSelfPeerRecord (libp2p) { multiaddrs: libp2p.multiaddrs }) const envelope = await Envelope.seal(peerRecord, libp2p.peerId) - libp2p.peerStore.addressBook.consumePeerRecord(envelope) + await libp2p.peerStore.addressBook.consumePeerRecord(envelope) } module.exports.updateSelfPeerRecord = updateSelfPeerRecord diff --git a/src/registrar.js b/src/registrar.js index 2a2f9f4084..812c76011e 100644 --- a/src/registrar.js +++ b/src/registrar.js @@ -13,7 +13,7 @@ const Topology = require('libp2p-interfaces/src/topology') /** * @typedef {import('peer-id')} PeerId - * @typedef {import('./peer-store')} PeerStore + * @typedef {import('./peer-store/types').PeerStore} PeerStore * @typedef {import('./connection-manager')} ConnectionManager * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection * @typedef {import('./').HandlerProps} HandlerProps @@ -82,9 +82,9 @@ class Registrar { * Register handlers for a set of multicodecs given * * @param {Topology} topology - protocol topology - * @returns {string} registrar identifier + * @returns {Promise} registrar identifier */ - register (topology) { + async register (topology) { if (!Topology.isTopology(topology)) { log.error('topology must be an instance of interfaces/topology') throw errcode(new Error('topology must be an instance of interfaces/topology'), ERR_INVALID_PARAMETERS) @@ -96,7 +96,7 @@ class Registrar { this.topologies.set(id, topology) // Set registrar - topology.registrar = this + await topology.setRegistrar(this) return id } diff --git a/src/upgrader.js b/src/upgrader.js index 23f9a338b9..8e28b71528 100644 --- a/src/upgrader.js +++ b/src/upgrader.js @@ -288,7 +288,9 @@ class Upgrader { } finally { this.onConnectionEnd(connection) } - })() + })().catch(err => { + log.error(err) + }) } return Reflect.set(...args) diff --git a/test/configuration/protocol-prefix.node.js b/test/configuration/protocol-prefix.node.js index 6a718e6579..aff8579ea0 100644 --- a/test/configuration/protocol-prefix.node.js +++ b/test/configuration/protocol-prefix.node.js @@ -10,6 +10,10 @@ const { baseOptions } = require('./utils') describe('Protocol prefix is configurable', () => { let libp2p + afterEach(async () => { + libp2p && await libp2p.stop() + }) + it('protocolPrefix is provided', async () => { const testProtocol = 'test-protocol' libp2p = await create(mergeOptions(baseOptions, { @@ -17,31 +21,27 @@ describe('Protocol prefix is configurable', () => { protocolPrefix: testProtocol } })) + await libp2p.start() - const protocols = libp2p.peerStore.protoBook.get(libp2p.peerId); - [ + const protocols = await libp2p.peerStore.protoBook.get(libp2p.peerId) + expect(protocols).to.include.members([ '/libp2p/circuit/relay/0.1.0', `/${testProtocol}/id/1.0.0`, `/${testProtocol}/id/push/1.0.0`, `/${testProtocol}/ping/1.0.0` - ].forEach((i, idx) => { - expect(protocols[idx]).equals(i) - }) - await libp2p.stop() + ]) }) it('protocolPrefix is not provided', async () => { libp2p = await create(baseOptions) + await libp2p.start() - const protocols = libp2p.peerStore.protoBook.get(libp2p.peerId); - [ + const protocols = await libp2p.peerStore.protoBook.get(libp2p.peerId) + expect(protocols).to.include.members([ '/libp2p/circuit/relay/0.1.0', '/ipfs/id/1.0.0', '/ipfs/id/push/1.0.0', '/ipfs/ping/1.0.0' - ].forEach((i, idx) => { - expect(protocols[idx]).equals(i) - }) - await libp2p.stop() + ]) }) }) diff --git a/test/connection-manager/index.node.js b/test/connection-manager/index.node.js index aae0b3462b..bcd381b23c 100644 --- a/test/connection-manager/index.node.js +++ b/test/connection-manager/index.node.js @@ -71,7 +71,7 @@ describe('Connection Manager', () => { sinon.spy(libp2p.connectionManager, 'emit') sinon.spy(remoteLibp2p.connectionManager, 'emit') - libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) + await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) await libp2p.dial(remoteLibp2p.peerId) // check connect event @@ -219,9 +219,9 @@ describe('libp2p.connections', () => { }) // 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.peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs) + await libp2p.peerStore.addressBook.set(nodes[1].peerId, nodes[1].multiaddrs) + await libp2p.peerStore.protoBook.set(nodes[1].peerId, ['/protocol-min-conns']) await libp2p.start() diff --git a/test/connection-manager/index.spec.js b/test/connection-manager/index.spec.js index 77e0934c64..d400f54b28 100644 --- a/test/connection-manager/index.spec.js +++ b/test/connection-manager/index.spec.js @@ -77,7 +77,7 @@ describe('Connection Manager', () => { const value = Math.random() spies.set(value, spy) libp2p.connectionManager.setPeerValue(connection.remotePeer, value) - libp2p.connectionManager.onConnect(connection) + await libp2p.connectionManager.onConnect(connection) })) // get the lowest value @@ -109,8 +109,8 @@ describe('Connection Manager', () => { const spy = sinon.spy() await Promise.all([...new Array(max + 1)].map(async () => { const connection = await mockConnection() - sinon.stub(connection, 'close').callsFake(() => spy()) // eslint-disable-line - libp2p.connectionManager.onConnect(connection) + sinon.stub(connection, 'close').callsFake(async () => spy()) // eslint-disable-line + await libp2p.connectionManager.onConnect(connection) })) expect(libp2p.connectionManager._maybeDisconnectOne).to.have.property('callCount', 1) diff --git a/test/content-routing/content-routing.node.js b/test/content-routing/content-routing.node.js index e35651e4c6..694d71cf61 100644 --- a/test/content-routing/content-routing.node.js +++ b/test/content-routing/content-routing.node.js @@ -291,11 +291,11 @@ describe('content-routing', () => { yield result }) - expect(node.peerStore.addressBook.get(providerPeerId)).to.not.be.ok() + expect(await node.peerStore.has(providerPeerId)).to.not.be.ok() await drain(node.contentRouting.findProviders('a cid')) - expect(node.peerStore.addressBook.get(providerPeerId)).to.deep.include({ + expect(await node.peerStore.addressBook.get(providerPeerId)).to.deep.include({ isCertified: false, multiaddr: result.multiaddrs[0] }) @@ -377,7 +377,7 @@ describe('content-routing', () => { await drain(node.contentRouting.findProviders('a cid')) - expect(node.peerStore.addressBook.get(providerPeerId)).to.deep.include({ + expect(await node.peerStore.addressBook.get(providerPeerId)).to.deep.include({ isCertified: false, multiaddr: result1.multiaddrs[0] }).and.to.deep.include({ diff --git a/test/content-routing/dht/operation.node.js b/test/content-routing/dht/operation.node.js index 646e843176..e6662b2084 100644 --- a/test/content-routing/dht/operation.node.js +++ b/test/content-routing/dht/operation.node.js @@ -45,8 +45,8 @@ describe('DHT subsystem operates correctly', () => { remoteLibp2p.start() ]) - libp2p.peerStore.addressBook.set(remotePeerId, [remoteListenAddr]) - remAddr = libp2p.peerStore.addressBook.getMultiaddrsForPeer(remotePeerId)[0] + await libp2p.peerStore.addressBook.set(remotePeerId, [remoteListenAddr]); + [remAddr] = await libp2p.peerStore.addressBook.getMultiaddrsForPeer(remotePeerId) }) afterEach(() => Promise.all([ @@ -106,8 +106,8 @@ describe('DHT subsystem operates correctly', () => { await libp2p.start() await remoteLibp2p.start() - libp2p.peerStore.addressBook.set(remotePeerId, [remoteListenAddr]) - remAddr = libp2p.peerStore.addressBook.getMultiaddrsForPeer(remotePeerId)[0] + await libp2p.peerStore.addressBook.set(remotePeerId, [remoteListenAddr]) + remAddr = (await libp2p.peerStore.addressBook.getMultiaddrsForPeer(remotePeerId))[0] }) afterEach(() => Promise.all([ diff --git a/test/dialing/direct.node.js b/test/dialing/direct.node.js index 6db0cb6b90..0d88a397f0 100644 --- a/test/dialing/direct.node.js +++ b/test/dialing/direct.node.js @@ -18,7 +18,7 @@ const AggregateError = require('aggregate-error') const { Connection } = require('libp2p-interfaces/src/connection') const { AbortError } = require('libp2p-interfaces/src/transport/errors') const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') - +const { MemoryDatastore } = require('datastore-core/memory') const Libp2p = require('../../src') const Dialer = require('../../src/dialer') const AddressManager = require('../../src/address-manager') @@ -48,7 +48,10 @@ describe('Dialing (direct, TCP)', () => { PeerId.createFromJSON(Peers[1]) ]) - peerStore = new PeerStore({ peerId: remotePeerId }) + peerStore = new PeerStore({ + peerId: remotePeerId, + datastore: new MemoryDatastore() + }) remoteTM = new TransportManager({ libp2p: { addressManager: new AddressManager(remotePeerId, { listen: [listenAddr] }), @@ -62,7 +65,10 @@ describe('Dialing (direct, TCP)', () => { localTM = new TransportManager({ libp2p: { peerId: localPeerId, - peerStore: new PeerStore({ peerId: localPeerId }) + peerStore: new PeerStore({ + peerId: localPeerId, + datastore: new MemoryDatastore() + }) }, upgrader: mockUpgrader }) @@ -113,7 +119,10 @@ describe('Dialing (direct, TCP)', () => { it('should be able to connect to a given peer id', async () => { const peerId = await PeerId.createFromJSON(Peers[0]) - const peerStore = new PeerStore({ peerId }) + const peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) const dialer = new Dialer({ transportManager: localTM, peerStore @@ -249,7 +258,7 @@ describe('Dialing (direct, TCP)', () => { connEncryption: [Crypto] } }) - remoteLibp2p.handle('/echo/1.0.0', ({ stream }) => pipe(stream, stream)) + await remoteLibp2p.handle('/echo/1.0.0', ({ stream }) => pipe(stream, stream)) await remoteLibp2p.start() remoteAddr = remoteLibp2p.transportManager.getAddrs()[0].encapsulate(`/p2p/${remotePeerId.toB58String()}`) @@ -339,12 +348,12 @@ describe('Dialing (direct, TCP)', () => { }) // 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)) + await libp2p.handle('/stream-count/1', ({ stream }) => pipe(stream, stream)) + await libp2p.handle('/stream-count/2', ({ stream }) => pipe(stream, stream)) + await remoteLibp2p.handle('/stream-count/3', ({ stream }) => pipe(stream, stream)) + await remoteLibp2p.handle('/stream-count/4', ({ stream }) => pipe(stream, stream)) - libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) + await libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) const connection = await libp2p.dial(remotePeerId) // Create local to remote streams @@ -363,8 +372,8 @@ describe('Dialing (direct, TCP)', () => { // Verify stream count const remoteConn = remoteLibp2p.connectionManager.get(libp2p.peerId) - expect(connection.streams).to.have.length(5) - expect(remoteConn.streams).to.have.length(5) + expect(connection.streams).to.have.length(6) + expect(remoteConn.streams).to.have.length(6) // Close the connection and verify all streams have been closed await connection.close() diff --git a/test/dialing/direct.spec.js b/test/dialing/direct.spec.js index 1617322b8a..14beacb4f1 100644 --- a/test/dialing/direct.spec.js +++ b/test/dialing/direct.spec.js @@ -13,7 +13,7 @@ const { NOISE: Crypto } = require('@chainsafe/libp2p-noise') const { Multiaddr } = require('multiaddr') const AggregateError = require('aggregate-error') const { AbortError } = require('libp2p-interfaces/src/transport/errors') - +const { MemoryDatastore } = require('datastore-core/memory') const { codes: ErrorCodes } = require('../../src/errors') const Constants = require('../../src/constants') const Dialer = require('../../src/dialer') @@ -36,7 +36,10 @@ describe('Dialing (direct, WebSockets)', () => { before(async () => { [peerId] = await createPeerId() - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) localTM = new TransportManager({ libp2p: {}, upgrader: mockUpgrader, @@ -215,7 +218,7 @@ describe('Dialing (direct, WebSockets)', () => { }) // Inject data in the AddressBook - peerStore.addressBook.add(peerId, peerMultiaddrs) + await peerStore.addressBook.add(peerId, peerMultiaddrs) // Perform 3 multiaddr dials await dialer.connectToPeer(peerId) diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js index a0c9ba631e..88a61eeeda 100644 --- a/test/identify/index.spec.js +++ b/test/identify/index.spec.js @@ -10,7 +10,6 @@ const duplexPair = require('it-pair/duplex') const { Multiaddr } = require('multiaddr') const pWaitFor = require('p-wait-for') const { toString: unit8ArrayToString } = require('uint8arrays/to-string') - const { codes: Errors } = require('../../src/errors') const IdentifyService = require('../../src/identify') const multicodecs = IdentifyService.multicodecs @@ -22,7 +21,7 @@ const baseOptions = require('../utils/base-options.browser') const { updateSelfPeerRecord } = require('../../src/record/utils') const pkg = require('../../package.json') const AddressManager = require('../../src/address-manager') - +const { MemoryDatastore } = require('datastore-core/memory') const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser') const remoteAddr = MULTIADDRS_WEBSOCKETS[0] const listenMaddrs = [new Multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] @@ -38,10 +37,16 @@ describe('Identify', () => { PeerId.createFromJSON(Peers[1]) ])) - localPeerStore = new PeerStore({ peerId: localPeer }) + localPeerStore = new PeerStore({ + peerId: localPeer, + datastore: new MemoryDatastore() + }) localPeerStore.protoBook.set(localPeer, protocols) - remotePeerStore = new PeerStore({ peerId: remotePeer }) + remotePeerStore = new PeerStore({ + peerId: remotePeer, + datastore: new MemoryDatastore() + }) remotePeerStore.protoBook.set(remotePeer, protocols) localAddressManager = new AddressManager(localPeer) @@ -103,7 +108,7 @@ describe('Identify', () => { expect(localIdentify.peerStore.protoBook.set.callCount).to.equal(1) // Validate the remote peer gets updated in the peer store - const addresses = localIdentify.peerStore.addressBook.get(remotePeer) + const addresses = await localIdentify.peerStore.addressBook.get(remotePeer) expect(addresses).to.exist() expect(addresses).have.lengthOf(listenMaddrs.length) expect(addresses.map((a) => a.multiaddr)[0].equals(listenMaddrs[0])) @@ -149,7 +154,7 @@ describe('Identify', () => { sinon.spy(localIdentify.peerStore.addressBook, 'set') sinon.spy(localIdentify.peerStore.protoBook, 'set') - sinon.spy(localIdentify.peerStore.metadataBook, 'set') + sinon.spy(localIdentify.peerStore.metadataBook, 'setValue') // Run identify await Promise.all([ @@ -164,7 +169,7 @@ 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 + const metadataArgs = localIdentify.peerStore.metadataBook.setValue.firstCall.args expect(metadataArgs[0].id.bytes).to.equal(remotePeer.bytes) expect(metadataArgs[1]).to.equal('AgentVersion') expect(unit8ArrayToString(metadataArgs[2])).to.equal(agentVersion) @@ -221,13 +226,16 @@ describe('Identify', () => { .and.to.have.property('code', Errors.ERR_INVALID_PEER) }) - it('should store host data and protocol version into metadataBook', () => { + it('should store host data and protocol version into metadataBook', async () => { const agentVersion = 'js-project/1.0.0' - const peerStore = new PeerStore({ peerId: localPeer }) + const peerStore = new PeerStore({ + peerId: localPeer, + datastore: new MemoryDatastore() + }) - sinon.spy(peerStore.metadataBook, 'set') + sinon.spy(peerStore.metadataBook, 'setValue') - new IdentifyService({ // eslint-disable-line no-new + const service = new IdentifyService({ // eslint-disable-line no-new libp2p: { peerId: localPeer, connectionManager: new EventEmitter(), @@ -243,23 +251,30 @@ describe('Identify', () => { protocols }) - expect(peerStore.metadataBook.set.callCount).to.eql(2) + await service.start() + + expect(peerStore.metadataBook.setValue.callCount).to.eql(2) - const storedAgentVersion = peerStore.metadataBook.getValue(localPeer, 'AgentVersion') - const storedProtocolVersion = peerStore.metadataBook.getValue(localPeer, 'ProtocolVersion') + const storedAgentVersion = await peerStore.metadataBook.getValue(localPeer, 'AgentVersion') + const storedProtocolVersion = await peerStore.metadataBook.getValue(localPeer, 'ProtocolVersion') expect(agentVersion).to.eql(unit8ArrayToString(storedAgentVersion)) expect(storedProtocolVersion).to.exist() + + await service.stop() }) 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 storedProtocols = [multicodecs.IDENTIFY, multicodecs.IDENTIFY_PUSH, '/echo/1.0.0'].sort() const connectionManager = new EventEmitter() connectionManager.getConnection = () => { } - const localPeerStore = new PeerStore({ peerId: localPeer }) - localPeerStore.protoBook.set(localPeer, storedProtocols) + const localPeerStore = new PeerStore({ + peerId: localPeer, + datastore: new MemoryDatastore() + }) + await localPeerStore.protoBook.set(localPeer, storedProtocols) const localIdentify = new IdentifyService({ libp2p: { @@ -273,8 +288,11 @@ describe('Identify', () => { } }) - const remotePeerStore = new PeerStore({ peerId: remotePeer }) - remotePeerStore.protoBook.set(remotePeer, storedProtocols) + const remotePeerStore = new PeerStore({ + peerId: remotePeer, + datastore: new MemoryDatastore() + }) + await remotePeerStore.protoBook.set(remotePeer, storedProtocols) const remoteIdentify = new IdentifyService({ libp2p: { @@ -316,7 +334,7 @@ describe('Identify', () => { 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) + const addresses = await localIdentify.peerStore.addressBook.get(localPeer) expect(addresses).to.exist() expect(addresses).have.lengthOf(listenMaddrs.length) expect(addresses.map((a) => a.multiaddr)).to.eql(listenMaddrs) @@ -328,12 +346,15 @@ 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 storedProtocols = [multicodecs.IDENTIFY, multicodecs.IDENTIFY_PUSH, '/echo/1.0.0'].sort() const connectionManager = new EventEmitter() connectionManager.getConnection = () => { } - const localPeerStore = new PeerStore({ peerId: localPeer }) - localPeerStore.protoBook.set(localPeer, storedProtocols) + const localPeerStore = new PeerStore({ + peerId: localPeer, + datastore: new MemoryDatastore() + }) + await localPeerStore.protoBook.set(localPeer, storedProtocols) const localIdentify = new IdentifyService({ libp2p: { @@ -347,14 +368,17 @@ describe('Identify', () => { } }) - const remotePeerStore = new PeerStore({ peerId: remotePeer }) + const remotePeerStore = new PeerStore({ + peerId: remotePeer, + datastore: new MemoryDatastore() + }) remotePeerStore.protoBook.set(remotePeer, storedProtocols) const remoteIdentify = new IdentifyService({ libp2p: { peerId: remotePeer, connectionManager, - peerStore: new PeerStore({ peerId: remotePeer }), + peerStore: remotePeerStore, multiaddrs: [], _options: { host: {} }, _config: { protocolPrefix: 'ipfs' }, @@ -492,11 +516,15 @@ describe('Identify', () => { await libp2p.identifyService.identify.firstCall.returnValue sinon.stub(libp2p, 'isStarted').returns(true) - libp2p.handle('/echo/2.0.0', () => {}) - libp2p.unhandle('/echo/2.0.0') + await libp2p.handle('/echo/2.0.0', () => {}) + await libp2p.unhandle('/echo/2.0.0') + + // the protocol change event listener in the identity service is async + await pWaitFor(() => libp2p.identifyService.push.callCount === 2) // Verify the remote peer is notified of both changes expect(libp2p.identifyService.push.callCount).to.equal(2) + for (const call of libp2p.identifyService.push.getCalls()) { const [connections] = call.args expect(connections.length).to.equal(1) @@ -509,7 +537,7 @@ describe('Identify', () => { await pWaitFor(() => connection.streams.length === 0) }) - it('should store host data and protocol version into metadataBook', () => { + it('should store host data and protocol version into metadataBook', async () => { const agentVersion = 'js-project/1.0.0' libp2p = new Libp2p({ @@ -519,9 +547,10 @@ describe('Identify', () => { agentVersion } }) + await libp2p.start() - const storedAgentVersion = libp2p.peerStore.metadataBook.getValue(localPeer, 'AgentVersion') - const storedProtocolVersion = libp2p.peerStore.metadataBook.getValue(localPeer, 'ProtocolVersion') + const storedAgentVersion = await libp2p.peerStore.metadataBook.getValue(localPeer, 'AgentVersion') + const storedProtocolVersion = await libp2p.peerStore.metadataBook.getValue(localPeer, 'ProtocolVersion') expect(agentVersion).to.eql(unit8ArrayToString(storedAgentVersion)) expect(storedProtocolVersion).to.exist() @@ -545,7 +574,10 @@ describe('Identify', () => { await libp2p.identifyService.identify.firstCall.returnValue sinon.stub(libp2p, 'isStarted').returns(true) - libp2p.peerStore.addressBook.add(libp2p.peerId, [new Multiaddr('/ip4/180.0.0.1/tcp/15001/ws')]) + await libp2p.peerStore.addressBook.add(libp2p.peerId, [new Multiaddr('/ip4/180.0.0.1/tcp/15001/ws')]) + + // the protocol change event listener in the identity service is async + await pWaitFor(() => libp2p.identifyService.push.callCount === 1) // Verify the remote peer is notified of change expect(libp2p.identifyService.push.callCount).to.equal(1) diff --git a/test/metrics/index.spec.js b/test/metrics/index.spec.js index ed17c57ef8..7107e97b38 100644 --- a/test/metrics/index.spec.js +++ b/test/metrics/index.spec.js @@ -266,10 +266,10 @@ describe('Metrics', () => { const metric = 'some-metric' const value = 1 - metrics.updateComponentMetric(component, metric, value) + metrics.updateComponentMetric({ component, metric, value }) expect(metrics.getComponentMetrics()).to.have.lengthOf(1) - expect(metrics.getComponentMetrics().get(component)).to.have.lengthOf(1) - expect(metrics.getComponentMetrics().get(component).get(metric)).to.equal(value) + expect(metrics.getComponentMetrics().get('libp2p').get(component)).to.have.lengthOf(1) + expect(metrics.getComponentMetrics().get('libp2p').get(component).get(metric)).to.equal(value) }) }) diff --git a/test/peer-routing/peer-routing.node.js b/test/peer-routing/peer-routing.node.js index 4c008a1c03..29492bd6ce 100644 --- a/test/peer-routing/peer-routing.node.js +++ b/test/peer-routing/peer-routing.node.js @@ -658,10 +658,8 @@ describe('peer-routing', () => { await node.start() - await delay(300) - expect(node._dht.getClosestPeers.callCount).to.eql(1) - await delay(500) - expect(node._dht.getClosestPeers.callCount).to.eql(2) + // should run more than once + await pWaitFor(() => node._dht.getClosestPeers.callCount === 2) }) }) }) diff --git a/test/peer-store/address-book.spec.js b/test/peer-store/address-book.spec.js index 929a45fbff..671e94f932 100644 --- a/test/peer-store/address-book.spec.js +++ b/test/peer-store/address-book.spec.js @@ -9,7 +9,7 @@ const arrayEquals = require('libp2p-utils/src/array-equals') const addressSort = require('libp2p-utils/src/address-sort') const PeerId = require('peer-id') const pDefer = require('p-defer') - +const { MemoryDatastore } = require('datastore-core/memory') const PeerStore = require('../../src/peer-store') const Envelope = require('../../src/record/envelope') const PeerRecord = require('../../src/record/peer-record') @@ -19,6 +19,11 @@ const { codes: { ERR_INVALID_PARAMETERS } } = require('../../src/errors') +/** + * @typedef {import('../../src/peer-store/types').PeerStore} PeerStore + * @typedef {import('../../src/peer-store/types').AddressBook} AddressBook + */ + const addr1 = new Multiaddr('/ip4/127.0.0.1/tcp/8000') const addr2 = new Multiaddr('/ip4/20.0.0.1/tcp/8001') const addr3 = new Multiaddr('/ip4/127.0.0.1/tcp/8002') @@ -31,10 +36,16 @@ describe('addressBook', () => { }) describe('addressBook.set', () => { - let peerStore, ab + /** @type {PeerStore} */ + let peerStore + /** @type {AddressBook} */ + let ab beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) ab = peerStore.addressBook }) @@ -42,9 +53,9 @@ describe('addressBook', () => { peerStore.removeAllListeners() }) - it('throwns invalid parameters error if invalid PeerId is provided', () => { + it('throws invalid parameters error if invalid PeerId is provided', async () => { try { - ab.set('invalid peerId') + await ab.set('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -52,9 +63,9 @@ describe('addressBook', () => { throw new Error('invalid peerId should throw error') }) - it('throwns invalid parameters error if no addresses provided', () => { + it('throws invalid parameters error if no addresses provided', async () => { try { - ab.set(peerId) + await ab.set(peerId) } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -62,9 +73,9 @@ describe('addressBook', () => { throw new Error('no addresses should throw error') }) - it('throwns invalid parameters error if invalid multiaddrs are provided', () => { + it('throws invalid parameters error if invalid multiaddrs are provided', async () => { try { - ab.set(peerId, ['invalid multiaddr']) + await ab.set(peerId, ['invalid multiaddr']) } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -72,7 +83,7 @@ describe('addressBook', () => { throw new Error('invalid multiaddrs should throw error') }) - it('replaces the stored content by default and emit change event', () => { + it('replaces the stored content by default and emit change event', async () => { const defer = pDefer() const supportedMultiaddrs = [addr1, addr2] @@ -82,8 +93,8 @@ describe('addressBook', () => { defer.resolve() }) - ab.set(peerId, supportedMultiaddrs) - const addresses = ab.get(peerId) + await ab.set(peerId, supportedMultiaddrs) + const addresses = await ab.get(peerId) const multiaddrs = addresses.map((mi) => mi.multiaddr) expect(multiaddrs).to.have.deep.members(supportedMultiaddrs) @@ -105,11 +116,11 @@ describe('addressBook', () => { }) // set 1 - ab.set(peerId, supportedMultiaddrsA) + await ab.set(peerId, supportedMultiaddrsA) // set 2 (same content) - ab.set(peerId, supportedMultiaddrsB) - const addresses = ab.get(peerId) + await ab.set(peerId, supportedMultiaddrsB) + const addresses = await ab.get(peerId) const multiaddrs = addresses.map((mi) => mi.multiaddr) expect(multiaddrs).to.have.deep.members(supportedMultiaddrsB) @@ -130,10 +141,10 @@ describe('addressBook', () => { }) // set 1 - ab.set(peerId, supportedMultiaddrs) + await ab.set(peerId, supportedMultiaddrs) // set 2 (same content) - ab.set(peerId, supportedMultiaddrs) + await ab.set(peerId, supportedMultiaddrs) // Wait 50ms for incorrect second event setTimeout(() => { @@ -145,10 +156,16 @@ describe('addressBook', () => { }) describe('addressBook.add', () => { - let peerStore, ab + /** @type {PeerStore} */ + let peerStore + /** @type {AddressBook} */ + let ab beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) ab = peerStore.addressBook }) @@ -156,9 +173,9 @@ describe('addressBook', () => { peerStore.removeAllListeners() }) - it('throwns invalid parameters error if invalid PeerId is provided', () => { + it('throws invalid parameters error if invalid PeerId is provided', async () => { try { - ab.add('invalid peerId') + await ab.add('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -166,9 +183,9 @@ describe('addressBook', () => { throw new Error('invalid peerId should throw error') }) - it('throwns invalid parameters error if no addresses provided', () => { + it('throws invalid parameters error if no addresses provided', async () => { try { - ab.add(peerId) + await ab.add(peerId) } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -176,9 +193,9 @@ describe('addressBook', () => { throw new Error('no addresses provided should throw error') }) - it('throwns invalid parameters error if invalid multiaddrs are provided', () => { + it('throws invalid parameters error if invalid multiaddrs are provided', async () => { try { - ab.add(peerId, ['invalid multiaddr']) + await ab.add(peerId, ['invalid multiaddr']) } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -193,7 +210,7 @@ describe('addressBook', () => { defer.reject() }) - ab.add(peerId, []) + await ab.add(peerId, []) // Wait 50ms for incorrect second event setTimeout(() => { @@ -203,7 +220,7 @@ describe('addressBook', () => { await defer.promise }) - it('adds the new content and emits change event', () => { + it('adds the new content and emits change event', async () => { const defer = pDefer() const supportedMultiaddrsA = [addr1, addr2] @@ -219,14 +236,14 @@ describe('addressBook', () => { }) // Replace - ab.set(peerId, supportedMultiaddrsA) - let addresses = ab.get(peerId) + await ab.set(peerId, supportedMultiaddrsA) + let addresses = await ab.get(peerId) let multiaddrs = addresses.map((mi) => mi.multiaddr) expect(multiaddrs).to.have.deep.members(supportedMultiaddrsA) // Add - ab.add(peerId, supportedMultiaddrsB) - addresses = ab.get(peerId) + await ab.add(peerId, supportedMultiaddrsB) + addresses = await ab.get(peerId) multiaddrs = addresses.map((mi) => mi.multiaddr) expect(multiaddrs).to.have.deep.members(finalMultiaddrs) @@ -249,11 +266,11 @@ describe('addressBook', () => { }) // set 1 - ab.set(peerId, supportedMultiaddrsA) + await ab.set(peerId, supportedMultiaddrsA) // set 2 (content already existing) - ab.add(peerId, supportedMultiaddrsB) - const addresses = ab.get(peerId) + await ab.add(peerId, supportedMultiaddrsB) + const addresses = await ab.get(peerId) const multiaddrs = addresses.map((mi) => mi.multiaddr) expect(multiaddrs).to.have.deep.members(finalMultiaddrs) @@ -275,10 +292,10 @@ describe('addressBook', () => { }) // set 1 - ab.set(peerId, supportedMultiaddrsA) + await ab.set(peerId, supportedMultiaddrsA) // set 2 (content already existing) - ab.add(peerId, supportedMultiaddrsB) + await ab.add(peerId, supportedMultiaddrsB) // Wait 50ms for incorrect second event setTimeout(() => { @@ -288,26 +305,32 @@ describe('addressBook', () => { await defer.promise }) - it('does not add replicated content', () => { + it('does not add replicated content', async () => { // set 1 - ab.set(peerId, [addr1, addr1]) + await ab.set(peerId, [addr1, addr1]) - const addresses = ab.get(peerId) + const addresses = await ab.get(peerId) expect(addresses).to.have.lengthOf(1) }) }) describe('addressBook.get', () => { - let peerStore, ab + /** @type {PeerStore} */ + let peerStore + /** @type {AddressBook} */ + let ab beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) ab = peerStore.addressBook }) - it('throwns invalid parameters error if invalid PeerId is provided', () => { + it('throws invalid parameters error if invalid PeerId is provided', async () => { try { - ab.get('invalid peerId') + await ab.get('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -315,34 +338,40 @@ describe('addressBook', () => { throw new Error('invalid peerId should throw error') }) - it('returns undefined if no multiaddrs are known for the provided peer', () => { - const addresses = ab.get(peerId) + it('returns empty if no multiaddrs are known for the provided peer', async () => { + const addresses = await ab.get(peerId) - expect(addresses).to.not.exist() + expect(addresses).to.be.empty() }) - it('returns the multiaddrs stored', () => { + it('returns the multiaddrs stored', async () => { const supportedMultiaddrs = [addr1, addr2] - ab.set(peerId, supportedMultiaddrs) + await ab.set(peerId, supportedMultiaddrs) - const addresses = ab.get(peerId) + const addresses = await ab.get(peerId) const multiaddrs = addresses.map((mi) => mi.multiaddr) expect(multiaddrs).to.have.deep.members(supportedMultiaddrs) }) }) describe('addressBook.getMultiaddrsForPeer', () => { - let peerStore, ab + /** @type {PeerStore} */ + let peerStore + /** @type {AddressBook} */ + let ab beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) ab = peerStore.addressBook }) - it('throwns invalid parameters error if invalid PeerId is provided', () => { + it('throws invalid parameters error if invalid PeerId is provided', async () => { try { - ab.getMultiaddrsForPeer('invalid peerId') + await ab.getMultiaddrsForPeer('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -350,28 +379,28 @@ describe('addressBook', () => { throw new Error('invalid peerId should throw error') }) - it('returns undefined if no multiaddrs are known for the provided peer', () => { - const addresses = ab.getMultiaddrsForPeer(peerId) + it('returns empty if no multiaddrs are known for the provided peer', async () => { + const addresses = await ab.getMultiaddrsForPeer(peerId) - expect(addresses).to.not.exist() + expect(addresses).to.be.empty() }) - it('returns the multiaddrs stored', () => { + it('returns the multiaddrs stored', async () => { const supportedMultiaddrs = [addr1, addr2] - ab.set(peerId, supportedMultiaddrs) + await ab.set(peerId, supportedMultiaddrs) - const multiaddrs = ab.getMultiaddrsForPeer(peerId) + const multiaddrs = await ab.getMultiaddrsForPeer(peerId) multiaddrs.forEach((m) => { expect(m.getPeerId()).to.equal(peerId.toB58String()) }) }) - it('can sort multiaddrs providing a sorter', () => { + it('can sort multiaddrs providing a sorter', async () => { const supportedMultiaddrs = [addr1, addr2] - ab.set(peerId, supportedMultiaddrs) + await ab.set(peerId, supportedMultiaddrs) - const multiaddrs = ab.getMultiaddrsForPeer(peerId, addressSort.publicAddressesFirst) + const multiaddrs = await ab.getMultiaddrsForPeer(peerId, addressSort.publicAddressesFirst) const sortedAddresses = addressSort.publicAddressesFirst(supportedMultiaddrs.map((m) => ({ multiaddr: m }))) multiaddrs.forEach((m, index) => { @@ -381,16 +410,22 @@ describe('addressBook', () => { }) describe('addressBook.delete', () => { - let peerStore, ab + /** @type {PeerStore} */ + let peerStore + /** @type {AddressBook} */ + let ab beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) ab = peerStore.addressBook }) - it('throwns invalid parameters error if invalid PeerId is provided', () => { + it('throws invalid parameters error if invalid PeerId is provided', async () => { try { - ab.delete('invalid peerId') + await ab.delete('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -398,16 +433,14 @@ describe('addressBook', () => { throw new Error('invalid peerId should throw error') }) - it('returns false if no records exist for the peer and no event is emitted', () => { + it('does not emit an event if no records exist for the peer', async () => { const defer = pDefer() peerStore.on('change:multiaddrs', () => { defer.reject() }) - const deleted = ab.delete(peerId) - - expect(deleted).to.equal(false) + await ab.delete(peerId) // Wait 50ms for incorrect invalid event setTimeout(() => { @@ -417,11 +450,11 @@ describe('addressBook', () => { return defer.promise }) - it('returns true if the record exists and an event is emitted', () => { + it('emits an event if the record exists', async () => { const defer = pDefer() const supportedMultiaddrs = [addr1, addr2] - ab.set(peerId, supportedMultiaddrs) + await ab.set(peerId, supportedMultiaddrs) // Listen after set peerStore.on('change:multiaddrs', ({ multiaddrs }) => { @@ -429,20 +462,24 @@ describe('addressBook', () => { defer.resolve() }) - const deleted = ab.delete(peerId) - - expect(deleted).to.equal(true) + await ab.delete(peerId) return defer.promise }) }) describe('certified records', () => { - let peerStore, ab + /** @type {PeerStore} */ + let peerStore + /** @type {AddressBook} */ + let ab describe('consumes a valid peer record and stores its data', () => { beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) ab = peerStore.addressBook }) @@ -455,15 +492,11 @@ describe('addressBook', () => { const envelope = await Envelope.seal(peerRecord, peerId) // consume peer record - const consumed = ab.consumePeerRecord(envelope) + const consumed = await ab.consumePeerRecord(envelope) expect(consumed).to.eql(true) - // Validate stored envelope - const storedEnvelope = await ab.getPeerRecord(peerId) - expect(envelope.equals(storedEnvelope)).to.eql(true) - // Validate AddressBook addresses - const addrs = ab.get(peerId) + const addrs = await ab.get(peerId) expect(addrs).to.exist() expect(addrs).to.have.lengthOf(multiaddrs.length) addrs.forEach((addr, index) => { @@ -488,7 +521,7 @@ describe('addressBook', () => { }) // consume peer record - const consumed = ab.consumePeerRecord(envelope) + const consumed = await ab.consumePeerRecord(envelope) expect(consumed).to.eql(true) return defer.promise @@ -499,10 +532,10 @@ describe('addressBook', () => { const multiaddrs = [addr1, addr2] // Set addressBook data - ab.set(peerId, multiaddrs) + await ab.set(peerId, multiaddrs) // Validate data exists, but not certified - let addrs = ab.get(peerId) + let addrs = await ab.get(peerId) expect(addrs).to.exist() expect(addrs).to.have.lengthOf(multiaddrs.length) @@ -525,14 +558,14 @@ describe('addressBook', () => { }) // consume peer record - const consumed = ab.consumePeerRecord(envelope) + const consumed = await ab.consumePeerRecord(envelope) expect(consumed).to.eql(true) // Wait event await defer.promise // Validate data exists and certified - addrs = ab.get(peerId) + addrs = await ab.get(peerId) expect(addrs).to.exist() expect(addrs).to.have.lengthOf(multiaddrs.length) addrs.forEach((addr, index) => { @@ -546,10 +579,10 @@ describe('addressBook', () => { const multiaddrs = [addr1, addr2] // Set addressBook data - ab.set(peerId, [addr1]) + await ab.set(peerId, [addr1]) // Validate data exists, but not certified - let addrs = ab.get(peerId) + let addrs = await ab.get(peerId) expect(addrs).to.exist() expect(addrs).to.have.lengthOf(1) expect(addrs[0].isCertified).to.eql(false) @@ -569,14 +602,14 @@ describe('addressBook', () => { }) // consume peer record - const consumed = ab.consumePeerRecord(envelope) + const consumed = await ab.consumePeerRecord(envelope) expect(consumed).to.eql(true) // Wait event await defer.promise // Validate data exists and certified - addrs = ab.get(peerId) + addrs = await ab.get(peerId) expect(addrs).to.exist() expect(addrs).to.have.lengthOf(multiaddrs.length) addrs.forEach((addr, index) => { @@ -591,10 +624,10 @@ describe('addressBook', () => { const multiaddrsCertified = [addr1, addr2] // Set addressBook data - ab.set(peerId, multiaddrsUncertified) + await ab.set(peerId, multiaddrsUncertified) // Validate data exists, but not certified - let addrs = ab.get(peerId) + let addrs = await ab.get(peerId) expect(addrs).to.exist() expect(addrs).to.have.lengthOf(multiaddrsUncertified.length) addrs.forEach((addr, index) => { @@ -616,14 +649,14 @@ describe('addressBook', () => { }) // consume peer record - const consumed = ab.consumePeerRecord(envelope) + const consumed = await ab.consumePeerRecord(envelope) expect(consumed).to.eql(true) // Wait event await defer.promise // Validate data exists and certified - addrs = ab.get(peerId) + addrs = await ab.get(peerId) expect(addrs).to.exist() expect(addrs).to.have.lengthOf(multiaddrsCertified.length) addrs.forEach((addr, index) => { @@ -635,16 +668,19 @@ describe('addressBook', () => { describe('fails to consume invalid peer records', () => { beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) ab = peerStore.addressBook }) - it('invalid peer record', () => { + it('invalid peer record', async () => { const invalidEnvelope = { payload: Buffer.from('invalid-peerRecord') } - const consumed = ab.consumePeerRecord(invalidEnvelope) + const consumed = await ab.consumePeerRecord(invalidEnvelope) expect(consumed).to.eql(false) }) @@ -659,7 +695,7 @@ describe('addressBook', () => { }) const envelope = await Envelope.seal(peerRecord, peerId) - const consumed = ab.consumePeerRecord(envelope) + const consumed = await ab.consumePeerRecord(envelope) expect(consumed).to.eql(false) }) @@ -679,10 +715,10 @@ describe('addressBook', () => { const envelope2 = await Envelope.seal(peerRecord2, peerId) // Consume envelope1 (bigger seqNumber) - let consumed = ab.consumePeerRecord(envelope1) + let consumed = await ab.consumePeerRecord(envelope1) expect(consumed).to.eql(true) - consumed = ab.consumePeerRecord(envelope2) + consumed = await ab.consumePeerRecord(envelope2) expect(consumed).to.eql(false) }) @@ -693,7 +729,7 @@ describe('addressBook', () => { }) const envelope = await Envelope.seal(peerRecord, peerId) - const consumed = ab.consumePeerRecord(envelope) + const consumed = await ab.consumePeerRecord(envelope) expect(consumed).to.eql(false) }) }) diff --git a/test/peer-store/key-book.spec.js b/test/peer-store/key-book.spec.js index 8c439a9051..c5c70db74a 100644 --- a/test/peer-store/key-book.spec.js +++ b/test/peer-store/key-book.spec.js @@ -3,26 +3,43 @@ const { expect } = require('aegir/utils/chai') const sinon = require('sinon') - +const { MemoryDatastore } = require('datastore-core/memory') const PeerStore = require('../../src/peer-store') - +const pDefer = require('p-defer') const peerUtils = require('../utils/creators/peer') const { codes: { ERR_INVALID_PARAMETERS } } = require('../../src/errors') +/** + * @typedef {import('../../src/peer-store/types').PeerStore} PeerStore + * @typedef {import('../../src/peer-store/types').KeyBook} KeyBook + * @typedef {import('peer-id')} PeerId + */ + describe('keyBook', () => { - let peerId, peerStore, kb + /** @type {PeerId} */ + let peerId + /** @type {PeerStore} */ + let peerStore + /** @type {KeyBook} */ + let kb + /** @type {MemoryDatastore} */ + let datastore beforeEach(async () => { [peerId] = await peerUtils.createPeerId() - peerStore = new PeerStore({ peerId }) + datastore = new MemoryDatastore() + peerStore = new PeerStore({ + peerId, + datastore + }) kb = peerStore.keyBook }) - it('throws invalid parameters error if invalid PeerId is provided in set', () => { + it('throws invalid parameters error if invalid PeerId is provided in set', async () => { try { - kb.set('invalid peerId') + await kb.set('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -30,9 +47,9 @@ describe('keyBook', () => { throw new Error('invalid peerId should throw error') }) - it('throws invalid parameters error if invalid PeerId is provided in get', () => { + it('throws invalid parameters error if invalid PeerId is provided in get', async () => { try { - kb.get('invalid peerId') + await kb.get('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -40,22 +57,58 @@ describe('keyBook', () => { throw new Error('invalid peerId should throw error') }) - it('stores the peerId in the book and returns the public key', () => { + it('stores the peerId in the book and returns the public key', async () => { // Set PeerId - kb.set(peerId, peerId.pubKey) + await kb.set(peerId, peerId.pubKey) // Get public key - const pubKey = kb.get(peerId) + const pubKey = await kb.get(peerId) expect(peerId.pubKey.bytes).to.equalBytes(pubKey.bytes) }) - it('should not store if already stored', () => { - const spy = sinon.spy(kb, '_setData') + it('should not store if already stored', async () => { + const spy = sinon.spy(datastore, 'put') // Set PeerId - kb.set(peerId, peerId.pubKey) - kb.set(peerId, peerId.pubKey) + await kb.set(peerId, peerId.pubKey) + await kb.set(peerId, peerId.pubKey) expect(spy).to.have.property('callCount', 1) }) + + it('should emit an event when setting a key', async () => { + const defer = pDefer() + + peerStore.on('change:pubkey', ({ peerId: id, pubKey }) => { + expect(id.toB58String()).to.equal(peerId.toB58String()) + expect(pubKey.bytes).to.equalBytes(peerId.pubKey.bytes) + defer.resolve() + }) + + // Set PeerId + await kb.set(peerId, peerId.pubKey) + await defer.promise + }) + + it('should not set when key does not match', async () => { + const [edKey] = await peerUtils.createPeerId({ fixture: false, opts: { keyType: 'Ed25519' } }) + + // Set PeerId + await expect(kb.set(edKey, peerId.pubKey)).to.eventually.be.rejectedWith(/bytes do not match/) + }) + + it('should emit an event when deleting a key', async () => { + const defer = pDefer() + + await kb.set(peerId, peerId.pubKey) + + peerStore.on('change:pubkey', ({ peerId: id, pubKey }) => { + expect(id.toB58String()).to.equal(peerId.toB58String()) + expect(pubKey).to.be.undefined() + defer.resolve() + }) + + await kb.delete(peerId) + await defer.promise + }) }) diff --git a/test/peer-store/metadata-book.spec.js b/test/peer-store/metadata-book.spec.js index 478dfff5dd..214a23ca1e 100644 --- a/test/peer-store/metadata-book.spec.js +++ b/test/peer-store/metadata-book.spec.js @@ -3,7 +3,7 @@ const { expect } = require('aegir/utils/chai') const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') - +const { MemoryDatastore } = require('datastore-core/memory') const pDefer = require('p-defer') const PeerStore = require('../../src/peer-store') @@ -12,7 +12,14 @@ const { codes: { ERR_INVALID_PARAMETERS } } = require('../../src/errors') +/** + * @typedef {import('../../src/peer-store/types').PeerStore} PeerStore + * @typedef {import('../../src/peer-store/types').MetadataBook} MetadataBook + * @typedef {import('peer-id')} PeerId + */ + describe('metadataBook', () => { + /** @type {PeerId} */ let peerId before(async () => { @@ -20,10 +27,16 @@ describe('metadataBook', () => { }) describe('metadataBook.set', () => { - let peerStore, mb + /** @type {PeerStore} */ + let peerStore + /** @type {MetadataBook} */ + let mb beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) mb = peerStore.metadataBook }) @@ -31,9 +44,9 @@ describe('metadataBook', () => { peerStore.removeAllListeners() }) - it('throws invalid parameters error if invalid PeerId is provided', () => { + it('throws invalid parameters error if invalid PeerId is provided', async () => { try { - mb.set('invalid peerId') + await mb.set('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -41,9 +54,9 @@ describe('metadataBook', () => { throw new Error('invalid peerId should throw error') }) - it('throws invalid parameters error if no key provided', () => { + it('throws invalid parameters error if no metadata provided', async () => { try { - mb.set(peerId) + await mb.set(peerId) } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -51,9 +64,9 @@ describe('metadataBook', () => { throw new Error('no key provided should throw error') }) - it('throws invalid parameters error if no value provided', () => { + it('throws invalid parameters error if no value provided', async () => { try { - mb.set(peerId, 'location') + await mb.setValue(peerId, 'location') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -61,9 +74,9 @@ describe('metadataBook', () => { throw new Error('no value provided should throw error') }) - it('throws invalid parameters error if value is not a buffer', () => { + it('throws invalid parameters error if value is not a buffer', async () => { try { - mb.set(peerId, 'location', 'mars') + await mb.setValue(peerId, 'location', 'mars') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -71,30 +84,30 @@ describe('metadataBook', () => { throw new Error('invalid value provided should throw error') }) - it('stores the content and emit change event', () => { + it('stores the content and emit change event', async () => { const defer = pDefer() const metadataKey = 'location' const metadataValue = uint8ArrayFromString('mars') peerStore.once('change:metadata', ({ peerId, metadata }) => { expect(peerId).to.exist() - expect(metadata).to.equal(metadataKey) + expect(metadata.get(metadataKey)).to.equalBytes(metadataValue) defer.resolve() }) - mb.set(peerId, metadataKey, metadataValue) + await mb.setValue(peerId, metadataKey, metadataValue) - const value = mb.getValue(peerId, metadataKey) + const value = await mb.getValue(peerId, metadataKey) expect(value).to.equalBytes(metadataValue) - const peerMetadata = mb.get(peerId) + const peerMetadata = await mb.get(peerId) expect(peerMetadata).to.exist() expect(peerMetadata.get(metadataKey)).to.equalBytes(metadataValue) return defer.promise }) - it('emits on set if not storing the exact same content', () => { + it('emits on set if not storing the exact same content', async () => { const defer = pDefer() const metadataKey = 'location' const metadataValue1 = uint8ArrayFromString('mars') @@ -109,22 +122,22 @@ describe('metadataBook', () => { }) // set 1 - mb.set(peerId, metadataKey, metadataValue1) + await mb.setValue(peerId, metadataKey, metadataValue1) // set 2 (same content) - mb.set(peerId, metadataKey, metadataValue2) + await mb.setValue(peerId, metadataKey, metadataValue2) - const value = mb.getValue(peerId, metadataKey) + const value = await mb.getValue(peerId, metadataKey) expect(value).to.equalBytes(metadataValue2) - const peerMetadata = mb.get(peerId) + const peerMetadata = await mb.get(peerId) expect(peerMetadata).to.exist() expect(peerMetadata.get(metadataKey)).to.equalBytes(metadataValue2) return defer.promise }) - it('does not emit on set if it is storing the exact same content', () => { + it('does not emit on set if it is storing the exact same content', async () => { const defer = pDefer() const metadataKey = 'location' const metadataValue = uint8ArrayFromString('mars') @@ -138,10 +151,10 @@ describe('metadataBook', () => { }) // set 1 - mb.set(peerId, metadataKey, metadataValue) + await mb.setValue(peerId, metadataKey, metadataValue) // set 2 (same content) - mb.set(peerId, metadataKey, metadataValue) + await mb.setValue(peerId, metadataKey, metadataValue) // Wait 50ms for incorrect second event setTimeout(() => { @@ -153,16 +166,22 @@ describe('metadataBook', () => { }) describe('metadataBook.get', () => { - let peerStore, mb + /** @type {PeerStore} */ + let peerStore + /** @type {MetadataBook} */ + let mb beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) mb = peerStore.metadataBook }) - it('throws invalid parameters error if invalid PeerId is provided', () => { + it('throws invalid parameters error if invalid PeerId is provided', async () => { try { - mb.get('invalid peerId') + await mb.get('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -170,35 +189,43 @@ describe('metadataBook', () => { throw new Error('invalid peerId should throw error') }) - it('returns undefined if no metadata is known for the provided peer', () => { - const metadata = mb.get(peerId) + it('returns empty if no metadata is known for the provided peer', async () => { + const metadata = await mb.get(peerId) - expect(metadata).to.not.exist() + expect(metadata).to.be.empty() }) - it('returns the metadata stored', () => { + it('returns the metadata stored', async () => { const metadataKey = 'location' const metadataValue = uint8ArrayFromString('mars') + const metadata = new Map() + metadata.set(metadataKey, metadataValue) - mb.set(peerId, metadataKey, metadataValue) + await mb.set(peerId, metadata) - const peerMetadata = mb.get(peerId) + const peerMetadata = await mb.get(peerId) expect(peerMetadata).to.exist() expect(peerMetadata.get(metadataKey)).to.equalBytes(metadataValue) }) }) describe('metadataBook.getValue', () => { - let peerStore, mb + /** @type {PeerStore} */ + let peerStore + /** @type {MetadataBook} */ + let mb beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) mb = peerStore.metadataBook }) - it('throws invalid parameters error if invalid PeerId is provided', () => { + it('throws invalid parameters error if invalid PeerId is provided', async () => { try { - mb.getValue('invalid peerId') + await mb.getValue('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -206,48 +233,53 @@ describe('metadataBook', () => { throw new Error('invalid peerId should throw error') }) - it('returns undefined if no metadata is known for the provided peer', () => { + it('returns undefined if no metadata is known for the provided peer', async () => { const metadataKey = 'location' - const metadata = mb.getValue(peerId, metadataKey) + const metadata = await mb.getValue(peerId, metadataKey) expect(metadata).to.not.exist() }) - it('returns the metadata value stored for the given key', () => { + it('returns the metadata value stored for the given key', async () => { const metadataKey = 'location' const metadataValue = uint8ArrayFromString('mars') - mb.set(peerId, metadataKey, metadataValue) + await mb.setValue(peerId, metadataKey, metadataValue) - const value = mb.getValue(peerId, metadataKey) + const value = await mb.getValue(peerId, metadataKey) expect(value).to.exist() expect(value).to.equalBytes(metadataValue) }) - it('returns undefined if no metadata is known for the provided peer and key', () => { + it('returns undefined if no metadata is known for the provided peer and key', async () => { const metadataKey = 'location' const metadataBadKey = 'nickname' const metadataValue = uint8ArrayFromString('mars') - mb.set(peerId, metadataKey, metadataValue) - - const metadata = mb.getValue(peerId, metadataBadKey) + await mb.setValue(peerId, metadataKey, metadataValue) + const metadata = await mb.getValue(peerId, metadataBadKey) expect(metadata).to.not.exist() }) }) describe('metadataBook.delete', () => { - let peerStore, mb + /** @type {PeerStore} */ + let peerStore + /** @type {MetadataBook} */ + let mb beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) mb = peerStore.metadataBook }) - it('throwns invalid parameters error if invalid PeerId is provided', () => { + it('throws invalid parameters error if invalid PeerId is provided', async () => { try { - mb.delete('invalid peerId') + await mb.delete('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -255,16 +287,14 @@ describe('metadataBook', () => { throw new Error('invalid peerId should throw error') }) - it('returns false if no records exist for the peer and no event is emitted', () => { + it('should not emit event if no records exist for the peer', async () => { const defer = pDefer() peerStore.on('change:metadata', () => { defer.reject() }) - const deleted = mb.delete(peerId) - - expect(deleted).to.equal(false) + await mb.delete(peerId) // Wait 50ms for incorrect invalid event setTimeout(() => { @@ -274,37 +304,41 @@ describe('metadataBook', () => { return defer.promise }) - it('returns true if the record exists and an event is emitted', () => { + it('should emit an event if the record exists for the peer', async () => { const defer = pDefer() const metadataKey = 'location' const metadataValue = uint8ArrayFromString('mars') - mb.set(peerId, metadataKey, metadataValue) + await mb.setValue(peerId, metadataKey, metadataValue) // Listen after set peerStore.on('change:metadata', () => { defer.resolve() }) - const deleted = mb.delete(peerId) - - expect(deleted).to.equal(true) + await mb.delete(peerId) return defer.promise }) }) describe('metadataBook.deleteValue', () => { - let peerStore, mb + /** @type {PeerStore} */ + let peerStore + /** @type {MetadataBook} */ + let mb beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) mb = peerStore.metadataBook }) - it('throws invalid parameters error if invalid PeerId is provided', () => { + it('throws invalid parameters error if invalid PeerId is provided', async () => { try { - mb.deleteValue('invalid peerId') + await mb.deleteValue('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -312,7 +346,7 @@ describe('metadataBook', () => { throw new Error('invalid peerId should throw error') }) - it('returns false if no records exist for the peer and no event is emitted', () => { + it('should not emit event if no records exist for the peer', async () => { const defer = pDefer() const metadataKey = 'location' @@ -320,9 +354,7 @@ describe('metadataBook', () => { defer.reject() }) - const deleted = mb.deleteValue(peerId, metadataKey) - - expect(deleted).to.equal(false) + await mb.deleteValue(peerId, metadataKey) // Wait 50ms for incorrect invalid event setTimeout(() => { @@ -332,45 +364,19 @@ describe('metadataBook', () => { return defer.promise }) - it('returns true if the record exists and an event is emitted', () => { + it('should emit event if a record exists for the peer', async () => { const defer = pDefer() const metadataKey = 'location' const metadataValue = uint8ArrayFromString('mars') - mb.set(peerId, metadataKey, metadataValue) + await mb.setValue(peerId, metadataKey, metadataValue) // Listen after set peerStore.on('change:metadata', () => { defer.resolve() }) - const deleted = mb.deleteValue(peerId, metadataKey) - - expect(deleted).to.equal(true) - - return defer.promise - }) - - it('returns false if there is a record for the peer but not the given metadata key', () => { - const defer = pDefer() - const metadataKey = 'location' - const metadataBadKey = 'nickname' - const metadataValue = uint8ArrayFromString('mars') - - mb.set(peerId, metadataKey, metadataValue) - - peerStore.on('change:metadata', () => { - defer.reject() - }) - - const deleted = mb.deleteValue(peerId, metadataBadKey) - - expect(deleted).to.equal(false) - - // Wait 50ms for incorrect invalid event - setTimeout(() => { - defer.resolve() - }, 50) + await mb.deleteValue(peerId, metadataKey) return defer.promise }) diff --git a/test/peer-store/peer-store.node.js b/test/peer-store/peer-store.node.js index d7a462ad2c..c7d14f110d 100644 --- a/test/peer-store/peer-store.node.js +++ b/test/peer-store/peer-store.node.js @@ -6,6 +6,7 @@ const sinon = require('sinon') const baseOptions = require('../utils/base-options') const peerUtils = require('../utils/creators/peer') +const all = require('it-all') describe('libp2p.peerStore', () => { let libp2p, remoteLibp2p @@ -35,13 +36,14 @@ describe('libp2p.peerStore', () => { expect(spyAddressBook).to.have.property('called', true) expect(spyKeyBook).to.have.property('called', true) - const localPeers = libp2p.peerStore.peers - expect(localPeers.size).to.equal(1) + const localPeers = await all(libp2p.peerStore.getPeers()) - const publicKeyInLocalPeer = localPeers.get(remoteIdStr).id.pubKey + expect(localPeers.length).to.equal(1) + + const publicKeyInLocalPeer = localPeers[0].id.pubKey expect(publicKeyInLocalPeer.bytes).to.equalBytes(remoteLibp2p.peerId.pubKey.bytes) - const publicKeyInRemotePeer = remoteLibp2p.peerStore.keyBook.get(libp2p.peerId) + const publicKeyInRemotePeer = await remoteLibp2p.peerStore.keyBook.get(libp2p.peerId) expect(publicKeyInRemotePeer).to.exist() expect(publicKeyInRemotePeer.bytes).to.equalBytes(libp2p.peerId.pubKey.bytes) }) diff --git a/test/peer-store/peer-store.spec.js b/test/peer-store/peer-store.spec.js index 4cfaecfe18..3456417a1b 100644 --- a/test/peer-store/peer-store.spec.js +++ b/test/peer-store/peer-store.spec.js @@ -2,11 +2,11 @@ /* eslint-env mocha */ const { expect } = require('aegir/utils/chai') - +const all = require('it-all') const PeerStore = require('../../src/peer-store') const { Multiaddr } = require('multiaddr') const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') - +const { MemoryDatastore } = require('datastore-core/memory') const peerUtils = require('../utils/creators/peer') const addr1 = new Multiaddr('/ip4/127.0.0.1/tcp/8000') @@ -18,6 +18,10 @@ const proto1 = '/protocol1' const proto2 = '/protocol2' const proto3 = '/protocol3' +/** + * @typedef {import('../../src/peer-store/types').PeerStore} PeerStore + */ + describe('peer-store', () => { let peerIds before(async () => { @@ -27,62 +31,65 @@ describe('peer-store', () => { }) describe('empty books', () => { + /** @type {PeerStore} */ let peerStore beforeEach(() => { - peerStore = new PeerStore({ peerId: peerIds[4] }) + peerStore = new PeerStore({ + peerId: peerIds[4], + datastore: new MemoryDatastore() + }) }) - it('has an empty map of peers', () => { - const peers = peerStore.peers - expect(peers.size).to.equal(0) + it('has an empty map of peers', async () => { + const peers = await all(peerStore.getPeers()) + expect(peers.length).to.equal(0) }) - it('returns false on trying to delete a non existant peerId', () => { - const deleted = peerStore.delete(peerIds[0]) - expect(deleted).to.equal(false) + it('deletes a peerId', async () => { + await peerStore.addressBook.set(peerIds[0], [new Multiaddr('/ip4/127.0.0.1/tcp/4001')]) + await expect(peerStore.has(peerIds[0])).to.eventually.be.true() + await peerStore.delete(peerIds[0]) + await expect(peerStore.has(peerIds[0])).to.eventually.be.false() }) - it('returns undefined on trying to find a non existant peerId', () => { - const peer = peerStore.get(peerIds[0]) - expect(peer).to.not.exist() - }) - - it('sets the peer\'s public key to the KeyBook', () => { - peerStore.keyBook.set(peerIds[0], peerIds[0].pubKey) - - const pubKey = peerStore.keyBook.get(peerIds[0]) - expect(pubKey).to.exist() + it('sets the peer\'s public key to the KeyBook', async () => { + await peerStore.keyBook.set(peerIds[0], peerIds[0].pubKey) + await expect(peerStore.keyBook.get(peerIds[0])).to.eventually.deep.equal(peerIds[0].pubKey) }) }) describe('previously populated books', () => { + /** @type {PeerStore} */ let peerStore - beforeEach(() => { - peerStore = new PeerStore({ peerId: peerIds[4] }) + beforeEach(async () => { + peerStore = new PeerStore({ + peerId: peerIds[4], + datastore: new MemoryDatastore() + }) // Add peer0 with { addr1, addr2 } and { proto1 } - peerStore.addressBook.set(peerIds[0], [addr1, addr2]) - peerStore.protoBook.set(peerIds[0], [proto1]) + await peerStore.addressBook.set(peerIds[0], [addr1, addr2]) + await peerStore.protoBook.set(peerIds[0], [proto1]) // Add peer1 with { addr3 } and { proto2, proto3 } - peerStore.addressBook.set(peerIds[1], [addr3]) - peerStore.protoBook.set(peerIds[1], [proto2, proto3]) + await peerStore.addressBook.set(peerIds[1], [addr3]) + await peerStore.protoBook.set(peerIds[1], [proto2, proto3]) // Add peer2 with { addr4 } - peerStore.addressBook.set(peerIds[2], [addr4]) + await peerStore.addressBook.set(peerIds[2], [addr4]) // Add peer3 with { addr4 } and { proto2 } - peerStore.addressBook.set(peerIds[3], [addr4]) - peerStore.protoBook.set(peerIds[3], [proto2]) + await peerStore.addressBook.set(peerIds[3], [addr4]) + await peerStore.protoBook.set(peerIds[3], [proto2]) }) - it('has peers', () => { - const peers = peerStore.peers + it('has peers', async () => { + const peers = await all(peerStore.getPeers()) - expect(peers.size).to.equal(4) - expect(Array.from(peers.keys())).to.have.members([ + expect(peers.length).to.equal(4) + expect(peers.map(peer => peer.id.toB58String())).to.have.members([ peerIds[0].toB58String(), peerIds[1].toB58String(), peerIds[2].toB58String(), @@ -90,47 +97,45 @@ describe('peer-store', () => { ]) }) - it('returns true on deleting a stored peer', () => { - const deleted = peerStore.delete(peerIds[0]) - expect(deleted).to.equal(true) + it('deletes a stored peer', async () => { + await peerStore.delete(peerIds[0]) - const peers = peerStore.peers - expect(peers.size).to.equal(3) + const peers = await all(peerStore.getPeers()) + expect(peers.length).to.equal(3) expect(Array.from(peers.keys())).to.not.have.members([peerIds[0].toB58String()]) }) - it('returns true on deleting a stored peer which is only on one book', () => { - const deleted = peerStore.delete(peerIds[2]) - expect(deleted).to.equal(true) + it('deletes a stored peer which is only on one book', async () => { + await peerStore.delete(peerIds[2]) - const peers = peerStore.peers - expect(peers.size).to.equal(3) + const peers = await all(peerStore.getPeers()) + expect(peers.length).to.equal(3) }) - it('gets the stored information of a peer in all its books', () => { - const peer = peerStore.get(peerIds[0]) + it('gets the stored information of a peer in all its books', async () => { + const peer = await peerStore.get(peerIds[0]) expect(peer).to.exist() expect(peer.protocols).to.have.members([proto1]) const peerMultiaddrs = peer.addresses.map((mi) => mi.multiaddr) - expect(peerMultiaddrs).to.have.members([addr1, addr2]) + expect(peerMultiaddrs).to.have.deep.members([addr1, addr2]) - expect(peer.id).to.exist() + expect(peer.id.toB58String()).to.equal(peerIds[0].toB58String()) }) - it('gets the stored information of a peer that is not present in all its books', () => { - const peers = peerStore.get(peerIds[2]) + it('gets the stored information of a peer that is not present in all its books', async () => { + const peers = await peerStore.get(peerIds[2]) expect(peers).to.exist() expect(peers.protocols.length).to.eql(0) const peerMultiaddrs = peers.addresses.map((mi) => mi.multiaddr) - expect(peerMultiaddrs).to.have.members([addr4]) + expect(peerMultiaddrs).to.have.deep.members([addr4]) }) - it('can find all the peers supporting a protocol', () => { + it('can find all the peers supporting a protocol', async () => { const peerSupporting2 = [] - for (const [, peer] of peerStore.peers.entries()) { + for await (const peer of peerStore.getPeers()) { if (peer.protocols.includes(proto2)) { peerSupporting2.push(peer) } @@ -141,67 +146,71 @@ describe('peer-store', () => { expect(peerSupporting2[1].id.toB58String()).to.eql(peerIds[3].toB58String()) }) - it('can find all the peers listening on a given address', () => { - const peerListenint4 = [] + it('can find all the peers listening on a given address', async () => { + const peerListening4 = [] - for (const [, peer] of peerStore.peers.entries()) { - const multiaddrs = peer.addresses.map((mi) => mi.multiaddr) + for await (const peer of peerStore.getPeers()) { + const multiaddrs = peer.addresses.map((mi) => mi.multiaddr.toString()) - if (multiaddrs.includes(addr4)) { - peerListenint4.push(peer) + if (multiaddrs.includes(addr4.toString())) { + peerListening4.push(peer) } } - expect(peerListenint4.length).to.eql(2) - expect(peerListenint4[0].id.toB58String()).to.eql(peerIds[2].toB58String()) - expect(peerListenint4[1].id.toB58String()).to.eql(peerIds[3].toB58String()) + expect(peerListening4.length).to.eql(2) + expect(peerListening4[0].id.toB58String()).to.eql(peerIds[2].toB58String()) + expect(peerListening4[1].id.toB58String()).to.eql(peerIds[3].toB58String()) }) }) - describe('peerStore.peers', () => { + describe('peerStore.getPeers', () => { + /** @type {PeerStore} */ let peerStore beforeEach(() => { - peerStore = new PeerStore({ peerId: peerIds[4] }) + peerStore = new PeerStore({ + peerId: peerIds[4], + datastore: new MemoryDatastore() + }) }) - it('returns peers if only addresses are known', () => { - peerStore.addressBook.set(peerIds[0], [addr1]) + it('returns peers if only addresses are known', async () => { + await peerStore.addressBook.set(peerIds[0], [addr1]) - const peers = peerStore.peers - expect(peers.size).to.equal(1) + const peers = await all(peerStore.getPeers()) + expect(peers.length).to.equal(1) - const peerData = peers.get(peerIds[0].toB58String()) + const peerData = peers[0] expect(peerData).to.exist() expect(peerData.id).to.exist() expect(peerData.addresses).to.have.lengthOf(1) expect(peerData.protocols).to.have.lengthOf(0) - expect(peerData.metadata).to.not.exist() + expect(peerData.metadata).to.be.empty() }) - it('returns peers if only protocols are known', () => { - peerStore.protoBook.set(peerIds[0], [proto1]) + it('returns peers if only protocols are known', async () => { + await peerStore.protoBook.set(peerIds[0], [proto1]) - const peers = peerStore.peers - expect(peers.size).to.equal(1) + const peers = await all(peerStore.getPeers()) + expect(peers.length).to.equal(1) - const peerData = peers.get(peerIds[0].toB58String()) + const peerData = peers[0] expect(peerData).to.exist() expect(peerData.id).to.exist() expect(peerData.addresses).to.have.lengthOf(0) expect(peerData.protocols).to.have.lengthOf(1) - expect(peerData.metadata).to.not.exist() + expect(peerData.metadata).to.be.empty() }) - it('returns peers if only metadata is known', () => { + it('returns peers if only metadata is known', async () => { const metadataKey = 'location' const metadataValue = uint8ArrayFromString('earth') - peerStore.metadataBook.set(peerIds[0], metadataKey, metadataValue) + await peerStore.metadataBook.setValue(peerIds[0], metadataKey, metadataValue) - const peers = peerStore.peers - expect(peers.size).to.equal(1) + const peers = await all(peerStore.getPeers()) + expect(peers.length).to.equal(1) - const peerData = peers.get(peerIds[0].toB58String()) + const peerData = peers[0] expect(peerData).to.exist() expect(peerData.id).to.exist() expect(peerData.addresses).to.have.lengthOf(0) diff --git a/test/peer-store/persisted-peer-store.spec.js b/test/peer-store/persisted-peer-store.spec.js deleted file mode 100644 index 67638a10a5..0000000000 --- a/test/peer-store/persisted-peer-store.spec.js +++ /dev/null @@ -1,608 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') - -const Envelope = require('../../src/record/envelope') -const PeerRecord = require('../../src/record/peer-record') -const PeerStore = require('../../src/peer-store/persistent') - -const { Multiaddr } = require('multiaddr') -const { MemoryDatastore } = require('datastore-core/memory') -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') - -const peerUtils = require('../utils/creators/peer') - -describe('Persisted PeerStore', () => { - let datastore, peerStore - let peerId - - before(async () => { - [peerId] = await peerUtils.createPeerId({ fixture: false }) - }) - - describe('start and stop flows', () => { - beforeEach(() => { - datastore = new MemoryDatastore() - peerStore = new PeerStore({ datastore, peerId }) - }) - - afterEach(() => peerStore.stop()) - - it('should try to load content from an empty datastore on start', async () => { - const spyQuery = sinon.spy(datastore, 'query') - const spyProcessEntry = sinon.spy(peerStore, '_processDatastoreEntry') - - await peerStore.start() - expect(spyQuery).to.have.property('callCount', 1) - expect(spyProcessEntry).to.have.property('callCount', 0) - - // No data to populate - expect(peerStore.peers.size).to.eq(0) - }) - - it('should try to commit data on stop but should not add to batch if not exists', async () => { - const spyDs = sinon.spy(peerStore, '_commitData') - const spyBatch = sinon.spy(datastore, 'batch') - - await peerStore.start() - expect(spyDs).to.have.property('callCount', 0) - - await peerStore.stop() - expect(spyBatch).to.have.property('callCount', 0) - expect(spyDs).to.have.property('callCount', 1) - }) - }) - - describe('simple setup with content stored per change (threshold 1)', () => { - beforeEach(() => { - datastore = new MemoryDatastore() - peerStore = new PeerStore({ datastore, peerId, threshold: 1 }) - }) - - afterEach(() => peerStore.stop()) - - it('should store peerStore content on datastore after peer marked as dirty (threshold 1)', async () => { - const [peer] = await peerUtils.createPeerId({ number: 2 }) - const multiaddrs = [new Multiaddr('/ip4/156.10.1.22/tcp/1000')] - 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() - - // AddressBook - peerStore.addressBook.set(peer, multiaddrs) - - 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/' - } - - let count = 0 - for await (const _ of datastore.query(queryParams)) { // eslint-disable-line - count++ - } - expect(count).to.equal(2) - - // Validate data - const storedPeer = peerStore.get(peer) - expect(storedPeer.id.toB58String()).to.eql(peer.toB58String()) - expect(storedPeer.protocols).to.have.members(protocols) - expect(storedPeer.addresses.map((a) => a.multiaddr.toString())).to.have.members([multiaddrs[0].toString()]) - expect(storedPeer.addresses.map((a) => a.isCertified)).to.have.members([false]) - }) - - 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 = [ - new Multiaddr('/ip4/156.10.1.22/tcp/1000'), - new Multiaddr('/ip4/156.10.1.23/tcp/1000') - ] - const protocols = ['/ping/1.0.0'] - - await peerStore.start() - - // AddressBook - 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) - - // no batch commit as public key inline - - // 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', uint8ArrayFromString('earth')) - - // let batch commit complete - await Promise.all(commitSpy.returnValues) - - expect(spyDs).to.have.property('callCount', 5) // 2 Address + 2 Proto + 1 Metadata - expect(peerStore.peers.size).to.equal(2) - - await peerStore.stop() - peerStore.keyBook.data.clear() - peerStore.addressBook.data.clear() - peerStore.protoBook.data.clear() - - // Load on restart - const spy = sinon.spy(peerStore, '_processDatastoreEntry') - - await peerStore.start() - - 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(0) - expect(peerStore.protoBook.data.size).to.equal(2) - expect(peerStore.metadataBook.data.size).to.equal(1) - }) - - it('should delete content from the datastore on delete', async () => { - const [peer] = await peerUtils.createPeerId() - const multiaddrs = [new Multiaddr('/ip4/156.10.1.22/tcp/1000')] - const protocols = ['/ping/1.0.0'] - const commitSpy = sinon.spy(peerStore, '_commitData') - - await peerStore.start() - - // AddressBook - peerStore.addressBook.set(peer, multiaddrs) - // ProtoBook - peerStore.protoBook.set(peer, protocols) - // MetadataBook - peerStore.metadataBook.set(peer, 'location', uint8ArrayFromString('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') - const spyProtoBook = sinon.spy(peerStore.protoBook, 'delete') - const spyMetadataBook = sinon.spy(peerStore.metadataBook, 'delete') - - // 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) - expect(spyKeyBook).to.have.property('callCount', 1) - expect(spyProtoBook).to.have.property('callCount', 1) - expect(spyMetadataBook).to.have.property('callCount', 1) - expect(spyDs).to.have.property('callCount', 3) - - // Should have zero peer records stored in the datastore - const queryParams = { - prefix: '/peers/' - } - - for await (const _ of datastore.query(queryParams)) { // eslint-disable-line - throw new Error('Datastore should be empty') - } - }) - - it('should store certified peer records after peer marked as dirty (threshold 1)', async () => { - const [peerId] = await peerUtils.createPeerId() - const multiaddrs = [new Multiaddr('/ip4/156.10.1.22/tcp/1000')] - const spyDirty = sinon.spy(peerStore, '_addDirtyPeer') - const spyDs = sinon.spy(datastore, 'batch') - const commitSpy = sinon.spy(peerStore, '_commitData') - - await peerStore.start() - - const peerRecord = new PeerRecord({ - peerId, - multiaddrs - }) - const envelope = await Envelope.seal(peerRecord, peerId) - - // consume peer record - const consumed = peerStore.addressBook.consumePeerRecord(envelope) - expect(consumed).to.eql(true) - expect(spyDirty).to.have.property('callCount', 1) // Address - expect(spyDs).to.have.property('callCount', 1) - - // let batch commit complete - await Promise.all(commitSpy.returnValues) - - // Should have three peer records stored in the datastore - const queryParams = { - prefix: '/peers/' - } - - let count = 0 - for await (const _ of datastore.query(queryParams)) { // eslint-disable-line - count++ - } - expect(count).to.equal(1) - - // Validate data - const storedPeer = peerStore.get(peerId) - expect(storedPeer.id.toB58String()).to.eql(peerId.toB58String()) - expect(storedPeer.addresses.map((a) => a.multiaddr.toString())).to.have.members([multiaddrs[0].toString()]) - expect(storedPeer.addresses.map((a) => a.isCertified)).to.have.members([true]) - }) - - it('should load certified peer records 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 = [ - new Multiaddr('/ip4/156.10.1.22/tcp/1000'), - new Multiaddr('/ip4/156.10.1.23/tcp/1000') - ] - const peerRecord0 = new PeerRecord({ - peerId: peers[0], - multiaddrs: [multiaddrs[0]] - }) - const envelope0 = await Envelope.seal(peerRecord0, peers[0]) - const peerRecord1 = new PeerRecord({ - peerId: peers[1], - multiaddrs: [multiaddrs[1]] - }) - const envelope1 = await Envelope.seal(peerRecord1, peers[1]) - - await peerStore.start() - - // AddressBook - let consumed = peerStore.addressBook.consumePeerRecord(envelope0) - expect(consumed).to.eql(true) - consumed = peerStore.addressBook.consumePeerRecord(envelope1) - expect(consumed).to.eql(true) - - // let batch commit complete - await Promise.all(commitSpy.returnValues) - - expect(spyDs).to.have.property('callCount', 2) // 2 Address + 2 Key + 2 Proto + 1 Metadata - expect(peerStore.peers.size).to.equal(2) - - await peerStore.stop() - peerStore.addressBook.data.clear() - - // Load on restart - const spy = sinon.spy(peerStore, '_processDatastoreEntry') - - await peerStore.start() - - expect(spy).to.have.property('callCount', 2) - expect(spyDs).to.have.property('callCount', 2) - - expect(peerStore.peers.size).to.equal(2) - expect(peerStore.addressBook.data.size).to.equal(2) - - expect(peerStore.addressBook.getRawEnvelope(peers[0])).to.exist() - expect(peerStore.addressBook.getRawEnvelope(peers[1])).to.exist() - - // Validate stored envelopes - const storedEnvelope0 = await peerStore.addressBook.getPeerRecord(peers[0]) - expect(envelope0.equals(storedEnvelope0)).to.eql(true) - - const storedEnvelope1 = await peerStore.addressBook.getPeerRecord(peers[1]) - expect(envelope1.equals(storedEnvelope1)).to.eql(true) - - // Validate multiaddrs - const storedPeer0 = peerStore.get(peers[0]) - expect(storedPeer0.id.toB58String()).to.eql(peers[0].toB58String()) - expect(storedPeer0.addresses.map((a) => a.multiaddr.toString())).to.have.members([multiaddrs[0].toString()]) - expect(storedPeer0.addresses.map((a) => a.isCertified)).to.have.members([true]) - - const storedPeer1 = peerStore.get(peers[1]) - expect(storedPeer1.id.toB58String()).to.eql(peers[1].toB58String()) - expect(storedPeer1.addresses.map((a) => a.multiaddr.toString())).to.have.members([multiaddrs[1].toString()]) - expect(storedPeer1.addresses.map((a) => a.isCertified)).to.have.members([true]) - }) - - it('should delete certified peer records from the datastore on delete', async () => { - const [peer] = await peerUtils.createPeerId() - const multiaddrs = [new Multiaddr('/ip4/156.10.1.22/tcp/1000')] - const commitSpy = sinon.spy(peerStore, '_commitData') - - await peerStore.start() - - // AddressBook - const peerRecord = new PeerRecord({ - peerId: peer, - multiaddrs - }) - const envelope = await Envelope.seal(peerRecord, peer) - - // consume peer record - const consumed = peerStore.addressBook.consumePeerRecord(envelope) - expect(consumed).to.eql(true) - - // let batch commit complete - await Promise.all(commitSpy.returnValues) - expect(peerStore.addressBook.getRawEnvelope(peer)).to.exist() - - const spyDs = sinon.spy(datastore, 'batch') - const spyAddressBook = sinon.spy(peerStore.addressBook, 'delete') - - // 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) - expect(spyDs).to.have.property('callCount', 1) - - // Should have zero peer records stored in the datastore - const queryParams = { - prefix: '/peers/' - } - - for await (const _ of datastore.query(queryParams)) { // eslint-disable-line - throw new Error('Datastore should be empty') - } - - expect(peerStore.addressBook.getRawEnvelope(peer)).to.not.exist() - }) - }) - - describe('setup with content not stored per change (threshold 2)', () => { - beforeEach(() => { - datastore = new MemoryDatastore() - peerStore = new PeerStore({ datastore, peerId, threshold: 2 }) - }) - - afterEach(() => peerStore.stop()) - - it('should not commit until threshold is reached', async () => { - 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 }) - - const multiaddrs = [new Multiaddr('/ip4/156.10.1.22/tcp/1000')] - const protocols = ['/ping/1.0.0'] - - await peerStore.start() - - expect(spyDirty).to.have.property('callCount', 0) - expect(spyDs).to.have.property('callCount', 0) - - // Add Peer0 data in multiple books - peerStore.addressBook.set(peers[0], multiaddrs) - peerStore.protoBook.set(peers[0], protocols) - peerStore.metadataBook.set(peers[0], 'location', uint8ArrayFromString('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) - expect(spyDs).to.have.property('callCount', 0) - - const queryParams = { - prefix: '/peers/' - } - for await (const _ of datastore.query(queryParams)) { // eslint-disable-line - throw new Error('Datastore should be empty') - } - - // 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) - - // Should have three peer records stored in the datastore - let count = 0 - for await (const _ of datastore.query(queryParams)) { // eslint-disable-line - count++ - } - expect(count).to.equal(3) - expect(peerStore.peers.size).to.equal(2) - }) - - it('should commit on stop if threshold was not reached', async () => { - const spyDirty = sinon.spy(peerStore, '_addDirtyPeer') - const spyDs = sinon.spy(datastore, 'batch') - - const protocols = ['/ping/1.0.0'] - const [peer] = await peerUtils.createPeerId() - - await peerStore.start() - - // Add Peer data in a book - peerStore.protoBook.set(peer, protocols) - - expect(spyDs).to.have.property('callCount', 0) - expect(spyDirty).to.have.property('callCount', 1) // ProtoBook - expect(peerStore._dirtyPeers.size).to.equal(1) - - const queryParams = { - prefix: '/peers/' - } - for await (const _ of datastore.query(queryParams)) { // eslint-disable-line - throw new Error('Datastore should be empty') - } - - await peerStore.stop() - - expect(spyDirty).to.have.property('callCount', 1) - expect(spyDs).to.have.property('callCount', 1) - expect(peerStore._dirtyPeers.size).to.equal(0) // Reset - - // Should have one peer record stored in the datastore - let count = 0 - for await (const _ of datastore.query(queryParams)) { // eslint-disable-line - count++ - } - expect(count).to.equal(1) - expect(peerStore.peers.size).to.equal(1) - }) - }) -}) - -describe('libp2p.peerStore (Persisted)', () => { - describe('disabled by default', () => { - let libp2p - - before(async () => { - [libp2p] = await peerUtils.createPeer({ - started: false - }) - }) - - afterEach(() => libp2p.stop()) - - it('should not have have persistence capabilities', async () => { - await libp2p.start() - expect(libp2p.peerStore._dirtyPeers).to.not.exist() - expect(libp2p.peerStore.threshold).to.not.exist() - }) - }) - - describe('enabled', () => { - let libp2p - let memoryDatastore - - beforeEach(async () => { - memoryDatastore = new MemoryDatastore() - ;[libp2p] = await peerUtils.createPeer({ - started: false, - config: { - datastore: memoryDatastore, - peerStore: { - persistence: true, - threshold: 2 // trigger on second peer changed - } - } - }) - }) - - afterEach(() => libp2p.stop()) - - it('should start on libp2p start and load content', async () => { - const spyPeerStore = sinon.spy(libp2p.peerStore, 'start') - const spyDs = sinon.spy(memoryDatastore, 'query') - - await libp2p.start() - expect(spyPeerStore).to.have.property('callCount', 1) - expect(spyDs).to.have.property('callCount', 1) - }) - - 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: 3 }) - const multiaddrs = [ - new Multiaddr('/ip4/156.10.1.22/tcp/1000'), - new Multiaddr('/ip4/156.10.1.23/tcp/1000') - ] - const protocols = ['/ping/1.0.0'] - - await libp2p.start() - - // AddressBook - libp2p.peerStore.addressBook.set(peers[1], [multiaddrs[0]]) - libp2p.peerStore.addressBook.set(peers[2], [multiaddrs[1]]) - - // let batch commit complete - await Promise.all(commitSpy.returnValues) - - // ProtoBook - libp2p.peerStore.protoBook.set(peers[1], protocols) - libp2p.peerStore.protoBook.set(peers[2], protocols) - - // let batch commit complete - await Promise.all(commitSpy.returnValues) - - expect(libp2p.peerStore.peers.size).to.equal(2) - - await libp2p.stop() - - // Use a new node with the previously populated datastore - const [newNode] = await peerUtils.createPeer({ - started: false, - config: { - datastore: memoryDatastore, - peerStore: { - persistence: true - }, - config: { - peerDiscovery: { - autoDial: false - } - } - } - }) - - expect(newNode.peerStore.peers.size).to.equal(0) - - const spy = sinon.spy(newNode.peerStore, '_processDatastoreEntry') - - await newNode.start() - - expect(spy).to.have.property('callCount', 4) // 4 datastore entries - - expect(newNode.peerStore.peers.size).to.equal(2) - - // Validate data - const peer0 = newNode.peerStore.get(peers[1]) - expect(peer0.id.toB58String()).to.eql(peers[1].toB58String()) - expect(peer0.protocols).to.have.members(protocols) - expect(peer0.addresses.map((a) => a.multiaddr.toString())).to.have.members([multiaddrs[0].toString()]) - - const peer1 = newNode.peerStore.get(peers[2]) - expect(peer1.id.toB58String()).to.eql(peers[2].toB58String()) - expect(peer1.protocols).to.have.members(protocols) - expect(peer1.addresses.map((a) => a.multiaddr.toString())).to.have.members([multiaddrs[1].toString()]) - - await newNode.stop() - }) - }) -}) diff --git a/test/peer-store/proto-book.spec.js b/test/peer-store/proto-book.spec.js index db05955f69..667ec4aa04 100644 --- a/test/peer-store/proto-book.spec.js +++ b/test/peer-store/proto-book.spec.js @@ -3,7 +3,7 @@ const { expect } = require('aegir/utils/chai') const sinon = require('sinon') - +const { MemoryDatastore } = require('datastore-core/memory') const pDefer = require('p-defer') const pWaitFor = require('p-wait-for') @@ -11,12 +11,19 @@ const PeerStore = require('../../src/peer-store') const peerUtils = require('../utils/creators/peer') const { - ERR_INVALID_PARAMETERS + codes: { ERR_INVALID_PARAMETERS } } = require('../../src/errors') +/** + * @typedef {import('../../src/peer-store/types').PeerStore} PeerStore + * @typedef {import('../../src/peer-store/types').ProtoBook} ProtoBook + * @typedef {import('peer-id')} PeerId + */ + const arraysAreEqual = (a, b) => a.length === b.length && a.sort().every((item, index) => b[index] === item) describe('protoBook', () => { + /** @type {PeerId} */ let peerId before(async () => { @@ -24,10 +31,16 @@ describe('protoBook', () => { }) describe('protoBook.set', () => { - let peerStore, pb + /** @type {PeerStore} */ + let peerStore + /** @type {ProtoBook} */ + let pb beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) pb = peerStore.protoBook }) @@ -35,19 +48,15 @@ describe('protoBook', () => { peerStore.removeAllListeners() }) - it('throwns invalid parameters error if invalid PeerId is provided', () => { - expect(() => { - pb.set('invalid peerId') - }).to.throw(ERR_INVALID_PARAMETERS) + it('throws invalid parameters error if invalid PeerId is provided', async () => { + await expect(pb.set('invalid peerId')).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) }) - it('throwns invalid parameters error if no protocols provided', () => { - expect(() => { - pb.set(peerId) - }).to.throw(ERR_INVALID_PARAMETERS) + it('throws invalid parameters error if no protocols provided', async () => { + await expect(pb.set(peerId)).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) }) - it('replaces the stored content by default and emit change event', () => { + it('replaces the stored content by default and emit change event', async () => { const defer = pDefer() const supportedProtocols = ['protocol1', 'protocol2'] @@ -57,14 +66,14 @@ describe('protoBook', () => { defer.resolve() }) - pb.set(peerId, supportedProtocols) - const protocols = pb.get(peerId) + await pb.set(peerId, supportedProtocols) + const protocols = await pb.get(peerId) expect(protocols).to.have.deep.members(supportedProtocols) - return defer.promise + await defer.promise }) - it('emits on set if not storing the exact same content', () => { + it('emits on set if not storing the exact same content', async () => { const defer = pDefer() const supportedProtocolsA = ['protocol1', 'protocol2'] @@ -79,17 +88,17 @@ describe('protoBook', () => { }) // set 1 - pb.set(peerId, supportedProtocolsA) + await pb.set(peerId, supportedProtocolsA) // set 2 (same content) - pb.set(peerId, supportedProtocolsB) - const protocols = pb.get(peerId) + await pb.set(peerId, supportedProtocolsB) + const protocols = await pb.get(peerId) expect(protocols).to.have.deep.members(supportedProtocolsB) - return defer.promise + await defer.promise }) - it('does not emit on set if it is storing the exact same content', () => { + it('does not emit on set if it is storing the exact same content', async () => { const defer = pDefer() const supportedProtocols = ['protocol1', 'protocol2'] @@ -103,10 +112,10 @@ describe('protoBook', () => { }) // set 1 - pb.set(peerId, supportedProtocols) + await pb.set(peerId, supportedProtocols) // set 2 (same content) - pb.set(peerId, supportedProtocols) + await pb.set(peerId, supportedProtocols) // Wait 50ms for incorrect second event setTimeout(() => { @@ -118,10 +127,16 @@ describe('protoBook', () => { }) describe('protoBook.add', () => { - let peerStore, pb + /** @type {PeerStore} */ + let peerStore + /** @type {ProtoBook} */ + let pb beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) pb = peerStore.protoBook }) @@ -129,19 +144,15 @@ describe('protoBook', () => { peerStore.removeAllListeners() }) - it('throwns invalid parameters error if invalid PeerId is provided', () => { - expect(() => { - pb.add('invalid peerId') - }).to.throw(ERR_INVALID_PARAMETERS) + it('throws invalid parameters error if invalid PeerId is provided', async () => { + await expect(pb.add('invalid peerId')).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) }) - it('throwns invalid parameters error if no protocols provided', () => { - expect(() => { - pb.add(peerId) - }).to.throw(ERR_INVALID_PARAMETERS) + it('throws invalid parameters error if no protocols provided', async () => { + await expect(pb.add(peerId)).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) }) - it('adds the new content and emits change event', () => { + it('adds the new content and emits change event', async () => { const defer = pDefer() const supportedProtocolsA = ['protocol1', 'protocol2'] @@ -157,19 +168,19 @@ describe('protoBook', () => { }) // Replace - pb.set(peerId, supportedProtocolsA) - let protocols = pb.get(peerId) + await pb.set(peerId, supportedProtocolsA) + let protocols = await pb.get(peerId) expect(protocols).to.have.deep.members(supportedProtocolsA) // Add - pb.add(peerId, supportedProtocolsB) - protocols = pb.get(peerId) + await pb.add(peerId, supportedProtocolsB) + protocols = await pb.get(peerId) expect(protocols).to.have.deep.members(finalProtocols) return defer.promise }) - it('emits on add if the content to add not exists', () => { + it('emits on add if the content to add not exists', async () => { const defer = pDefer() const supportedProtocolsA = ['protocol1'] @@ -185,17 +196,17 @@ describe('protoBook', () => { }) // set 1 - pb.set(peerId, supportedProtocolsA) + await pb.set(peerId, supportedProtocolsA) // set 2 (content already existing) - pb.add(peerId, supportedProtocolsB) - const protocols = pb.get(peerId) + await pb.add(peerId, supportedProtocolsB) + const protocols = await pb.get(peerId) expect(protocols).to.have.deep.members(finalProtocols) return defer.promise }) - it('does not emit on add if the content to add already exists', () => { + it('does not emit on add if the content to add already exists', async () => { const defer = pDefer() const supportedProtocolsA = ['protocol1', 'protocol2'] @@ -210,10 +221,10 @@ describe('protoBook', () => { }) // set 1 - pb.set(peerId, supportedProtocolsA) + await pb.set(peerId, supportedProtocolsA) // set 2 (content already existing) - pb.add(peerId, supportedProtocolsB) + await pb.add(peerId, supportedProtocolsB) // Wait 50ms for incorrect second event setTimeout(() => { @@ -225,10 +236,16 @@ describe('protoBook', () => { }) describe('protoBook.remove', () => { - let peerStore, pb + /** @type {PeerStore} */ + let peerStore + /** @type {ProtoBook} */ + let pb beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) pb = peerStore.protoBook }) @@ -236,16 +253,12 @@ describe('protoBook', () => { 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 invalid PeerId is provided', async () => { + await expect(pb.remove('invalid peerId')).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) }) - it('throws invalid parameters error if no protocols provided', () => { - expect(() => { - pb.remove(peerId) - }).to.throw(ERR_INVALID_PARAMETERS) + it('throws invalid parameters error if no protocols provided', async () => { + await expect(pb.remove(peerId)).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) }) it('removes the given protocol and emits change event', async () => { @@ -258,13 +271,13 @@ describe('protoBook', () => { peerStore.on('change:protocols', spy) // Replace - pb.set(peerId, supportedProtocols) - let protocols = pb.get(peerId) + await pb.set(peerId, supportedProtocols) + let protocols = await pb.get(peerId) expect(protocols).to.have.deep.members(supportedProtocols) // Remove - pb.remove(peerId, removedProtocols) - protocols = pb.get(peerId) + await pb.remove(peerId, removedProtocols) + protocols = await pb.get(peerId) expect(protocols).to.have.deep.members(finalProtocols) await pWaitFor(() => spy.callCount === 2) @@ -275,7 +288,7 @@ describe('protoBook', () => { expect(arraysAreEqual(secondCallArgs.protocols, finalProtocols)) }) - it('emits on remove if the content changes', () => { + it('emits on remove if the content changes', async () => { const spy = sinon.spy() const supportedProtocols = ['protocol1', 'protocol2'] @@ -285,17 +298,17 @@ describe('protoBook', () => { peerStore.on('change:protocols', spy) // set - pb.set(peerId, supportedProtocols) + await pb.set(peerId, supportedProtocols) // remove (content already existing) - pb.remove(peerId, removedProtocols) - const protocols = pb.get(peerId) + await pb.remove(peerId, removedProtocols) + const protocols = await 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', () => { + it('does not emit on remove if the content does not change', async () => { const spy = sinon.spy() const supportedProtocols = ['protocol1', 'protocol2'] @@ -304,10 +317,10 @@ describe('protoBook', () => { peerStore.on('change:protocols', spy) // set - pb.set(peerId, supportedProtocols) + await pb.set(peerId, supportedProtocols) // remove - pb.remove(peerId, removedProtocols) + await pb.remove(peerId, removedProtocols) // Only one event expect(spy.callCount).to.eql(1) @@ -315,73 +328,79 @@ describe('protoBook', () => { }) describe('protoBook.get', () => { - let peerStore, pb + /** @type {PeerStore} */ + let peerStore + /** @type {ProtoBook} */ + let pb beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) pb = peerStore.protoBook }) - it('throwns invalid parameters error if invalid PeerId is provided', () => { - expect(() => { - pb.get('invalid peerId') - }).to.throw(ERR_INVALID_PARAMETERS) + it('throws invalid parameters error if invalid PeerId is provided', async () => { + await expect(pb.get('invalid peerId')).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) }) - it('returns undefined if no protocols are known for the provided peer', () => { - const protocols = pb.get(peerId) + it('returns empty if no protocols are known for the provided peer', async () => { + const protocols = await pb.get(peerId) - expect(protocols).to.not.exist() + expect(protocols).to.be.empty() }) - it('returns the protocols stored', () => { + it('returns the protocols stored', async () => { const supportedProtocols = ['protocol1', 'protocol2'] - pb.set(peerId, supportedProtocols) + await pb.set(peerId, supportedProtocols) - const protocols = pb.get(peerId) + const protocols = await pb.get(peerId) expect(protocols).to.have.deep.members(supportedProtocols) }) }) describe('protoBook.delete', () => { - let peerStore, pb + /** @type {PeerStore} */ + let peerStore + /** @type {ProtoBook} */ + let pb beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) pb = peerStore.protoBook }) - it('throwns invalid parameters error if invalid PeerId is provided', () => { - expect(() => { - pb.delete('invalid peerId') - }).to.throw(ERR_INVALID_PARAMETERS) + it('throws invalid parameters error if invalid PeerId is provided', async () => { + await expect(pb.delete('invalid peerId')).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) }) - it('returns false if no records exist for the peer and no event is emitted', () => { + it('should not emit event if no records exist for the peer', async () => { const defer = pDefer() peerStore.on('change:protocols', () => { defer.reject() }) - const deleted = pb.delete(peerId) - - expect(deleted).to.equal(false) + await pb.delete(peerId) // Wait 50ms for incorrect invalid event setTimeout(() => { defer.resolve() }, 50) - return defer.promise + await defer.promise }) - it('returns true if the record exists and an event is emitted', () => { + it('should emit event if a record exists for the peer', async () => { const defer = pDefer() const supportedProtocols = ['protocol1', 'protocol2'] - pb.set(peerId, supportedProtocols) + await pb.set(peerId, supportedProtocols) // Listen after set peerStore.on('change:protocols', ({ protocols }) => { @@ -389,11 +408,9 @@ describe('protoBook', () => { defer.resolve() }) - const deleted = pb.delete(peerId) - - expect(deleted).to.equal(true) + await pb.delete(peerId) - return defer.promise + await defer.promise }) }) }) diff --git a/test/registrar/registrar.spec.js b/test/registrar/registrar.spec.js index 8f90b9fcf4..b86f52a8fa 100644 --- a/test/registrar/registrar.spec.js +++ b/test/registrar/registrar.spec.js @@ -5,7 +5,7 @@ const { expect } = require('aegir/utils/chai') const pDefer = require('p-defer') const { EventEmitter } = require('events') - +const { MemoryDatastore } = require('datastore-core/memory') const Topology = require('libp2p-interfaces/src/topology/multicodec-topology') const PeerStore = require('../../src/peer-store') const Registrar = require('../../src/registrar') @@ -27,19 +27,23 @@ describe('registrar', () => { describe('errors', () => { beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) registrar = new Registrar({ peerStore, connectionManager: new EventEmitter() }) }) it('should fail to register a protocol if no multicodec is provided', () => { - expect(() => registrar.register()).to.throw() + return expect(registrar.register()).to.eventually.be.rejected() }) it('should fail to register a protocol if an invalid topology is provided', () => { const fakeTopology = { random: 1 } - expect(() => registrar.register(fakeTopology)).to.throw() + + return expect(registrar.register(fakeTopology)).to.eventually.be.rejected() }) }) @@ -57,7 +61,7 @@ describe('registrar', () => { afterEach(() => libp2p.stop()) - it('should be able to register a protocol', () => { + it('should be able to register a protocol', async () => { const topologyProps = new Topology({ multicodecs: multicodec, handlers: { @@ -66,12 +70,12 @@ describe('registrar', () => { } }) - const identifier = libp2p.registrar.register(topologyProps) + const identifier = await libp2p.registrar.register(topologyProps) expect(identifier).to.exist() }) - it('should be able to unregister a protocol', () => { + it('should be able to unregister a protocol', async () => { const topologyProps = new Topology({ multicodecs: multicodec, handlers: { @@ -80,7 +84,7 @@ describe('registrar', () => { } }) - const identifier = libp2p.registrar.register(topologyProps) + const identifier = await libp2p.registrar.register(topologyProps) const success = libp2p.registrar.unregister(identifier) expect(success).to.eql(true) @@ -100,12 +104,6 @@ describe('registrar', () => { const conn = await createMockConnection() const remotePeerId = conn.remotePeer - // Add connected peer with protocol to peerStore and registrar - libp2p.peerStore.protoBook.add(remotePeerId, [multicodec]) - - libp2p.connectionManager.onConnect(conn) - expect(libp2p.connectionManager.size).to.eql(1) - const topologyProps = new Topology({ multicodecs: multicodec, handlers: { @@ -124,12 +122,18 @@ describe('registrar', () => { }) // Register protocol - const identifier = libp2p.registrar.register(topologyProps) + const identifier = await libp2p.registrar.register(topologyProps) const topology = libp2p.registrar.topologies.get(identifier) // Topology created expect(topology).to.exist() + // Add connected peer with protocol to peerStore and registrar + await libp2p.peerStore.protoBook.add(remotePeerId, [multicodec]) + + await libp2p.connectionManager.onConnect(conn) + expect(libp2p.connectionManager.size).to.eql(1) + await conn.close() libp2p.connectionManager.onDisconnect(conn) @@ -159,7 +163,7 @@ describe('registrar', () => { }) // Register protocol - const identifier = libp2p.registrar.register(topologyProps) + const identifier = await libp2p.registrar.register(topologyProps) const topology = libp2p.registrar.topologies.get(identifier) // Topology created @@ -171,16 +175,16 @@ describe('registrar', () => { const remotePeerId = conn.remotePeer // Add connected peer to peerStore and registrar - libp2p.peerStore.protoBook.set(remotePeerId, []) - libp2p.connectionManager.onConnect(conn) + await libp2p.peerStore.protoBook.set(remotePeerId, []) // Add protocol to peer and update it - libp2p.peerStore.protoBook.add(remotePeerId, [multicodec]) + await libp2p.peerStore.protoBook.add(remotePeerId, [multicodec]) + await libp2p.connectionManager.onConnect(conn) await onConnectDefer.promise // Remove protocol to peer and update it - libp2p.peerStore.protoBook.set(remotePeerId, []) + await libp2p.peerStore.protoBook.set(remotePeerId, []) await onDisconnectDefer.promise }) diff --git a/test/relay/auto-relay.node.js b/test/relay/auto-relay.node.js index aaddd21491..5ec2643e06 100644 --- a/test/relay/auto-relay.node.js +++ b/test/relay/auto-relay.node.js @@ -82,7 +82,7 @@ describe('auto-relay', () => { const originalMultiaddrsLength = relayLibp2p.multiaddrs.length // Discover relay - libp2p.peerStore.addressBook.add(relayLibp2p.peerId, relayLibp2p.multiaddrs) + await libp2p.peerStore.addressBook.add(relayLibp2p.peerId, relayLibp2p.multiaddrs) await libp2p.dial(relayLibp2p.peerId) // Wait for peer added as listen relay @@ -94,7 +94,7 @@ describe('auto-relay', () => { expect(libp2p.multiaddrs[originalMultiaddrsLength].getPeerId()).to.eql(relayLibp2p.peerId.toB58String()) // Peer has relay multicodec - const knownProtocols = libp2p.peerStore.protoBook.get(relayLibp2p.peerId) + const knownProtocols = await libp2p.peerStore.protoBook.get(relayLibp2p.peerId) expect(knownProtocols).to.include(relayMulticodec) }) }) @@ -165,7 +165,7 @@ describe('auto-relay', () => { sinon.spy(autoRelay1, '_addListenRelay') // Discover relay - relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) const originalMultiaddrs1Length = relayLibp2p1.multiaddrs.length const originalMultiaddrs2Length = relayLibp2p2.multiaddrs.length @@ -184,7 +184,7 @@ describe('auto-relay', () => { expect(relayLibp2p1.multiaddrs[originalMultiaddrs1Length].getPeerId()).to.eql(relayLibp2p2.peerId.toB58String()) // Peer has relay multicodec - const knownProtocols = relayLibp2p1.peerStore.protoBook.get(relayLibp2p2.peerId) + const knownProtocols = await relayLibp2p1.peerStore.protoBook.get(relayLibp2p2.peerId) expect(knownProtocols).to.include(relayMulticodec) }) @@ -193,7 +193,7 @@ describe('auto-relay', () => { const originalMultiaddrs2Length = relayLibp2p2.multiaddrs.length // Discover relay - relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) await relayLibp2p1.dial(relayLibp2p2.peerId) @@ -206,7 +206,7 @@ describe('auto-relay', () => { // Dial from the other through a relay const relayedMultiaddr2 = new Multiaddr(`${relayLibp2p1.multiaddrs[0]}/p2p/${relayLibp2p1.peerId.toB58String()}/p2p-circuit`) - libp2p.peerStore.addressBook.add(relayLibp2p2.peerId, [relayedMultiaddr2]) + await libp2p.peerStore.addressBook.add(relayLibp2p2.peerId, [relayedMultiaddr2]) await libp2p.dial(relayLibp2p2.peerId) }) @@ -220,7 +220,7 @@ describe('auto-relay', () => { sinon.spy(autoRelay1._listenRelays, 'add') // Discover one relay and connect - relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) await relayLibp2p1.dial(relayLibp2p2.peerId) expect(relayLibp2p1.connectionManager.size).to.eql(1) @@ -237,11 +237,11 @@ describe('auto-relay', () => { expect(relayLibp2p1.multiaddrs[originalMultiaddrs1Length].getPeerId()).to.eql(relayLibp2p2.peerId.toB58String()) // Relay2 has relay multicodec - const knownProtocols2 = relayLibp2p1.peerStore.protoBook.get(relayLibp2p2.peerId) + const knownProtocols2 = await 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.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 @@ -253,7 +253,7 @@ describe('auto-relay', () => { expect(relayLibp2p1.connectionManager.size).to.eql(2) // Relay2 has relay multicodec - const knownProtocols3 = relayLibp2p1.peerStore.protoBook.get(relayLibp2p3.peerId) + const knownProtocols3 = await relayLibp2p1.peerStore.protoBook.get(relayLibp2p3.peerId) expect(knownProtocols3).to.include(relayMulticodec) }) @@ -264,7 +264,7 @@ describe('auto-relay', () => { sinon.spy(relayLibp2p1.identifyService, 'pushToPeerStore') // Discover one relay and connect - relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) await relayLibp2p1.dial(relayLibp2p2.peerId) // Wait for listenning on the relay @@ -283,7 +283,7 @@ describe('auto-relay', () => { expect(autoRelay1._listenRelays.size).to.equal(0) // Identify push for removing listen relay multiaddr - expect(relayLibp2p1.identifyService.pushToPeerStore.callCount).to.equal(2) + await pWaitFor(() => relayLibp2p1.identifyService.pushToPeerStore.callCount === 2) }) it('should try to listen on other connected peers relayed address if one used relay disconnects', async () => { @@ -294,11 +294,11 @@ describe('auto-relay', () => { sinon.spy(relayLibp2p1.transportManager, 'listen') // Discover one relay and connect - relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) + await 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.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) await relayLibp2p1.dial(relayLibp2p3.peerId) // Wait for both peer to be attempted to added as listen relay @@ -337,11 +337,11 @@ describe('auto-relay', () => { sinon.spy(relayLibp2p1.transportManager, 'listen') // Discover one relay and connect - relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) + await 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.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) await relayLibp2p1.dial(relayLibp2p3.peerId) // Wait for both peer to be attempted to added as listen relay @@ -382,11 +382,11 @@ describe('auto-relay', () => { sinon.spy(relayLibp2p1.transportManager, 'listen') // Discover one relay and connect - relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) + await 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.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) await relayLibp2p1.dial(relayLibp2p3.peerId) // Wait for both peer to be attempted to added as listen relay @@ -479,7 +479,7 @@ describe('auto-relay', () => { sinon.spy(autoRelay2, '_addListenRelay') // Relay 1 discovers Relay 3 and connect - relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) await relayLibp2p1.dial(relayLibp2p3.peerId) // Wait for peer added as listen relay @@ -487,7 +487,7 @@ describe('auto-relay', () => { expect(autoRelay1._listenRelays.size).to.equal(1) // Relay 2 discovers Relay 3 and connect - relayLibp2p2.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) + await relayLibp2p2.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) await relayLibp2p2.dial(relayLibp2p3.peerId) // Wait for peer added as listen relay @@ -496,7 +496,7 @@ describe('auto-relay', () => { // 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.peerStore.addressBook.add(relayLibp2p2.peerId, [ma2RelayedBy3]) await relayLibp2p1.dial(relayLibp2p2.peerId) // Peer not added as listen relay diff --git a/test/relay/relay.node.js b/test/relay/relay.node.js index f2290a74e4..803a3849ae 100644 --- a/test/relay/relay.node.js +++ b/test/relay/relay.node.js @@ -8,7 +8,6 @@ const { Multiaddr } = require('multiaddr') const { collect } = require('streaming-iterables') const pipe = require('it-pipe') const AggregateError = require('aggregate-error') -const PeerId = require('peer-id') const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') const { createPeerId } = require('../utils/creators/peer') @@ -46,14 +45,13 @@ describe('Dialing (via relay, TCP)', () => { return Promise.all([srcLibp2p, relayLibp2p, dstLibp2p].map(libp2p => libp2p.start())) }) - afterEach(() => { + afterEach(async () => { // Stop each node return Promise.all([srcLibp2p, relayLibp2p, dstLibp2p].map(async libp2p => { await libp2p.stop() // Clear the peer stores - for (const peerIdStr of libp2p.peerStore.peers.keys()) { - const peerId = PeerId.createFromB58String(peerIdStr) - libp2p.peerStore.delete(peerId) + for await (const peer of libp2p.peerStore.getPeers()) { + libp2p.peerStore.delete(peer.id) } })) }) diff --git a/test/transports/transport-manager.node.js b/test/transports/transport-manager.node.js index 49876a0554..f762bbe452 100644 --- a/test/transports/transport-manager.node.js +++ b/test/transports/transport-manager.node.js @@ -2,7 +2,7 @@ /* eslint-env mocha */ const { expect } = require('aegir/utils/chai') - +const { MemoryDatastore } = require('datastore-core/memory') const AddressManager = require('../../src/address-manager') const TransportManager = require('../../src/transport-manager') const PeerStore = require('../../src/peer-store') @@ -33,7 +33,10 @@ describe('Transport Manager (TCP)', () => { peerId: localPeer, multiaddrs: addrs, addressManager: new AddressManager({ listen: addrs }), - peerStore: new PeerStore({ peerId: localPeer }) + peerStore: new PeerStore({ + peerId: localPeer, + datastore: new MemoryDatastore() + }) }, upgrader: mockUpgrader, onConnection: () => {} @@ -67,7 +70,7 @@ describe('Transport Manager (TCP)', () => { }) it('should create self signed peer record on listen', async () => { - let signedPeerRecord = await tm.libp2p.peerStore.addressBook.getPeerRecord(localPeer) + let signedPeerRecord = await tm.libp2p.peerStore.addressBook.getRawEnvelope(localPeer) expect(signedPeerRecord).to.not.exist() tm.add(Transport.prototype[Symbol.toStringTag], Transport) diff --git a/test/ts-use/package.json b/test/ts-use/package.json index ac98a7b4e3..87ec9b5bf9 100644 --- a/test/ts-use/package.json +++ b/test/ts-use/package.json @@ -2,17 +2,17 @@ "name": "ts-use", "private": true, "dependencies": { - "datastore-level": "^6.0.0", - "ipfs-http-client": "^50.1.2", + "@achingbrain/libp2p-gossipsub": "^0.12.2", + "@chainsafe/libp2p-noise": "^5.0.0", + "datastore-level": "^7.0.1", + "ipfs-http-client": "^55.0.0", "libp2p": "file:../..", - "libp2p-bootstrap": "^0.13.0", + "libp2p-bootstrap": "^0.14.0", "libp2p-delegated-content-routing": "^0.11.0", "libp2p-delegated-peer-routing": "^0.11.1", - "libp2p-gossipsub": "^0.9.0", - "libp2p-interfaces": "^1.0.1", - "libp2p-kad-dht": "^0.26.5", + "libp2p-interfaces": "^4.0.0", + "libp2p-kad-dht": "^0.28.6", "libp2p-mplex": "^0.10.4", - "@chainsafe/libp2p-noise": "^4.1.0", "libp2p-record": "^0.10.4", "libp2p-tcp": "^0.17.1", "libp2p-websockets": "^0.16.1", diff --git a/test/ts-use/src/main.ts b/test/ts-use/src/main.ts index 17c5e4d681..15b927b80d 100644 --- a/test/ts-use/src/main.ts +++ b/test/ts-use/src/main.ts @@ -105,8 +105,7 @@ async function main() { }, datastore: new LevelStore('path/to/store'), peerStore: { - persistence: false, - threshold: 5 + persistence: false }, keychain: { pass: 'notsafepassword123456789', diff --git a/test/utils/creators/peer.js b/test/utils/creators/peer.js index c9f213f44e..ef93d237dd 100644 --- a/test/utils/creators/peer.js +++ b/test/utils/creators/peer.js @@ -58,12 +58,13 @@ function _populateAddressBooks (peers) { * @param {Object} [properties] * @param {number} [properties.number] - number of peers (default: 1). * @param {boolean} [properties.fixture] - use fixture for peer-id generation (default: true) + * @param {PeerId.CreateOptions} [properties.opts] * @returns {Promise>} */ -function createPeerId ({ number = 1, fixture = true } = {}) { +function createPeerId ({ number = 1, fixture = true, opts = {} } = {}) { return pTimes(number, (i) => fixture ? PeerId.createFromJSON(Peers[i]) - : PeerId.create() + : PeerId.create(opts) ) } diff --git a/test/utils/mockConnection.js b/test/utils/mockConnection.js index 6c1439aad8..55c8d60646 100644 --- a/test/utils/mockConnection.js +++ b/test/utils/mockConnection.js @@ -48,7 +48,7 @@ module.exports = async (properties = {}) => { protocol: protocols[0] } }, - close: () => { }, + close: async () => { }, getStreams: () => openStreams, ...properties }) diff --git a/tsconfig.json b/tsconfig.json index 47b635e4af..0f357f9363 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,8 +10,7 @@ "src/circuit/protocol/index.js", // exclude generated file "src/identify/message.js", // exclude generated file "src/insecure/proto.js", // exclude generated file - "src/peer-store/persistent/pb/address-book.js", // exclude generated file - "src/peer-store/persistent/pb/proto-book.js", // exclude generated file + "src/peer-store/pb/peer.js", // exclude generated file "src/record/peer-record/peer-record.js", // exclude generated file "src/record/envelope/envelope.js" // exclude generated file ] From 13d45de376fb8a16e16827adae974abe1f9d52cc Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 20 Jan 2022 12:37:52 +0000 Subject: [PATCH 319/447] chore: add missing cache dir (#1125) Fixes ci config --- .github/workflows/examples.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index c99a803232..1d00ed588f 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -55,6 +55,7 @@ jobs: with: directories: | ./examples/node_modules + ~/.cache build: | cd examples npm i From 2963b852dbf7fc5e51da7e7c1d73bb375b944ca7 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 20 Jan 2022 13:19:38 +0000 Subject: [PATCH 320/447] chore: add automated releases (#1124) Adds release-please github action to create gated releases and a release issue that tracks all issues that will be in a release. --- .github/workflows/main.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3a4f76f00c..8e9f48a4cc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -53,6 +53,7 @@ jobs: - uses: ipfs/aegir/actions/cache-node-modules@master - run: npm run test:node -- --cov --bail - uses: codecov/codecov-action@v1 + test-chrome: needs: check runs-on: ubuntu-latest @@ -63,6 +64,7 @@ jobs: node-version: lts/* - uses: ipfs/aegir/actions/cache-node-modules@master - run: npm run test:browser -- -t browser -t webworker --bail + test-firefox: needs: check runs-on: ubuntu-latest @@ -73,6 +75,7 @@ jobs: node-version: lts/* - uses: ipfs/aegir/actions/cache-node-modules@master - run: npm run test:browser -- -t browser -t webworker --bail -- --browser firefox + test-ts: needs: check runs-on: ubuntu-latest @@ -83,6 +86,7 @@ jobs: node-version: lts/* - uses: ipfs/aegir/actions/cache-node-modules@master - run: npm run test:ts + test-interop: needs: check runs-on: ubuntu-latest @@ -93,3 +97,28 @@ jobs: node-version: lts/* - uses: ipfs/aegir/actions/cache-node-modules@master - run: npm run test:interop -- --bail -- --exit + + release: + runs-on: ubuntu-latest + needs: [test-node, test-chrome, test-firefox, test-ts, test-interop] + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + steps: + - uses: GoogleCloudPlatform/release-please-action@v2 + id: release + with: + token: ${{ secrets.GITHUB_TOKEN }} + release-type: node + bump-minor-pre-major: true + - uses: actions/checkout@v2 + if: ${{ steps.release.outputs.release_created }} + - uses: actions/setup-node@v2 + with: + node-version: lts/* + registry-url: 'https://registry.npmjs.org' + if: ${{ steps.release.outputs.release_created }} + - uses: bahmutov/npm-install@v1 + if: ${{ steps.release.outputs.release_created }} + - run: npm publish + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + if: ${{ steps.release.outputs.release_created }} From a0bfe8b53492827f99635a5e9ca5e9110cb8f6fc Mon Sep 17 00:00:00 2001 From: achingbrain Date: Thu, 20 Jan 2022 13:32:25 +0000 Subject: [PATCH 321/447] chore: restore correct cache --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8e9f48a4cc..8c4472903d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -116,7 +116,7 @@ jobs: node-version: lts/* registry-url: 'https://registry.npmjs.org' if: ${{ steps.release.outputs.release_created }} - - uses: bahmutov/npm-install@v1 + - uses: ipfs/aegir/actions/cache-node-modules@master if: ${{ steps.release.outputs.release_created }} - run: npm publish env: From 0264eb62ee98c0c9db992006a43a50f45823defc Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 20 Jan 2022 13:33:01 +0000 Subject: [PATCH 322/447] chore: update example tests (#1126) Remove some of the redundancy in the example tests --- examples/connection-encryption/test.js | 22 ++----------- examples/discovery-mechanisms/test-1.js | 20 ++--------- examples/peer-and-content-routing/test-1.js | 23 +++---------- examples/peer-and-content-routing/test-2.js | 22 +++---------- examples/pnet/test.js | 25 +++----------- examples/protocol-and-stream-muxing/test-1.js | 25 +++----------- examples/protocol-and-stream-muxing/test-2.js | 32 +++--------------- examples/protocol-and-stream-muxing/test-3.js | 31 +++-------------- examples/transports/test-1.js | 30 ++--------------- examples/transports/test-2.js | 22 ++----------- examples/transports/test-3.js | 33 ++----------------- examples/transports/test-4.js | 25 ++------------ 12 files changed, 42 insertions(+), 268 deletions(-) diff --git a/examples/connection-encryption/test.js b/examples/connection-encryption/test.js index 4574dd9acf..b6ea10ad5d 100644 --- a/examples/connection-encryption/test.js +++ b/examples/connection-encryption/test.js @@ -1,30 +1,14 @@ 'use strict' const path = require('path') -const execa = require('execa') -const pDefer = require('p-defer') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +const { waitForOutput } = require('../utils') async function test () { - const messageReceived = pDefer() process.stdout.write('1.js\n') - const proc = execa('node', [path.join(__dirname, '1.js')], { - cwd: path.resolve(__dirname), - all: true + await waitForOutput('This information is sent out encrypted to the other peer', 'node', [path.join(__dirname, '1.js')], { + cwd: __dirname }) - - proc.all.on('data', async (data) => { - process.stdout.write(data) - - const s = uint8ArrayToString(data) - if (s.includes('This information is sent out encrypted to the other peer')) { - messageReceived.resolve() - } - }) - - await messageReceived.promise - proc.kill() } module.exports = test diff --git a/examples/discovery-mechanisms/test-1.js b/examples/discovery-mechanisms/test-1.js index 36f297b3b3..73e90ad54c 100644 --- a/examples/discovery-mechanisms/test-1.js +++ b/examples/discovery-mechanisms/test-1.js @@ -1,27 +1,13 @@ 'use strict' const path = require('path') -const execa = require('execa') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +const { waitForOutput } = require('../utils') async function test () { process.stdout.write('1.js\n') - const proc = execa('node', [path.join(__dirname, '1.js')], { - cwd: path.resolve(__dirname), - all: true - }) - - let output = '' - - proc.all.on('data', async (data) => { - process.stdout.write(data) - output += uint8ArrayToString(data) - - // Discovered and connected - if (output.includes('Connection established to:')) { - proc.kill() - } + await waitForOutput('Connection established to:', 'node', [path.join(__dirname, '1.js')], { + cwd: __dirname }) } diff --git a/examples/peer-and-content-routing/test-1.js b/examples/peer-and-content-routing/test-1.js index 9e10f7aeee..43d6c1eb7e 100644 --- a/examples/peer-and-content-routing/test-1.js +++ b/examples/peer-and-content-routing/test-1.js @@ -1,28 +1,13 @@ 'use strict' const path = require('path') -const execa = require('execa') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +const { waitForOutput } = require('../utils') -async function test() { +async function test () { process.stdout.write('1.js\n') - const proc = execa('node', [path.join(__dirname, '1.js')], { - cwd: path.resolve(__dirname), - all: true - }) - - let output = '' - - proc.all.on('data', async (data) => { - process.stdout.write(data) - - output += uint8ArrayToString(data) - - // Discovered peers - if (output.includes('Found it, multiaddrs are:')) { - proc.kill() - } + await waitForOutput('Found it, multiaddrs are:', 'node', [path.join(__dirname, '1.js')], { + cwd: __dirname }) } diff --git a/examples/peer-and-content-routing/test-2.js b/examples/peer-and-content-routing/test-2.js index 644820b687..76c492de66 100644 --- a/examples/peer-and-content-routing/test-2.js +++ b/examples/peer-and-content-routing/test-2.js @@ -1,27 +1,13 @@ 'use strict' const path = require('path') -const execa = require('execa') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +const { waitForOutput } = require('../utils') -async function test() { +async function test () { process.stdout.write('2.js\n') - const proc = execa('node', [path.join(__dirname, '2.js')], { - cwd: path.resolve(__dirname), - all: true - }) - - let output = '' - - proc.all.on('data', async (data) => { - process.stdout.write(data) - - output += uint8ArrayToString(data) - - if (output.includes('Found provider:')) { - proc.kill() - } + await waitForOutput('Found provider:', 'node', [path.join(__dirname, '2.js')], { + cwd: __dirname }) } diff --git a/examples/pnet/test.js b/examples/pnet/test.js index 8a295dbb8e..56519927aa 100644 --- a/examples/pnet/test.js +++ b/examples/pnet/test.js @@ -1,30 +1,13 @@ 'use strict' const path = require('path') -const execa = require('execa') -const pDefer = require('p-defer') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +const { waitForOutput } = require('../utils') async function test () { - const messageReceived = pDefer() - process.stdout.write('index.js\n') - - const proc = execa('node', [path.join(__dirname, 'index.js')], { - cwd: path.resolve(__dirname), - all: true - }) - - proc.all.on('data', async (data) => { - process.stdout.write(data) - - const s = uint8ArrayToString(data) - if (s.includes('This message is sent on a private network')) { - messageReceived.resolve() - } + await waitForOutput('This message is sent on a private network', 'node', [path.join(__dirname, 'index.js')], { + cwd: __dirname }) - - await messageReceived.promise - proc.kill() } module.exports = test + diff --git a/examples/protocol-and-stream-muxing/test-1.js b/examples/protocol-and-stream-muxing/test-1.js index c851b879e8..91f409d5a9 100644 --- a/examples/protocol-and-stream-muxing/test-1.js +++ b/examples/protocol-and-stream-muxing/test-1.js @@ -1,31 +1,14 @@ 'use strict' const path = require('path') -const execa = require('execa') -const pDefer = require('p-defer') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +const { waitForOutput } = require('../utils') -async function test() { - const messageDefer = pDefer() +async function test () { process.stdout.write('1.js\n') - const proc = execa('node', [path.join(__dirname, '1.js')], { - cwd: path.resolve(__dirname), - all: true + await waitForOutput('my own protocol, wow!', 'node', [path.join(__dirname, '1.js')], { + cwd: __dirname }) - - proc.all.on('data', async (data) => { - process.stdout.write(data) - - const line = uint8ArrayToString(data) - - if (line.includes('my own protocol, wow!')) { - messageDefer.resolve() - } - }) - - await messageDefer.promise - proc.kill() } module.exports = test diff --git a/examples/protocol-and-stream-muxing/test-2.js b/examples/protocol-and-stream-muxing/test-2.js index 3f04c574f2..26b4b12ea1 100644 --- a/examples/protocol-and-stream-muxing/test-2.js +++ b/examples/protocol-and-stream-muxing/test-2.js @@ -1,38 +1,14 @@ 'use strict' const path = require('path') -const execa = require('execa') -const pWaitFor = require('p-wait-for') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +const { waitForOutput } = require('../utils') -const messages = [ - 'protocol (a)', - 'protocol (b)', - 'another stream on protocol (b)' -] - -async function test() { +async function test () { process.stdout.write('2.js\n') - let count = 0 - const proc = execa('node', [path.join(__dirname, '2.js')], { - cwd: path.resolve(__dirname), - all: true + await waitForOutput('another stream on protocol (b)', 'node', [path.join(__dirname, '2.js')], { + cwd: __dirname }) - - proc.all.on('data', async (data) => { - process.stdout.write(data) - - const line = uint8ArrayToString(data) - - if (messages.find((m) => line.includes(m))) { - count += 1 - } - }) - - await pWaitFor(() => count === messages.length) - - proc.kill() } module.exports = test diff --git a/examples/protocol-and-stream-muxing/test-3.js b/examples/protocol-and-stream-muxing/test-3.js index 223e1d124b..8724237cf2 100644 --- a/examples/protocol-and-stream-muxing/test-3.js +++ b/examples/protocol-and-stream-muxing/test-3.js @@ -1,37 +1,14 @@ 'use strict' const path = require('path') -const execa = require('execa') -const pWaitFor = require('p-wait-for') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +const { waitForOutput } = require('../utils') -const messages = [ - 'from 1 to 2', - 'from 2 to 1' -] - -async function test() { +async function test () { process.stdout.write('3.js\n') - let count = 0 - const proc = execa('node', [path.join(__dirname, '3.js')], { - cwd: path.resolve(__dirname), - all: true + await waitForOutput('from 2 to 1', 'node', [path.join(__dirname, '3.js')], { + cwd: __dirname }) - - proc.all.on('data', async (data) => { - process.stdout.write(data) - - const line = uint8ArrayToString(data) - - if (messages.find((m) => line.includes(m))) { - count += 1 - } - }) - - await pWaitFor(() => count === messages.length) - - proc.kill() } module.exports = test diff --git a/examples/transports/test-1.js b/examples/transports/test-1.js index 63e320329d..bcdaf57b23 100644 --- a/examples/transports/test-1.js +++ b/examples/transports/test-1.js @@ -1,38 +1,14 @@ 'use strict' const path = require('path') -const execa = require('execa') -const pDefer = require('p-defer') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +const { waitForOutput } = require('../utils') async function test () { - const deferStarted = pDefer() - const deferListen = pDefer() - process.stdout.write('1.js\n') - const proc = execa('node', [path.join(__dirname, '1.js')], { - cwd: path.resolve(__dirname), - all: true - }) - - proc.all.on('data', async (data) => { - process.stdout.write(data) - const line = uint8ArrayToString(data) - - - if (line.includes('node has started (true/false): true')) { - deferStarted.resolve() - } else if (line.includes('p2p')) { - deferListen.resolve() - } + await waitForOutput('/p2p/', 'node', [path.join(__dirname, '1.js')], { + cwd: __dirname }) - - await Promise.all([ - deferStarted.promise, - deferListen.promise - ]) - proc.kill() } module.exports = test diff --git a/examples/transports/test-2.js b/examples/transports/test-2.js index 48c37fff70..b383ac9e98 100644 --- a/examples/transports/test-2.js +++ b/examples/transports/test-2.js @@ -1,30 +1,14 @@ 'use strict' const path = require('path') -const execa = require('execa') -const pDefer = require('p-defer') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +const { waitForOutput } = require('../utils') async function test () { - const defer = pDefer() process.stdout.write('2.js\n') - const proc = execa('node', [path.join(__dirname, '2.js')], { - cwd: path.resolve(__dirname), - all: true + await waitForOutput('Hello p2p world!', 'node', [path.join(__dirname, '2.js')], { + cwd: __dirname }) - - proc.all.on('data', async (data) => { - process.stdout.write(data) - const line = uint8ArrayToString(data) - - if (line.includes('Hello p2p world!')) { - defer.resolve() - } - }) - - await defer.promise - proc.kill() } module.exports = test diff --git a/examples/transports/test-3.js b/examples/transports/test-3.js index cf75b44e45..642ab045b4 100644 --- a/examples/transports/test-3.js +++ b/examples/transports/test-3.js @@ -1,41 +1,14 @@ 'use strict' const path = require('path') -const execa = require('execa') -const pDefer = require('p-defer') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +const { waitForOutput } = require('../utils') async function test () { - const deferNode1 = pDefer() - const deferNode2 = pDefer() - const deferNode3 = pDefer() - process.stdout.write('3.js\n') - const proc = execa('node', [path.join(__dirname, '3.js')], { - cwd: path.resolve(__dirname), - all: true + await waitForOutput('node 3 failed to dial to node 1 with:', 'node', [path.join(__dirname, '3.js')], { + cwd: __dirname }) - - proc.all.on('data', async (data) => { - process.stdout.write(data) - const line = uint8ArrayToString(data) - - if (line.includes('node 1 dialed to node 2 successfully')) { - deferNode1.resolve() - } else if (line.includes('node 2 dialed to node 3 successfully')) { - deferNode2.resolve() - } else if (line.includes('node 3 failed to dial to node 1 with:')) { - deferNode3.resolve() - } - }) - - await Promise.all([ - deferNode1.promise, - deferNode2.promise, - deferNode3.promise - ]) - proc.kill() } module.exports = test diff --git a/examples/transports/test-4.js b/examples/transports/test-4.js index 7b2b6e5d79..5186ff533e 100644 --- a/examples/transports/test-4.js +++ b/examples/transports/test-4.js @@ -1,33 +1,14 @@ 'use strict' const path = require('path') -const execa = require('execa') -const pDefer = require('p-defer') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +const { waitForOutput } = require('../utils') async function test () { - const deferNode1 = pDefer() - process.stdout.write('4.js\n') - const proc = execa('node', [path.join(__dirname, '4.js')], { - cwd: path.resolve(__dirname), - all: true + await waitForOutput('node 2 dialed to node 1 successfully', 'node', [path.join(__dirname, '4.js')], { + cwd: __dirname }) - - proc.all.on('data', async (data) => { - process.stdout.write(data) - const line = uint8ArrayToString(data) - - if (line.includes('node 2 dialed to node 1 successfully')) { - deferNode1.resolve() - } - }) - - await Promise.all([ - deferNode1.promise, - ]) - proc.kill() } module.exports = test From e0354b4c6b95bb90656b868849182eb3efddf096 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 20 Jan 2022 14:17:41 +0000 Subject: [PATCH 323/447] fix: update any-signal and timeout-abort-controller (#1128) Updates deps to remove abort controller dep BREAKING CHANGE: abort-controller dep is gone from dependency tree --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 24f32a78c5..0723c306f7 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "@vascosantos/moving-average": "^1.1.0", "abortable-iterator": "^3.0.0", "aggregate-error": "^3.1.0", - "any-signal": "^2.1.1", + "any-signal": "^3.0.0", "bignumber.js": "^9.0.1", "class-is": "^1.1.0", "datastore-core": "^7.0.0", @@ -125,7 +125,7 @@ "sanitize-filename": "^1.6.3", "set-delayed-interval": "^1.0.0", "streaming-iterables": "^6.0.0", - "timeout-abort-controller": "^2.0.0", + "timeout-abort-controller": "^3.0.0", "uint8arrays": "^3.0.0", "varint": "^6.0.0", "wherearewe": "^1.0.0", From 280bb1b1f6c1baeda79846785c25bc5b686e780c Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 20 Jan 2022 14:24:06 +0000 Subject: [PATCH 324/447] chore: autopublish next version (#1129) If we aren't releasing a version on a given release run, publish an rc under the `next` tag instead. --- .github/workflows/main.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8c4472903d..75c46c0198 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -114,7 +114,6 @@ jobs: - uses: actions/setup-node@v2 with: node-version: lts/* - registry-url: 'https://registry.npmjs.org' if: ${{ steps.release.outputs.release_created }} - uses: ipfs/aegir/actions/cache-node-modules@master if: ${{ steps.release.outputs.release_created }} @@ -122,3 +121,9 @@ jobs: env: NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} if: ${{ steps.release.outputs.release_created }} + - run: | + npm version `node -p -e "require('./package.json').version"`-`git rev-parse --short HEAD` --no-git-tag-version + npm publish --tag next + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + if: ${{ !steps.release.outputs.release_created }} From 75b922dc214ee33787563d7290c25ab1da4ac7eb Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 20 Jan 2022 14:52:12 +0000 Subject: [PATCH 325/447] chore: checkout code and set up node for publishing rc (#1131) Need to run the checkout and setup-node actions before publishing an RC. --- .github/workflows/main.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 75c46c0198..ff4a9917ac 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -110,11 +110,9 @@ jobs: release-type: node bump-minor-pre-major: true - uses: actions/checkout@v2 - if: ${{ steps.release.outputs.release_created }} - uses: actions/setup-node@v2 with: node-version: lts/* - if: ${{ steps.release.outputs.release_created }} - uses: ipfs/aegir/actions/cache-node-modules@master if: ${{ steps.release.outputs.release_created }} - run: npm publish From cbd9bad86dafd438b44572b80c5381644e442a55 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 20 Jan 2022 14:52:31 +0000 Subject: [PATCH 326/447] chore: run test after build (#1130) Run CI tests and check at the same time --- .github/workflows/main.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ff4a9917ac..e391ae6729 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -38,7 +38,7 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} test-node: - needs: check + needs: build runs-on: ${{ matrix.os }} strategy: matrix: @@ -55,7 +55,7 @@ jobs: - uses: codecov/codecov-action@v1 test-chrome: - needs: check + needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -66,7 +66,7 @@ jobs: - run: npm run test:browser -- -t browser -t webworker --bail test-firefox: - needs: check + needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -77,7 +77,7 @@ jobs: - run: npm run test:browser -- -t browser -t webworker --bail -- --browser firefox test-ts: - needs: check + needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -88,7 +88,7 @@ jobs: - run: npm run test:ts test-interop: - needs: check + needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 From 85e23eb1cbf1ce039d09ed2a846bb594db3730f2 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 20 Jan 2022 15:21:42 +0000 Subject: [PATCH 327/447] chore: unpack node_module cache before rc publish (#1132) --- .github/workflows/main.yml | 1 - package.json | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e391ae6729..1bf32e690f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -114,7 +114,6 @@ jobs: with: node-version: lts/* - uses: ipfs/aegir/actions/cache-node-modules@master - if: ${{ steps.release.outputs.release_created }} - run: npm publish env: NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} diff --git a/package.json b/package.json index 0723c306f7..49ef942094 100644 --- a/package.json +++ b/package.json @@ -40,10 +40,7 @@ "test:browser": "aegir test -t browser", "test:examples": "cd examples && npm run test:all", "test:interop": "LIBP2P_JS=$PWD npx aegir test -t node -f ./node_modules/libp2p-interop/test/*", - "prepare": "aegir build --no-bundle", - "release": "aegir release -t node -t browser", - "release-minor": "aegir release --type minor -t node -t browser", - "release-major": "aegir release --type major -t node -t browser", + "prepare": "npm run build", "coverage": "nyc --reporter=text --reporter=lcov npm run test:node" }, "repository": { From 54e77221ebec85451036ccbba49fa8ca1bf7a50b Mon Sep 17 00:00:00 2001 From: achingbrain Date: Thu, 20 Jan 2022 16:27:01 +0000 Subject: [PATCH 328/447] chore: fix publish command --- .github/workflows/main.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1bf32e690f..5b99fcad4e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -114,13 +114,13 @@ jobs: with: node-version: lts/* - uses: ipfs/aegir/actions/cache-node-modules@master - - run: npm publish + - if: ${{ steps.release.outputs.release_created }} + run: npm publish env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - if: ${{ steps.release.outputs.release_created }} - - run: | + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - if: ${{ !steps.release.outputs.release_created }} + run: | npm version `node -p -e "require('./package.json').version"`-`git rev-parse --short HEAD` --no-git-tag-version npm publish --tag next env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - if: ${{ !steps.release.outputs.release_created }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From 79b356a31311a0197ffc9994e7dbb2280ce867ea Mon Sep 17 00:00:00 2001 From: achingbrain Date: Thu, 20 Jan 2022 16:52:30 +0000 Subject: [PATCH 329/447] chore: update var name --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5b99fcad4e..478f3313ed 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -117,10 +117,10 @@ jobs: - if: ${{ steps.release.outputs.release_created }} run: npm publish env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - if: ${{ !steps.release.outputs.release_created }} run: | npm version `node -p -e "require('./package.json').version"`-`git rev-parse --short HEAD` --no-git-tag-version npm publish --tag next env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} From bf1fc325b65f744b21d6242510a87af0d41d3f15 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Thu, 20 Jan 2022 17:21:48 +0000 Subject: [PATCH 330/447] chore: add registry --- .github/workflows/main.yml | 44 +++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 478f3313ed..1738fb327c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -52,7 +52,10 @@ jobs: node-version: ${{ matrix.node }} - uses: ipfs/aegir/actions/cache-node-modules@master - run: npm run test:node -- --cov --bail - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 + with: + directory: ./.nyc_output + flags: node test-chrome: needs: build @@ -63,7 +66,26 @@ jobs: with: node-version: lts/* - uses: ipfs/aegir/actions/cache-node-modules@master - - run: npm run test:browser -- -t browser -t webworker --bail + - run: npm run test:browser -- -t browser --cov --bail + - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 + with: + directory: ./.nyc_output + flags: chrome + + test-chrome-webworker: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run test:browser -- -t webworker --cov --bail + - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 + with: + directory: ./.nyc_output + flags: chrome-webworker test-firefox: needs: build @@ -74,7 +96,18 @@ jobs: with: node-version: lts/* - uses: ipfs/aegir/actions/cache-node-modules@master - - run: npm run test:browser -- -t browser -t webworker --bail -- --browser firefox + - run: npm run test:browser -- -t browser --bail -- --browser firefox + + test-firefox-webworker: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run test:browser -- -t webworker --bail -- --browser firefox test-ts: needs: build @@ -113,14 +146,15 @@ jobs: - uses: actions/setup-node@v2 with: node-version: lts/* + registry-url: 'https://registry.npmjs.org' - uses: ipfs/aegir/actions/cache-node-modules@master - if: ${{ steps.release.outputs.release_created }} run: npm publish env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - if: ${{ !steps.release.outputs.release_created }} run: | npm version `node -p -e "require('./package.json').version"`-`git rev-parse --short HEAD` --no-git-tag-version npm publish --tag next env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From fc43db750de72ec6f326174d3fdeed78078b3bf9 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Thu, 20 Jan 2022 17:33:58 +0000 Subject: [PATCH 331/447] chore: fix build on webworkers --- .github/workflows/main.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1738fb327c..4054a7b061 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -81,7 +81,7 @@ jobs: with: node-version: lts/* - uses: ipfs/aegir/actions/cache-node-modules@master - - run: npm run test:browser -- -t webworker --cov --bail + - run: npm run test:browser -- -t webworker --bail - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 with: directory: ./.nyc_output @@ -97,6 +97,10 @@ jobs: node-version: lts/* - uses: ipfs/aegir/actions/cache-node-modules@master - run: npm run test:browser -- -t browser --bail -- --browser firefox + - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 + with: + directory: ./.nyc_output + flags: firefox test-firefox-webworker: needs: build @@ -108,6 +112,10 @@ jobs: node-version: lts/* - uses: ipfs/aegir/actions/cache-node-modules@master - run: npm run test:browser -- -t webworker --bail -- --browser firefox + - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 + with: + directory: ./.nyc_output + flags: firefox-webworker test-ts: needs: build @@ -133,7 +141,7 @@ jobs: release: runs-on: ubuntu-latest - needs: [test-node, test-chrome, test-firefox, test-ts, test-interop] + needs: [test-node, test-chrome, test-chrome-webworker, test-firefox, test-firefox-webworker, test-ts, test-interop] if: github.event_name == 'push' && github.ref == 'refs/heads/master' steps: - uses: GoogleCloudPlatform/release-please-action@v2 @@ -149,10 +157,12 @@ jobs: registry-url: 'https://registry.npmjs.org' - uses: ipfs/aegir/actions/cache-node-modules@master - if: ${{ steps.release.outputs.release_created }} + name: Run release version run: npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - if: ${{ !steps.release.outputs.release_created }} + name: Run release rc run: | npm version `node -p -e "require('./package.json').version"`-`git rev-parse --short HEAD` --no-git-tag-version npm publish --tag next From a4bba35948e1cd8dbe5147f2c8d6385b1fbb6fae Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 20 Jan 2022 19:23:24 +0000 Subject: [PATCH 332/447] fix: update node-forge (#1133) Upgrade to 1v of node-forge --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 49ef942094..c9e9bd9b29 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "it-merge": "^1.0.0", "it-pipe": "^1.1.0", "it-take": "^1.0.0", - "libp2p-crypto": "^0.21.1", + "libp2p-crypto": "^0.21.2", "libp2p-interfaces": "^4.0.0", "libp2p-utils": "^0.4.0", "mafmt": "^10.0.0", @@ -110,7 +110,7 @@ "multistream-select": "^2.0.0", "mutable-proxy": "^1.0.0", "nat-api": "^0.3.1", - "node-forge": "^0.10.0", + "node-forge": "^1.2.1", "p-any": "^3.0.0", "p-fifo": "^1.0.0", "p-retry": "^4.4.0", @@ -133,7 +133,7 @@ "@nodeutils/defaults-deep": "^1.1.0", "@types/es6-promisify": "^6.0.0", "@types/node": "^16.0.1", - "@types/node-forge": "^0.10.1", + "@types/node-forge": "^1.0.0", "@types/varint": "^6.0.0", "aegir": "^36.0.0", "buffer": "^6.0.3", From 12f1bb0aeec4b639bd2af05807215f3b4284e379 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 21 Jan 2022 08:26:21 +0000 Subject: [PATCH 333/447] fix: catch errors during identify (#1138) Sometimes identify can be started while we are shutting down (for example) - we should just log these errors. --- src/identify/index.js | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/identify/index.js b/src/identify/index.js index 0836551652..16d23a66b4 100644 --- a/src/identify/index.js +++ b/src/identify/index.js @@ -267,25 +267,25 @@ class IdentifyService { * @returns {Promise} */ async _handleIdentify ({ connection, stream }) { - let publicKey = new Uint8Array(0) - if (this.peerId.pubKey) { - publicKey = this.peerId.pubKey.bytes - } + try { + let publicKey = new Uint8Array(0) + if (this.peerId.pubKey) { + publicKey = this.peerId.pubKey.bytes + } - const signedPeerRecord = await this.peerStore.addressBook.getRawEnvelope(this.peerId) - const protocols = await this.peerStore.protoBook.get(this.peerId) + const signedPeerRecord = await this.peerStore.addressBook.getRawEnvelope(this.peerId) + const protocols = await this.peerStore.protoBook.get(this.peerId) - const message = Message.Identify.encode({ - protocolVersion: this._host.protocolVersion, - agentVersion: this._host.agentVersion, - publicKey, - listenAddrs: this._libp2p.multiaddrs.map((ma) => ma.bytes), - signedPeerRecord, - observedAddr: connection.remoteAddr.bytes, - protocols - }).finish() + const message = Message.Identify.encode({ + protocolVersion: this._host.protocolVersion, + agentVersion: this._host.agentVersion, + publicKey, + listenAddrs: this._libp2p.multiaddrs.map((ma) => ma.bytes), + signedPeerRecord, + observedAddr: connection.remoteAddr.bytes, + protocols + }).finish() - try { await pipe( [message], lp.encode(), @@ -343,7 +343,11 @@ class IdentifyService { } // Update the protocols - await this.peerStore.protoBook.set(id, message.protocols) + try { + await this.peerStore.protoBook.set(id, message.protocols) + } catch (/** @type {any} */ err) { + log.error('received invalid protocols', err) + } } /** From 4c3bf01f35a2d1205fc17b4fe4fe3294c1beef7d Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 21 Jan 2022 08:44:13 +0000 Subject: [PATCH 334/447] chore: fix flaky tests (#1137) These tests are flaky in CI, probably due to differences in timing introduced by #1058 Fixes #1134 --- test/connection-manager/index.node.js | 14 +++++++------- test/core/ping.node.js | 4 ++-- test/dialing/direct.node.js | 8 ++++---- test/dialing/direct.spec.js | 4 ++-- test/identify/index.spec.js | 6 +++--- test/peer-discovery/index.node.js | 4 ++-- test/peer-discovery/index.spec.js | 2 +- test/relay/auto-relay.node.js | 20 ++++++++++---------- test/relay/relay.node.js | 8 +------- test/utils/creators/peer.js | 6 +++--- 10 files changed, 35 insertions(+), 41 deletions(-) diff --git a/test/connection-manager/index.node.js b/test/connection-manager/index.node.js index bcd381b23c..d187895c6a 100644 --- a/test/connection-manager/index.node.js +++ b/test/connection-manager/index.node.js @@ -118,7 +118,7 @@ describe('libp2p.connections', () => { } }) - libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) + await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) await libp2p.dial(remoteLibp2p.peerId) expect(libp2p.connections.size).to.eql(1) @@ -161,8 +161,8 @@ describe('libp2p.connections', () => { }) // 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.peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs) + await libp2p.peerStore.addressBook.set(nodes[1].peerId, nodes[1].multiaddrs) await libp2p.start() @@ -188,8 +188,8 @@ describe('libp2p.connections', () => { }) // 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.peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs) + await libp2p.peerStore.addressBook.set(nodes[1].peerId, nodes[1].multiaddrs) await libp2p.start() @@ -253,7 +253,7 @@ describe('libp2p.connections', () => { }) // Populate PeerStore after starting (discovery) - libp2p.peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs) + await libp2p.peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs) // Wait for peer to connect const conn = await libp2p.dial(nodes[0].peerId) @@ -290,7 +290,7 @@ describe('libp2p.connections', () => { } }) - libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) + await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) await libp2p.dial(remoteLibp2p.peerId) const totalConns = Array.from(libp2p.connections.values()) diff --git a/test/core/ping.node.js b/test/core/ping.node.js index 4bbd4f76cc..63ebec0160 100644 --- a/test/core/ping.node.js +++ b/test/core/ping.node.js @@ -19,8 +19,8 @@ describe('ping', () => { config: baseOptions }) - nodes[0].peerStore.addressBook.set(nodes[1].peerId, nodes[1].multiaddrs) - nodes[1].peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs) + await nodes[0].peerStore.addressBook.set(nodes[1].peerId, nodes[1].multiaddrs) + await nodes[1].peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs) }) afterEach(() => Promise.all(nodes.map(n => n.stop()))) diff --git a/test/dialing/direct.node.js b/test/dialing/direct.node.js index 0d88a397f0..b321fc2da7 100644 --- a/test/dialing/direct.node.js +++ b/test/dialing/direct.node.js @@ -128,7 +128,7 @@ describe('Dialing (direct, TCP)', () => { peerStore }) - peerStore.addressBook.set(peerId, remoteTM.getAddrs()) + await peerStore.addressBook.set(peerId, remoteTM.getAddrs()) const connection = await dialer.connectToPeer(peerId) expect(connection).to.exist() @@ -326,7 +326,7 @@ describe('Dialing (direct, TCP)', () => { }) sinon.spy(libp2p.dialer, 'connectToPeer') - libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) + await libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) const connection = await libp2p.dial(remotePeerId) expect(connection).to.exist() @@ -471,7 +471,7 @@ describe('Dialing (direct, TCP)', () => { const fullAddress = remoteAddr.encapsulate(`/p2p/${remoteLibp2p.peerId.toB58String()}`) - libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) + await libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) const dialResults = await Promise.all([...new Array(dials)].map((_, index) => { if (index % 2 === 0) return libp2p.dial(remoteLibp2p.peerId) return libp2p.dial(fullAddress) @@ -501,7 +501,7 @@ describe('Dialing (direct, TCP)', () => { const error = new Error('Boom') sinon.stub(libp2p.transportManager, 'dial').callsFake(() => Promise.reject(error)) - libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) + await libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) const dialResults = await pSettle([...new Array(dials)].map((_, index) => { if (index % 2 === 0) return libp2p.dial(remoteLibp2p.peerId) return libp2p.dial(remoteAddr) diff --git a/test/dialing/direct.spec.js b/test/dialing/direct.spec.js index 14beacb4f1..ba97352668 100644 --- a/test/dialing/direct.spec.js +++ b/test/dialing/direct.spec.js @@ -48,8 +48,8 @@ describe('Dialing (direct, WebSockets)', () => { localTM.add(Transport.prototype[Symbol.toStringTag], Transport, { filter: filters.all }) }) - afterEach(() => { - peerStore.delete(peerId) + afterEach(async () => { + await peerStore.delete(peerId) sinon.restore() }) diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js index 88a61eeeda..7e960dfe1b 100644 --- a/test/identify/index.spec.js +++ b/test/identify/index.spec.js @@ -41,13 +41,13 @@ describe('Identify', () => { peerId: localPeer, datastore: new MemoryDatastore() }) - localPeerStore.protoBook.set(localPeer, protocols) + await localPeerStore.protoBook.set(localPeer, protocols) remotePeerStore = new PeerStore({ peerId: remotePeer, datastore: new MemoryDatastore() }) - remotePeerStore.protoBook.set(remotePeer, protocols) + await remotePeerStore.protoBook.set(remotePeer, protocols) localAddressManager = new AddressManager(localPeer) remoteAddressManager = new AddressManager(remotePeer) @@ -372,7 +372,7 @@ describe('Identify', () => { peerId: remotePeer, datastore: new MemoryDatastore() }) - remotePeerStore.protoBook.set(remotePeer, storedProtocols) + await remotePeerStore.protoBook.set(remotePeer, storedProtocols) const remoteIdentify = new IdentifyService({ libp2p: { diff --git a/test/peer-discovery/index.node.js b/test/peer-discovery/index.node.js index bdf4587960..cc5618bb4e 100644 --- a/test/peer-discovery/index.node.js +++ b/test/peer-discovery/index.node.js @@ -186,8 +186,8 @@ describe('peer discovery scenarios', () => { remoteLibp2p2.start() ]) - libp2p.peerStore.addressBook.set(remotePeerId1, remoteLibp2p1.multiaddrs) - remoteLibp2p2.peerStore.addressBook.set(remotePeerId1, remoteLibp2p1.multiaddrs) + await libp2p.peerStore.addressBook.set(remotePeerId1, remoteLibp2p1.multiaddrs) + await remoteLibp2p2.peerStore.addressBook.set(remotePeerId1, remoteLibp2p1.multiaddrs) // Topology: // A -> B diff --git a/test/peer-discovery/index.spec.js b/test/peer-discovery/index.spec.js index 72248621b0..1a43d9f7e8 100644 --- a/test/peer-discovery/index.spec.js +++ b/test/peer-discovery/index.spec.js @@ -38,7 +38,7 @@ describe('peer discovery', () => { } }) - libp2p.peerStore.addressBook.set(remotePeerId, [new Multiaddr('/ip4/165.1.1.1/tcp/80')]) + await libp2p.peerStore.addressBook.set(remotePeerId, [new Multiaddr('/ip4/165.1.1.1/tcp/80')]) const deferred = defer() sinon.stub(libp2p.dialer, 'connectToPeer').callsFake((remotePeerId) => { diff --git a/test/relay/auto-relay.node.js b/test/relay/auto-relay.node.js index 5ec2643e06..d2028cc4b2 100644 --- a/test/relay/auto-relay.node.js +++ b/test/relay/auto-relay.node.js @@ -310,7 +310,7 @@ describe('auto-relay', () => { await pWaitFor(() => relayLibp2p1.multiaddrs.length === originalMultiaddrs1Length + 1) expect(relayLibp2p1.multiaddrs[originalMultiaddrs1Length].getPeerId()).to.eql(relayLibp2p2.peerId.toB58String()) - // Only one will be used for listeninng + // Only one will be used for listening expect(relayLibp2p1.transportManager.listen.callCount).to.equal(1) // Spy if relay from listen map was removed @@ -349,7 +349,7 @@ describe('auto-relay', () => { expect(autoRelay1._listenRelays.size).to.equal(1) expect(relayLibp2p1.connectionManager.size).to.equal(2) - // Only one will be used for listeninng + // Only one will be used for listening expect(relayLibp2p1.transportManager.listen.callCount).to.equal(1) // Disconnect not used listen relay @@ -363,16 +363,16 @@ describe('auto-relay', () => { sinon.spy(relayLibp2p1, 'dial') // Remove peer used as relay from peerStore and disconnect it - relayLibp2p1.peerStore.delete(relayLibp2p2.peerId) + await 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._tryToListenOnRelay.callCount).to.equal(1) - expect(autoRelay1._listenRelays.size).to.equal(1) - expect(relayLibp2p1.connectionManager.size).to.eql(1) + await pWaitFor(() => autoRelay1._tryToListenOnRelay.callCount === 1) + await pWaitFor(() => autoRelay1._listenRelays.size === 1) + await pWaitFor(() => relayLibp2p1.connectionManager.size === 1) }) it('should not fail when trying to dial unreachable peers to add as hop relay and replaced removed ones', async () => { @@ -394,7 +394,7 @@ describe('auto-relay', () => { expect(autoRelay1._listenRelays.size).to.equal(1) expect(relayLibp2p1.connectionManager.size).to.equal(2) - // Only one will be used for listeninng + // Only one will be used for listening expect(relayLibp2p1.transportManager.listen.callCount).to.equal(1) // Disconnect not used listen relay @@ -410,7 +410,7 @@ describe('auto-relay', () => { }) // Remove peer used as relay from peerStore and disconnect it - relayLibp2p1.peerStore.delete(relayLibp2p2.peerId) + await relayLibp2p1.peerStore.delete(relayLibp2p2.peerId) await relayLibp2p1.hangUp(relayLibp2p2.peerId) expect(autoRelay1._listenRelays.size).to.equal(0) expect(relayLibp2p1.connectionManager.size).to.equal(0) @@ -492,7 +492,7 @@ describe('auto-relay', () => { // Wait for peer added as listen relay await pWaitFor(() => autoRelay2._addListenRelay.callCount === 1) - expect(autoRelay2._listenRelays.size).to.equal(1) + await pWaitFor(() => autoRelay2._listenRelays.size === 1) // Relay 1 discovers Relay 2 relayed multiaddr via Relay 3 const ma2RelayedBy3 = relayLibp2p2.multiaddrs[relayLibp2p2.multiaddrs.length - 1] @@ -619,7 +619,7 @@ describe('auto-relay', () => { await pWaitFor(() => local.multiaddrs.length === originalMultiaddrsLength + 1) const relayedAddr = local.multiaddrs[local.multiaddrs.length - 1] - remote.peerStore.addressBook.set(local.peerId, [relayedAddr]) + await remote.peerStore.addressBook.set(local.peerId, [relayedAddr]) // Dial from remote through the relayed address const conn = await remote.dial(local.peerId) diff --git a/test/relay/relay.node.js b/test/relay/relay.node.js index 803a3849ae..e9c1207875 100644 --- a/test/relay/relay.node.js +++ b/test/relay/relay.node.js @@ -47,13 +47,7 @@ describe('Dialing (via relay, TCP)', () => { afterEach(async () => { // Stop each node - return Promise.all([srcLibp2p, relayLibp2p, dstLibp2p].map(async libp2p => { - await libp2p.stop() - // Clear the peer stores - for await (const peer of libp2p.peerStore.getPeers()) { - libp2p.peerStore.delete(peer.id) - } - })) + return Promise.all([srcLibp2p, relayLibp2p, dstLibp2p].map(libp2p => libp2p.stop())) }) it('should be able to connect to a peer over a relay with active connections', async () => { diff --git a/test/utils/creators/peer.js b/test/utils/creators/peer.js index ef93d237dd..63b943f76b 100644 --- a/test/utils/creators/peer.js +++ b/test/utils/creators/peer.js @@ -36,17 +36,17 @@ async function createPeer ({ number = 1, fixture = true, started = true, populat if (started) { await Promise.all(peers.map((p) => p.start())) - populateAddressBooks && _populateAddressBooks(peers) + populateAddressBooks && await _populateAddressBooks(peers) } return peers } -function _populateAddressBooks (peers) { +async function _populateAddressBooks (peers) { for (let i = 0; i < peers.length; i++) { for (let j = 0; j < peers.length; j++) { if (i !== j) { - peers[i].peerStore.addressBook.set(peers[j].peerId, peers[j].multiaddrs) + await peers[i].peerStore.addressBook.set(peers[j].peerId, peers[j].multiaddrs) } } } From b7e87066a69970f1adca4ba552c7fdf624916a7e Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 21 Jan 2022 12:58:31 +0000 Subject: [PATCH 335/447] fix: make tests more reliable (#1139) Try to use only public functions and properties to verify test behaviour --- doc/CONFIGURATION.md | 2 +- src/circuit/auto-relay.js | 41 +++--- test/relay/auto-relay.node.js | 246 +++++++++++----------------------- 3 files changed, 100 insertions(+), 189 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 3ebf6689b0..73ef515898 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -592,7 +592,7 @@ const node = await Libp2p.create({ #### Configuring Transport Manager -The Transport Manager is responsible for managing the libp2p transports life cycle. This includes starting listeners for the provided listen addresses, closing these listeners and dialing using the provided transports. By default, if a libp2p node has a list of multiaddrs for listenning on and there are no valid transports for those multiaddrs, libp2p will throw an error on startup and shutdown. However, for some applications it is perfectly acceptable for libp2p nodes to start in dial only mode if all the listen multiaddrs failed. This error tolerance can be enabled as follows: +The Transport Manager is responsible for managing the libp2p transports life cycle. This includes starting listeners for the provided listen addresses, closing these listeners and dialing using the provided transports. By default, if a libp2p node has a list of multiaddrs for listening on and there are no valid transports for those multiaddrs, libp2p will throw an error on startup and shutdown. However, for some applications it is perfectly acceptable for libp2p nodes to start in dial only mode if all the listen multiaddrs failed. This error tolerance can be enabled as follows: ```js const Libp2p = require('libp2p') diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js index 91d150ec2c..8ead2bb5f8 100644 --- a/src/circuit/auto-relay.js +++ b/src/circuit/auto-relay.js @@ -8,6 +8,7 @@ const log = Object.assign(debug('libp2p:auto-relay'), { const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') const { toString: uint8ArrayToString } = require('uint8arrays/to-string') const { Multiaddr } = require('multiaddr') +const all = require('it-all') const { relay: multicodec } = require('./multicodec') const { canHop } = require('./circuit/hop') @@ -149,26 +150,27 @@ class AutoRelay { * @returns {Promise} */ async _addListenRelay (connection, id) { - // Check if already listening on enough relays - if (this._listenRelays.size >= this.maxListeners) { - return - } + try { + // Check if already listening on enough relays + if (this._listenRelays.size >= this.maxListeners) { + return + } - // Get peer known addresses and sort them per public addresses first - const remoteAddrs = await this._peerStore.addressBook.getMultiaddrsForPeer( - connection.remotePeer, this._addressSorter - ) + // Get peer known addresses and sort them per public addresses first + const remoteAddrs = await this._peerStore.addressBook.getMultiaddrsForPeer( + connection.remotePeer, this._addressSorter + ) - if (!remoteAddrs || !remoteAddrs.length) { - return - } + if (!remoteAddrs || !remoteAddrs.length) { + return + } - const listenAddr = `${remoteAddrs[0].toString()}/p2p-circuit` - this._listenRelays.add(id) + const listenAddr = `${remoteAddrs[0].toString()}/p2p-circuit` + this._listenRelays.add(id) - // Attempt to listen on relay - try { + // Attempt to listen on relay await this._transportManager.listen([new Multiaddr(listenAddr)]) + // Announce multiaddrs will update on listen success by TransportManager event being triggered } catch (/** @type {any} */ err) { this._onError(err) @@ -206,13 +208,18 @@ class AutoRelay { } const knownHopsToDial = [] + const peers = await all(this._peerStore.getPeers()) // Check if we have known hop peers to use and attempt to listen on the already connected - for await (const { id, metadata } of this._peerStore.getPeers()) { + for await (const { id, metadata } of peers) { const idStr = id.toB58String() // Continue to next if listening on this or peer to ignore - if (this._listenRelays.has(idStr) || peersToIgnore.includes(idStr)) { + if (this._listenRelays.has(idStr)) { + continue + } + + if (peersToIgnore.includes(idStr)) { continue } diff --git a/test/relay/auto-relay.node.js b/test/relay/auto-relay.node.js index d2028cc4b2..c9d230a410 100644 --- a/test/relay/auto-relay.node.js +++ b/test/relay/auto-relay.node.js @@ -2,8 +2,7 @@ /* eslint-env mocha */ const { expect } = require('aegir/utils/chai') -const delay = require('delay') -const pDefer = require('p-defer') +const defer = require('p-defer') const pWaitFor = require('p-wait-for') const sinon = require('sinon') const nock = require('nock') @@ -19,11 +18,35 @@ const baseOptions = require('../utils/base-options') const listenAddr = '/ip4/0.0.0.0/tcp/0' +async function usingAsRelay (node, relay, opts) { + // Wait for peer to be used as a relay + await pWaitFor(() => { + for (const addr of node.multiaddrs) { + if (addr.toString().includes(`${relay.peerId.toB58String()}/p2p-circuit`)) { + return true + } + } + + return false + }, opts) +} + +async function discoveredRelayConfig (node, relay) { + await pWaitFor(async () => { + const protos = await node.peerStore.protoBook.get(relay.peerId) + const supportsRelay = protos.includes('/libp2p/circuit/relay/0.1.0') + + const metadata = await node.peerStore.metadataBook.get(relay.peerId) + const supportsHop = metadata.has('hop_relay') + + return supportsRelay && supportsHop + }) +} + describe('auto-relay', () => { describe('basics', () => { let libp2p let relayLibp2p - let autoRelay beforeEach(async () => { const peerIds = await createPeerId({ number: 2 }) @@ -59,10 +82,6 @@ describe('auto-relay', () => { peerId }) }) - - autoRelay = libp2p.relay._autoRelay - - expect(autoRelay.maxListeners).to.eql(1) }) beforeEach(() => { @@ -76,22 +95,15 @@ describe('auto-relay', () => { }) 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 await 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) + await discoveredRelayConfig(libp2p, relayLibp2p) - // Wait for listen multiaddr update - await pWaitFor(() => libp2p.multiaddrs.length === originalMultiaddrsLength + 1) - expect(libp2p.multiaddrs[originalMultiaddrsLength].getPeerId()).to.eql(relayLibp2p.peerId.toB58String()) + // Wait to start using peer as a relay + await usingAsRelay(libp2p, relayLibp2p) // Peer has relay multicodec const knownProtocols = await libp2p.peerStore.protoBook.get(relayLibp2p.peerId) @@ -104,7 +116,6 @@ describe('auto-relay', () => { let relayLibp2p1 let relayLibp2p2 let relayLibp2p3 - let autoRelay1 beforeEach(async () => { const peerIds = await createPeerId({ number: 4 }) @@ -144,10 +155,6 @@ describe('auto-relay', () => { peerId }) }) - - autoRelay1 = relayLibp2p1.relay._autoRelay - - expect(autoRelay1.maxListeners).to.eql(1) }) beforeEach(() => { @@ -161,27 +168,13 @@ describe('auto-relay', () => { }) 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 await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) - - const originalMultiaddrs1Length = relayLibp2p1.multiaddrs.length - const originalMultiaddrs2Length = relayLibp2p2.multiaddrs.length - await relayLibp2p1.dial(relayLibp2p2.peerId) + await discoveredRelayConfig(relayLibp2p1, relayLibp2p2) // 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()) + await usingAsRelay(relayLibp2p1, relayLibp2p2) // Peer has relay multicodec const knownProtocols = await relayLibp2p1.peerStore.protoBook.get(relayLibp2p2.peerId) @@ -189,52 +182,28 @@ describe('auto-relay', () => { }) 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 await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) - await relayLibp2p1.dial(relayLibp2p2.peerId) + await discoveredRelayConfig(relayLibp2p1, relayLibp2p2) - // 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()) + // Wait for peer added as listen relay + await usingAsRelay(relayLibp2p1, relayLibp2p2) // Dial from the other through a relay const relayedMultiaddr2 = new Multiaddr(`${relayLibp2p1.multiaddrs[0]}/p2p/${relayLibp2p1.peerId.toB58String()}/p2p-circuit`) await 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 await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) await relayLibp2p1.dial(relayLibp2p2.peerId) - - expect(relayLibp2p1.connectionManager.size).to.eql(1) + await discoveredRelayConfig(relayLibp2p1, relayLibp2p2) // 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()) + await usingAsRelay(relayLibp2p1, relayLibp2p2) // Relay2 has relay multicodec const knownProtocols2 = await relayLibp2p1.peerStore.protoBook.get(relayLibp2p2.peerId) @@ -243,14 +212,12 @@ describe('auto-relay', () => { // Discover an extra relay and connect await relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) await relayLibp2p1.dial(relayLibp2p3.peerId) + await discoveredRelayConfig(relayLibp2p1, relayLibp2p3) // 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) + await expect(usingAsRelay(relayLibp2p1, relayLibp2p3, { + timeout: 1000 + })).to.eventually.be.rejected() // Relay2 has relay multicodec const knownProtocols3 = await relayLibp2p1.peerStore.protoBook.get(relayLibp2p3.peerId) @@ -258,19 +225,16 @@ describe('auto-relay', () => { }) 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 await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) await relayLibp2p1.dial(relayLibp2p2.peerId) + await discoveredRelayConfig(relayLibp2p1, relayLibp2p2) - // 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()) + // Wait for listening on the relay + await usingAsRelay(relayLibp2p1, relayLibp2p2) // Identify push for adding listen relay multiaddr expect(relayLibp2p1.identifyService.pushToPeerStore.callCount).to.equal(1) @@ -279,107 +243,67 @@ describe('auto-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) + await expect(usingAsRelay(relayLibp2p1, relayLibp2p2, { + timeout: 1000 + })).to.eventually.be.rejected() // Identify push for removing listen relay multiaddr await pWaitFor(() => relayLibp2p1.identifyService.pushToPeerStore.callCount === 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 await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) await relayLibp2p1.dial(relayLibp2p2.peerId) + await discoveredRelayConfig(relayLibp2p1, relayLibp2p2) + await usingAsRelay(relayLibp2p1, relayLibp2p2) // Discover an extra relay and connect await 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()) + await discoveredRelayConfig(relayLibp2p1, relayLibp2p3) // Only one will be used for listening - expect(relayLibp2p1.transportManager.listen.callCount).to.equal(1) - - // Spy if relay from listen map was removed - sinon.spy(autoRelay1._listenRelays, 'delete') + await expect(usingAsRelay(relayLibp2p1, relayLibp2p3, { + timeout: 1000 + })).to.eventually.be.rejected() // 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) + await relayLibp2p2.stop() // 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()) + await usingAsRelay(relayLibp2p1, relayLibp2p3) }) 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 await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) await relayLibp2p1.dial(relayLibp2p2.peerId) + // Wait for peer to be used as a relay + await usingAsRelay(relayLibp2p1, relayLibp2p2) + // Discover an extra relay and connect to gather its Hop support await 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 listening - expect(relayLibp2p1.transportManager.listen.callCount).to.equal(1) + // wait for identify for newly dialled peer + await discoveredRelayConfig(relayLibp2p1, relayLibp2p3) // 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(autoRelay1, '_tryToListenOnRelay') - sinon.spy(relayLibp2p1, 'dial') - // Remove peer used as relay from peerStore and disconnect it - await relayLibp2p1.peerStore.delete(relayLibp2p2.peerId) await relayLibp2p1.hangUp(relayLibp2p2.peerId) - expect(autoRelay1._listenRelays.size).to.equal(0) - expect(relayLibp2p1.connectionManager.size).to.equal(0) + await relayLibp2p1.peerStore.delete(relayLibp2p2.peerId) + await pWaitFor(() => relayLibp2p1.connectionManager.size === 0) // Wait for other peer connected to be added as listen addr - await pWaitFor(() => relayLibp2p1.transportManager.listen.callCount === 2) - await pWaitFor(() => autoRelay1._tryToListenOnRelay.callCount === 1) - await pWaitFor(() => autoRelay1._listenRelays.size === 1) - await pWaitFor(() => relayLibp2p1.connectionManager.size === 1) + await usingAsRelay(relayLibp2p1, relayLibp2p3) }) it('should not fail when trying to dial unreachable peers to add as hop relay and replaced removed ones', async () => { - const defer = pDefer() - // Spy if a connected peer is being added as listen relay - sinon.spy(autoRelay1, '_addListenRelay') - sinon.spy(relayLibp2p1.transportManager, 'listen') + const deferred = defer() // Discover one relay and connect await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) @@ -389,34 +313,28 @@ describe('auto-relay', () => { await 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) + // Wait for peer to be used as a relay + await usingAsRelay(relayLibp2p1, relayLibp2p2) - // Only one will be used for listening - expect(relayLibp2p1.transportManager.listen.callCount).to.equal(1) + // wait for identify for newly dialled peer + await discoveredRelayConfig(relayLibp2p1, relayLibp2p3) // Disconnect not used listen relay await relayLibp2p1.hangUp(relayLibp2p3.peerId) - expect(autoRelay1._listenRelays.size).to.equal(1) - expect(relayLibp2p1.connectionManager.size).to.equal(1) - // Stub dial sinon.stub(relayLibp2p1, 'dial').callsFake(() => { - defer.resolve() + deferred.resolve() return Promise.reject(new Error('failed to dial')) }) // Remove peer used as relay from peerStore and disconnect it - await relayLibp2p1.peerStore.delete(relayLibp2p2.peerId) await relayLibp2p1.hangUp(relayLibp2p2.peerId) - expect(autoRelay1._listenRelays.size).to.equal(0) + await relayLibp2p1.peerStore.delete(relayLibp2p2.peerId) expect(relayLibp2p1.connectionManager.size).to.equal(0) // Wait for failed dial - await defer.promise + await deferred.promise }) }) @@ -424,8 +342,6 @@ describe('auto-relay', () => { let relayLibp2p1 let relayLibp2p2 let relayLibp2p3 - let autoRelay1 - let autoRelay2 beforeEach(async () => { const peerIds = await createPeerId({ number: 3 }) @@ -458,9 +374,6 @@ describe('auto-relay', () => { peerId }) }) - - autoRelay1 = relayLibp2p1.relay._autoRelay - autoRelay2 = relayLibp2p2.relay._autoRelay }) beforeEach(() => { @@ -474,25 +387,15 @@ describe('auto-relay', () => { }) 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 await 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) + await usingAsRelay(relayLibp2p1, relayLibp2p3) // Relay 2 discovers Relay 3 and connect await 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) - await pWaitFor(() => autoRelay2._listenRelays.size === 1) + await usingAsRelay(relayLibp2p2, relayLibp2p3) // Relay 1 discovers Relay 2 relayed multiaddr via Relay 3 const ma2RelayedBy3 = relayLibp2p2.multiaddrs[relayLibp2p2.multiaddrs.length - 1] @@ -500,8 +403,9 @@ describe('auto-relay', () => { 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) + await expect(usingAsRelay(relayLibp2p1, relayLibp2p2, { + timeout: 1000 + })).to.eventually.be.rejected() }) }) From 63aa480800974515f44d3b7e013da9c8ccaae8ad Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 21 Jan 2022 17:57:09 +0000 Subject: [PATCH 336/447] fix: try all peer addresses when dialing a relay (#1140) The order of the addresses can affect our success rate in dialing a relay - if it's a loopback address or similar it won't work. Instead try dialing every address. --- src/circuit/auto-relay.js | 25 ++++++++++++++++--------- src/connection-manager/index.js | 3 +++ test/relay/auto-relay.node.js | 13 ++++++------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js index 8ead2bb5f8..e173ac039b 100644 --- a/src/circuit/auto-relay.js +++ b/src/circuit/auto-relay.js @@ -161,17 +161,24 @@ class AutoRelay { connection.remotePeer, this._addressSorter ) - if (!remoteAddrs || !remoteAddrs.length) { - return - } - - const listenAddr = `${remoteAddrs[0].toString()}/p2p-circuit` - this._listenRelays.add(id) - // Attempt to listen on relay - await this._transportManager.listen([new Multiaddr(listenAddr)]) + const result = await Promise.all( + remoteAddrs.map(async addr => { + try { + // Announce multiaddrs will update on listen success by TransportManager event being triggered + await this._transportManager.listen([new Multiaddr(`${addr.toString()}/p2p-circuit`)]) + return true + } catch (/** @type {any} */ err) { + this._onError(err) + } + + return false + }) + ) - // Announce multiaddrs will update on listen success by TransportManager event being triggered + if (result.includes(true)) { + this._listenRelays.add(id) + } } catch (/** @type {any} */ err) { this._onError(err) this._listenRelays.delete(id) diff --git a/src/connection-manager/index.js b/src/connection-manager/index.js index 553455fd3e..fa2cb40225 100644 --- a/src/connection-manager/index.js +++ b/src/connection-manager/index.js @@ -112,6 +112,9 @@ class ConnectionManager extends EventEmitter { latencyCheckIntervalMs: this._options.pollInterval, dataEmitIntervalMs: this._options.pollInterval }) + + // This emitter gets listened to a lot + this.setMaxListeners(Infinity) } /** diff --git a/test/relay/auto-relay.node.js b/test/relay/auto-relay.node.js index c9d230a410..2316563dfd 100644 --- a/test/relay/auto-relay.node.js +++ b/test/relay/auto-relay.node.js @@ -224,7 +224,7 @@ describe('auto-relay', () => { expect(knownProtocols3).to.include(relayMulticodec) }) - it('should not listen on a relayed address if peer disconnects', async () => { + it('should not listen on a relayed address we disconnect from peer', async () => { // Spy if identify push is fired on adding/removing listen addr sinon.spy(relayLibp2p1.identifyService, 'pushToPeerStore') @@ -236,9 +236,6 @@ describe('auto-relay', () => { // Wait for listening on the relay await usingAsRelay(relayLibp2p1, relayLibp2p2) - // 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) @@ -246,9 +243,6 @@ describe('auto-relay', () => { await expect(usingAsRelay(relayLibp2p1, relayLibp2p2, { timeout: 1000 })).to.eventually.be.rejected() - - // Identify push for removing listen relay multiaddr - await pWaitFor(() => relayLibp2p1.identifyService.pushToPeerStore.callCount === 2) }) it('should try to listen on other connected peers relayed address if one used relay disconnects', async () => { @@ -271,6 +265,11 @@ describe('auto-relay', () => { // Disconnect from peer used for relay await relayLibp2p2.stop() + // Should not be using the relay any more + await expect(usingAsRelay(relayLibp2p1, relayLibp2p2, { + timeout: 1000 + })).to.eventually.be.rejected() + // Wait for other peer connected to be added as listen addr await usingAsRelay(relayLibp2p1, relayLibp2p3) }) From 00e49592a356e39b20c889d5f40b9bb37d4bf293 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 21 Jan 2022 18:13:33 +0000 Subject: [PATCH 337/447] fix: update multistream select (#1136) It has types now so the ignore can be removed. --- package.json | 2 +- src/upgrader.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index c9e9bd9b29..c78c349c67 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "mortice": "^2.0.1", "multiaddr": "^10.0.0", "multiformats": "^9.0.0", - "multistream-select": "^2.0.0", + "multistream-select": "^3.0.0", "mutable-proxy": "^1.0.0", "nat-api": "^0.3.1", "node-forge": "^1.2.1", diff --git a/src/upgrader.js b/src/upgrader.js index 8e28b71528..58a6418f0a 100644 --- a/src/upgrader.js +++ b/src/upgrader.js @@ -5,7 +5,6 @@ const log = Object.assign(debug('libp2p:upgrader'), { error: debug('libp2p:upgrader:err') }) const errCode = require('err-code') -// @ts-ignore multistream-select does not export types const Multistream = require('multistream-select') const { Connection } = require('libp2p-interfaces/src/connection') const PeerId = require('peer-id') From d8ceb0bc66fe225d1335d3f05b9a3a30983c2a57 Mon Sep 17 00:00:00 2001 From: Spencer T Brody Date: Mon, 24 Jan 2022 12:07:11 -0500 Subject: [PATCH 338/447] feat: add fetch protocol (#1036) Adds three methods to implement the `/libp2p/fetch/0.0.1` protocol: * `libp2p.fetch(peerId, key) => Promise` * `libp2p.fetchService.registerLookupFunction(prefix, lookupFunction)` * `libp2p.fetchService.unRegisterLookupFunction(prefix, [lookupFunction])` Co-authored-by: achingbrain --- doc/API.md | 69 ++++++++ package.json | 6 +- src/fetch/README.md | 36 +++++ src/fetch/constants.js | 6 + src/fetch/index.js | 159 +++++++++++++++++++ src/fetch/proto.d.ts | 134 ++++++++++++++++ src/fetch/proto.js | 333 +++++++++++++++++++++++++++++++++++++++ src/fetch/proto.proto | 15 ++ src/index.js | 20 +++ test/fetch/fetch.node.js | 155 ++++++++++++++++++ tsconfig.json | 1 + 11 files changed, 932 insertions(+), 2 deletions(-) create mode 100644 src/fetch/README.md create mode 100644 src/fetch/constants.js create mode 100644 src/fetch/index.js create mode 100644 src/fetch/proto.d.ts create mode 100644 src/fetch/proto.js create mode 100644 src/fetch/proto.proto create mode 100644 test/fetch/fetch.node.js diff --git a/doc/API.md b/doc/API.md index 96fc9fc62b..fd2cd82452 100644 --- a/doc/API.md +++ b/doc/API.md @@ -12,6 +12,9 @@ * [`handle`](#handle) * [`unhandle`](#unhandle) * [`ping`](#ping) + * [`fetch`](#fetch) + * [`fetchService.registerLookupFunction`](#fetchserviceregisterlookupfunction) + * [`fetchService.unRegisterLookupFunction`](#fetchserviceunregisterlookupfunction) * [`multiaddrs`](#multiaddrs) * [`addressManager.getListenAddrs`](#addressmanagergetlistenaddrs) * [`addressManager.getAnnounceAddrs`](#addressmanagergetannounceaddrs) @@ -455,6 +458,72 @@ Pings a given peer and get the operation's latency. const latency = await libp2p.ping(otherPeerId) ``` +## fetch + +Fetch a value from a remote node + +`libp2p.fetch(peer, key)` + +#### Parameters + +| Name | Type | Description | +|------|------|-------------| +| peer | [`PeerId`][peer-id]\|[`Multiaddr`][multiaddr]\|`string` | peer to ping | +| key | `string` | A key that corresponds to a value on the remote node | + +#### Returns + +| Type | Description | +|------|-------------| +| `Promise` | The value for the key or null if it cannot be found | + +#### Example + +```js +// ... +const value = await libp2p.fetch(otherPeerId, '/some/key') +``` + +## fetchService.registerLookupFunction + +Register a function to look up values requested by remote nodes + +`libp2p.fetchService.registerLookupFunction(prefix, lookup)` + +#### Parameters + +| Name | Type | Description | +|------|------|-------------| +| prefix | `string` | All queries below this prefix will be passed to the lookup function | +| lookup | `(key: string) => Promise` | A function that takes a key and returns a Uint8Array or null | + +#### Example + +```js +// ... +const value = await libp2p.fetchService.registerLookupFunction('/prefix', (key) => { ... }) +``` + +## fetchService.unregisterLookupFunction + +Removes the passed lookup function or any function registered for the passed prefix + +`libp2p.fetchService.unregisterLookupFunction(prefix, lookup)` + +#### Parameters + +| Name | Type | Description | +|------|------|-------------| +| prefix | `string` | All queries below this prefix will be passed to the lookup function | +| lookup | `(key: string) => Promise` | Optional: A function that takes a key and returns a Uint8Array or null | + +#### Example + +```js +// ... +libp2p.fetchService.unregisterLookupFunction('/prefix') +``` + ## multiaddrs Gets the multiaddrs the libp2p node announces to the network. This computes the advertising multiaddrs diff --git a/package.json b/package.json index c78c349c67..357ddd9213 100644 --- a/package.json +++ b/package.json @@ -20,15 +20,17 @@ "scripts": { "lint": "aegir lint", "build": "aegir build", - "build:proto": "npm run build:proto:circuit && npm run build:proto:identify && npm run build:proto:plaintext && npm run build:proto:address-book && npm run build:proto:proto-book && npm run build:proto:peer && npm run build:proto:peer-record && npm run build:proto:envelope", + "build:proto": "npm run build:proto:circuit && npm run build:proto:fetch && npm run build:proto:identify && npm run build:proto:plaintext && npm run build:proto:address-book && npm run build:proto:proto-book && npm run build:proto:peer && npm run build:proto:peer-record && npm run build:proto:envelope", "build:proto:circuit": "pbjs -t static-module -w commonjs -r libp2p-circuit --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/circuit/protocol/index.js ./src/circuit/protocol/index.proto", + "build:proto:fetch": "pbjs -t static-module -w commonjs -r libp2p-fetch --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/fetch/proto.js ./src/fetch/proto.proto", "build:proto:identify": "pbjs -t static-module -w commonjs -r libp2p-identify --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/identify/message.js ./src/identify/message.proto", "build:proto:plaintext": "pbjs -t static-module -w commonjs -r libp2p-plaintext --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/insecure/proto.js ./src/insecure/proto.proto", "build:proto:peer": "pbjs -t static-module -w commonjs -r libp2p-peer --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/peer-store/pb/peer.js ./src/peer-store/pb/peer.proto", "build:proto:peer-record": "pbjs -t static-module -w commonjs -r libp2p-peer-record --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/record/peer-record/peer-record.js ./src/record/peer-record/peer-record.proto", "build:proto:envelope": "pbjs -t static-module -w commonjs -r libp2p-envelope --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/record/envelope/envelope.js ./src/record/envelope/envelope.proto", - "build:proto-types": "npm run build:proto-types:circuit && npm run build:proto-types:identify && npm run build:proto-types:plaintext && npm run build:proto-types:address-book && npm run build:proto-types:proto-book && npm run build:proto-types:peer && npm run build:proto-types:peer-record && npm run build:proto-types:envelope", + "build:proto-types": "npm run build:proto-types:circuit && npm run build:proto-types:fetch && npm run build:proto-types:identify && npm run build:proto-types:plaintext && npm run build:proto-types:address-book && npm run build:proto-types:proto-book && npm run build:proto-types:peer && npm run build:proto-types:peer-record && npm run build:proto-types:envelope", "build:proto-types:circuit": "pbts -o src/circuit/protocol/index.d.ts src/circuit/protocol/index.js", + "build:proto-types:fetch": "pbts -o src/fetch/proto.d.ts src/fetch/proto.js", "build:proto-types:identify": "pbts -o src/identify/message.d.ts src/identify/message.js", "build:proto-types:plaintext": "pbts -o src/insecure/proto.d.ts src/insecure/proto.js", "build:proto-types:peer": "pbts -o src/peer-store/pb/peer.d.ts src/peer-store/pb/peer.js", diff --git a/src/fetch/README.md b/src/fetch/README.md new file mode 100644 index 0000000000..7ea9997a5e --- /dev/null +++ b/src/fetch/README.md @@ -0,0 +1,36 @@ +libp2p-fetch JavaScript Implementation +===================================== + +> Libp2p fetch protocol JavaScript implementation + +## Overview + +An implementation of the Fetch protocol as described here: https://github.com/libp2p/specs/tree/master/fetch + +The fetch protocol is a simple protocol for requesting a value corresponding to a key from a peer. + +## Usage + +```javascript +const Libp2p = require('libp2p') + +/** + * Given a key (as a string) returns a value (as a Uint8Array), or null if the key isn't found. + * All keys must be prefixed my the same prefix, which will be used to find the appropriate key + * lookup function. + * @param key - a string + * @returns value - a Uint8Array value that corresponds to the given key, or null if the key doesn't + * have a corresponding value. + */ +async function my_subsystem_key_lookup(key) { + // app specific callback to lookup key-value pairs. +} + +// Enable this peer to respond to fetch requests for keys that begin with '/my_subsystem_key_prefix/' +const libp2p = Libp2p.create(...) +libp2p.fetchService.registerLookupFunction('/my_subsystem_key_prefix/', my_subsystem_key_lookup) + +const key = '/my_subsystem_key_prefix/{...}' +const peerDst = PeerId.parse('Qmfoo...') // or Multiaddr instance +const value = await libp2p.fetch(peerDst, key) +``` diff --git a/src/fetch/constants.js b/src/fetch/constants.js new file mode 100644 index 0000000000..2c1044e535 --- /dev/null +++ b/src/fetch/constants.js @@ -0,0 +1,6 @@ +'use strict' + +module.exports = { + // https://github.com/libp2p/specs/tree/master/fetch#wire-protocol + PROTOCOL: '/libp2p/fetch/0.0.1' +} diff --git a/src/fetch/index.js b/src/fetch/index.js new file mode 100644 index 0000000000..ef8c37f153 --- /dev/null +++ b/src/fetch/index.js @@ -0,0 +1,159 @@ +'use strict' + +const debug = require('debug') +const log = Object.assign(debug('libp2p:fetch'), { + error: debug('libp2p:fetch:err') +}) +const errCode = require('err-code') +const { codes } = require('../errors') +const lp = require('it-length-prefixed') +const { FetchRequest, FetchResponse } = require('./proto') +// @ts-ignore it-handshake does not export types +const handshake = require('it-handshake') +const { PROTOCOL } = require('./constants') + +/** + * @typedef {import('../')} Libp2p + * @typedef {import('multiaddr').Multiaddr} Multiaddr + * @typedef {import('peer-id')} PeerId + * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream + * @typedef {(key: string) => Promise} LookupFunction + */ + +/** + * A simple libp2p protocol for requesting a value corresponding to a key from a peer. + * Developers can register one or more lookup function for retrieving the value corresponding to + * a given key. Each lookup function must act on a distinct part of the overall key space, defined + * by a fixed prefix that all keys that should be routed to that lookup function will start with. + */ +class FetchProtocol { + /** + * @param {Libp2p} libp2p + */ + constructor (libp2p) { + this._lookupFunctions = new Map() // Maps key prefix to value lookup function + this._libp2p = libp2p + this.handleMessage = this.handleMessage.bind(this) + } + + /** + * Sends a request to fetch the value associated with the given key from the given peer. + * + * @param {PeerId|Multiaddr} peer + * @param {string} key + * @returns {Promise} + */ + async fetch (peer, key) { + // @ts-ignore multiaddr might not have toB58String + log('dialing %s to %s', this._protocol, peer.toB58String ? peer.toB58String() : peer) + + const connection = await this._libp2p.dial(peer) + const { stream } = await connection.newStream(FetchProtocol.PROTOCOL) + const shake = handshake(stream) + + // send message + const request = new FetchRequest({ identifier: key }) + shake.write(lp.encode.single(FetchRequest.encode(request).finish())) + + // read response + const response = FetchResponse.decode((await lp.decode.fromReader(shake.reader).next()).value.slice()) + switch (response.status) { + case (FetchResponse.StatusCode.OK): { + return response.data + } + case (FetchResponse.StatusCode.NOT_FOUND): { + return null + } + case (FetchResponse.StatusCode.ERROR): { + const errmsg = (new TextDecoder()).decode(response.data) + throw errCode(new Error('Error in fetch protocol response: ' + errmsg), codes.ERR_INVALID_PARAMETERS) + } + default: { + throw errCode(new Error('Unknown response status'), codes.ERR_INVALID_MESSAGE) + } + } + } + + /** + * Invoked when a fetch request is received. Reads the request message off the given stream and + * responds based on looking up the key in the request via the lookup callback that corresponds + * to the key's prefix. + * + * @param {object} options + * @param {MuxedStream} options.stream + * @param {string} options.protocol + */ + async handleMessage (options) { + const { stream } = options + const shake = handshake(stream) + const request = FetchRequest.decode((await lp.decode.fromReader(shake.reader).next()).value.slice()) + + let response + const lookup = this._getLookupFunction(request.identifier) + if (lookup) { + const data = await lookup(request.identifier) + if (data) { + response = new FetchResponse({ status: FetchResponse.StatusCode.OK, data }) + } else { + response = new FetchResponse({ status: FetchResponse.StatusCode.NOT_FOUND }) + } + } else { + const errmsg = (new TextEncoder()).encode('No lookup function registered for key: ' + request.identifier) + response = new FetchResponse({ status: FetchResponse.StatusCode.ERROR, data: errmsg }) + } + + shake.write(lp.encode.single(FetchResponse.encode(response).finish())) + } + + /** + * Given a key, finds the appropriate function for looking up its corresponding value, based on + * the key's prefix. + * + * @param {string} key + */ + _getLookupFunction (key) { + for (const prefix of this._lookupFunctions.keys()) { + if (key.startsWith(prefix)) { + return this._lookupFunctions.get(prefix) + } + } + return null + } + + /** + * Registers a new lookup callback that can map keys to values, for a given set of keys that + * share the same prefix. + * + * @param {string} prefix + * @param {LookupFunction} lookup + */ + registerLookupFunction (prefix, lookup) { + if (this._lookupFunctions.has(prefix)) { + throw errCode(new Error("Fetch protocol handler for key prefix '" + prefix + "' already registered"), codes.ERR_KEY_ALREADY_EXISTS) + } + this._lookupFunctions.set(prefix, lookup) + } + + /** + * Registers a new lookup callback that can map keys to values, for a given set of keys that + * share the same prefix. + * + * @param {string} prefix + * @param {LookupFunction} [lookup] + */ + unregisterLookupFunction (prefix, lookup) { + if (lookup != null) { + const existingLookup = this._lookupFunctions.get(prefix) + + if (existingLookup !== lookup) { + return + } + } + + this._lookupFunctions.delete(prefix) + } +} + +FetchProtocol.PROTOCOL = PROTOCOL + +exports = module.exports = FetchProtocol diff --git a/src/fetch/proto.d.ts b/src/fetch/proto.d.ts new file mode 100644 index 0000000000..bf022f516c --- /dev/null +++ b/src/fetch/proto.d.ts @@ -0,0 +1,134 @@ +import * as $protobuf from "protobufjs"; +/** Properties of a FetchRequest. */ +export interface IFetchRequest { + + /** FetchRequest identifier */ + identifier?: (string|null); +} + +/** Represents a FetchRequest. */ +export class FetchRequest implements IFetchRequest { + + /** + * Constructs a new FetchRequest. + * @param [p] Properties to set + */ + constructor(p?: IFetchRequest); + + /** FetchRequest identifier. */ + public identifier: string; + + /** + * Encodes the specified FetchRequest message. Does not implicitly {@link FetchRequest.verify|verify} messages. + * @param m FetchRequest message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: IFetchRequest, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a FetchRequest message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns FetchRequest + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): FetchRequest; + + /** + * Creates a FetchRequest message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns FetchRequest + */ + public static fromObject(d: { [k: string]: any }): FetchRequest; + + /** + * Creates a plain object from a FetchRequest message. Also converts values to other types if specified. + * @param m FetchRequest + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: FetchRequest, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this FetchRequest to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; +} + +/** Properties of a FetchResponse. */ +export interface IFetchResponse { + + /** FetchResponse status */ + status?: (FetchResponse.StatusCode|null); + + /** FetchResponse data */ + data?: (Uint8Array|null); +} + +/** Represents a FetchResponse. */ +export class FetchResponse implements IFetchResponse { + + /** + * Constructs a new FetchResponse. + * @param [p] Properties to set + */ + constructor(p?: IFetchResponse); + + /** FetchResponse status. */ + public status: FetchResponse.StatusCode; + + /** FetchResponse data. */ + public data: Uint8Array; + + /** + * Encodes the specified FetchResponse message. Does not implicitly {@link FetchResponse.verify|verify} messages. + * @param m FetchResponse message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: IFetchResponse, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a FetchResponse message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns FetchResponse + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): FetchResponse; + + /** + * Creates a FetchResponse message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns FetchResponse + */ + public static fromObject(d: { [k: string]: any }): FetchResponse; + + /** + * Creates a plain object from a FetchResponse message. Also converts values to other types if specified. + * @param m FetchResponse + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: FetchResponse, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this FetchResponse to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; +} + +export namespace FetchResponse { + + /** StatusCode enum. */ + enum StatusCode { + OK = 0, + NOT_FOUND = 1, + ERROR = 2 + } +} diff --git a/src/fetch/proto.js b/src/fetch/proto.js new file mode 100644 index 0000000000..f7de2b1dd5 --- /dev/null +++ b/src/fetch/proto.js @@ -0,0 +1,333 @@ +/*eslint-disable*/ +"use strict"; + +var $protobuf = require("protobufjs/minimal"); + +// Common aliases +var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; + +// Exported root namespace +var $root = $protobuf.roots["libp2p-fetch"] || ($protobuf.roots["libp2p-fetch"] = {}); + +$root.FetchRequest = (function() { + + /** + * Properties of a FetchRequest. + * @exports IFetchRequest + * @interface IFetchRequest + * @property {string|null} [identifier] FetchRequest identifier + */ + + /** + * Constructs a new FetchRequest. + * @exports FetchRequest + * @classdesc Represents a FetchRequest. + * @implements IFetchRequest + * @constructor + * @param {IFetchRequest=} [p] Properties to set + */ + function FetchRequest(p) { + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + /** + * FetchRequest identifier. + * @member {string} identifier + * @memberof FetchRequest + * @instance + */ + FetchRequest.prototype.identifier = ""; + + /** + * Encodes the specified FetchRequest message. Does not implicitly {@link FetchRequest.verify|verify} messages. + * @function encode + * @memberof FetchRequest + * @static + * @param {IFetchRequest} m FetchRequest message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + FetchRequest.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.identifier != null && Object.hasOwnProperty.call(m, "identifier")) + w.uint32(10).string(m.identifier); + return w; + }; + + /** + * Decodes a FetchRequest message from the specified reader or buffer. + * @function decode + * @memberof FetchRequest + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {FetchRequest} FetchRequest + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + FetchRequest.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.FetchRequest(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + m.identifier = r.string(); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates a FetchRequest message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof FetchRequest + * @static + * @param {Object.} d Plain object + * @returns {FetchRequest} FetchRequest + */ + FetchRequest.fromObject = function fromObject(d) { + if (d instanceof $root.FetchRequest) + return d; + var m = new $root.FetchRequest(); + if (d.identifier != null) { + m.identifier = String(d.identifier); + } + return m; + }; + + /** + * Creates a plain object from a FetchRequest message. Also converts values to other types if specified. + * @function toObject + * @memberof FetchRequest + * @static + * @param {FetchRequest} m FetchRequest + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + FetchRequest.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.defaults) { + d.identifier = ""; + } + if (m.identifier != null && m.hasOwnProperty("identifier")) { + d.identifier = m.identifier; + } + return d; + }; + + /** + * Converts this FetchRequest to JSON. + * @function toJSON + * @memberof FetchRequest + * @instance + * @returns {Object.} JSON object + */ + FetchRequest.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return FetchRequest; +})(); + +$root.FetchResponse = (function() { + + /** + * Properties of a FetchResponse. + * @exports IFetchResponse + * @interface IFetchResponse + * @property {FetchResponse.StatusCode|null} [status] FetchResponse status + * @property {Uint8Array|null} [data] FetchResponse data + */ + + /** + * Constructs a new FetchResponse. + * @exports FetchResponse + * @classdesc Represents a FetchResponse. + * @implements IFetchResponse + * @constructor + * @param {IFetchResponse=} [p] Properties to set + */ + function FetchResponse(p) { + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + /** + * FetchResponse status. + * @member {FetchResponse.StatusCode} status + * @memberof FetchResponse + * @instance + */ + FetchResponse.prototype.status = 0; + + /** + * FetchResponse data. + * @member {Uint8Array} data + * @memberof FetchResponse + * @instance + */ + FetchResponse.prototype.data = $util.newBuffer([]); + + /** + * Encodes the specified FetchResponse message. Does not implicitly {@link FetchResponse.verify|verify} messages. + * @function encode + * @memberof FetchResponse + * @static + * @param {IFetchResponse} m FetchResponse message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + FetchResponse.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.status != null && Object.hasOwnProperty.call(m, "status")) + w.uint32(8).int32(m.status); + if (m.data != null && Object.hasOwnProperty.call(m, "data")) + w.uint32(18).bytes(m.data); + return w; + }; + + /** + * Decodes a FetchResponse message from the specified reader or buffer. + * @function decode + * @memberof FetchResponse + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {FetchResponse} FetchResponse + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + FetchResponse.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.FetchResponse(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + m.status = r.int32(); + break; + case 2: + m.data = r.bytes(); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates a FetchResponse message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof FetchResponse + * @static + * @param {Object.} d Plain object + * @returns {FetchResponse} FetchResponse + */ + FetchResponse.fromObject = function fromObject(d) { + if (d instanceof $root.FetchResponse) + return d; + var m = new $root.FetchResponse(); + switch (d.status) { + case "OK": + case 0: + m.status = 0; + break; + case "NOT_FOUND": + case 1: + m.status = 1; + break; + case "ERROR": + case 2: + m.status = 2; + break; + } + if (d.data != null) { + if (typeof d.data === "string") + $util.base64.decode(d.data, m.data = $util.newBuffer($util.base64.length(d.data)), 0); + else if (d.data.length) + m.data = d.data; + } + return m; + }; + + /** + * Creates a plain object from a FetchResponse message. Also converts values to other types if specified. + * @function toObject + * @memberof FetchResponse + * @static + * @param {FetchResponse} m FetchResponse + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + FetchResponse.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.defaults) { + d.status = o.enums === String ? "OK" : 0; + if (o.bytes === String) + d.data = ""; + else { + d.data = []; + if (o.bytes !== Array) + d.data = $util.newBuffer(d.data); + } + } + if (m.status != null && m.hasOwnProperty("status")) { + d.status = o.enums === String ? $root.FetchResponse.StatusCode[m.status] : m.status; + } + if (m.data != null && m.hasOwnProperty("data")) { + d.data = o.bytes === String ? $util.base64.encode(m.data, 0, m.data.length) : o.bytes === Array ? Array.prototype.slice.call(m.data) : m.data; + } + return d; + }; + + /** + * Converts this FetchResponse to JSON. + * @function toJSON + * @memberof FetchResponse + * @instance + * @returns {Object.} JSON object + */ + FetchResponse.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * StatusCode enum. + * @name FetchResponse.StatusCode + * @enum {number} + * @property {number} OK=0 OK value + * @property {number} NOT_FOUND=1 NOT_FOUND value + * @property {number} ERROR=2 ERROR value + */ + FetchResponse.StatusCode = (function() { + var valuesById = {}, values = Object.create(valuesById); + values[valuesById[0] = "OK"] = 0; + values[valuesById[1] = "NOT_FOUND"] = 1; + values[valuesById[2] = "ERROR"] = 2; + return values; + })(); + + return FetchResponse; +})(); + +module.exports = $root; diff --git a/src/fetch/proto.proto b/src/fetch/proto.proto new file mode 100644 index 0000000000..95956f1622 --- /dev/null +++ b/src/fetch/proto.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +message FetchRequest { + string identifier = 1; +} + +message FetchResponse { + StatusCode status = 1; + enum StatusCode { + OK = 0; + NOT_FOUND = 1; + ERROR = 2; + } + bytes data = 2; +} diff --git a/src/index.js b/src/index.js index edb715442b..966eb77bbd 100644 --- a/src/index.js +++ b/src/index.js @@ -31,6 +31,7 @@ const PubsubAdapter = require('./pubsub-adapter') const Registrar = require('./registrar') const ping = require('./ping') const IdentifyService = require('./identify') +const FetchService = require('./fetch') const NatManager = require('./nat-manager') const { updateSelfPeerRecord } = require('./record/utils') @@ -323,6 +324,8 @@ class Libp2p extends EventEmitter { ping.mount(this) this._onDiscoveryPeer = this._onDiscoveryPeer.bind(this) + + this.fetchService = new FetchService(this) } /** @@ -356,6 +359,10 @@ class Libp2p extends EventEmitter { await this.handle(Object.values(IdentifyService.getProtocolStr(this)), this.identifyService.handleMessage) } + if (this.fetchService) { + await this.handle(FetchService.PROTOCOL, this.fetchService.handleMessage) + } + try { await this._onStarting() await this._onDidStart() @@ -407,6 +414,8 @@ class Libp2p extends EventEmitter { await this.natManager.stop() await this.transportManager.close() + this.unhandle(FetchService.PROTOCOL) + ping.unmount(this) this.dialer.destroy() } catch (/** @type {any} */ err) { @@ -559,6 +568,17 @@ class Libp2p extends EventEmitter { ) } + /** + * Sends a request to fetch the value associated with the given key from the given peer. + * + * @param {PeerId|Multiaddr} peer + * @param {string} key + * @returns {Promise} + */ + fetch (peer, key) { + return this.fetchService.fetch(peer, key) + } + /** * Pings the given peer in order to obtain the operation latency. * diff --git a/test/fetch/fetch.node.js b/test/fetch/fetch.node.js new file mode 100644 index 0000000000..da78495951 --- /dev/null +++ b/test/fetch/fetch.node.js @@ -0,0 +1,155 @@ +'use strict' +/* eslint-env mocha */ + +const { expect } = require('aegir/utils/chai') +const Libp2p = require('../../src') +const TCP = require('libp2p-tcp') +const Mplex = require('libp2p-mplex') +const { NOISE } = require('@chainsafe/libp2p-noise') +const MDNS = require('libp2p-mdns') +const { createPeerId } = require('../utils/creators/peer') +const { codes } = require('../../src/errors') +const { Multiaddr } = require('multiaddr') + +async function createLibp2pNode (peerId) { + return await Libp2p.create({ + peerId, + addresses: { + listen: ['/ip4/0.0.0.0/tcp/0'] + }, + modules: { + transport: [TCP], + streamMuxer: [Mplex], + connEncryption: [NOISE], + peerDiscovery: [MDNS] + } + }) +} + +describe('Fetch', () => { + /** @type {Libp2p} */ + let sender + /** @type {Libp2p} */ + let receiver + const PREFIX_A = '/moduleA/' + const PREFIX_B = '/moduleB/' + const DATA_A = { foobar: 'hello world' } + const DATA_B = { foobar: 'goodnight moon' } + + const generateLookupFunction = function (prefix, data) { + return async function (key) { + key = key.slice(prefix.length) // strip prefix from key + const val = data[key] + if (val) { + return (new TextEncoder()).encode(val) + } + return null + } + } + + beforeEach(async () => { + const [peerIdA, peerIdB] = await createPeerId({ number: 2 }) + sender = await createLibp2pNode(peerIdA) + receiver = await createLibp2pNode(peerIdB) + + await sender.start() + await receiver.start() + + await Promise.all([ + ...sender.multiaddrs.map(addr => receiver.dial(addr.encapsulate(new Multiaddr(`/p2p/${sender.peerId}`)))), + ...receiver.multiaddrs.map(addr => sender.dial(addr.encapsulate(new Multiaddr(`/p2p/${receiver.peerId}`)))) + ]) + }) + + afterEach(async () => { + receiver.fetchService.unregisterLookupFunction(PREFIX_A) + receiver.fetchService.unregisterLookupFunction(PREFIX_B) + + await sender.stop() + await receiver.stop() + }) + + it('fetch key that exists in receivers datastore', async () => { + receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) + + const rawData = await sender.fetch(receiver.peerId, '/moduleA/foobar') + const value = (new TextDecoder()).decode(rawData) + expect(value).to.equal('hello world') + }) + + it('Different lookups for different prefixes', async () => { + receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) + receiver.fetchService.registerLookupFunction(PREFIX_B, generateLookupFunction(PREFIX_B, DATA_B)) + + const rawDataA = await sender.fetch(receiver.peerId, '/moduleA/foobar') + const valueA = (new TextDecoder()).decode(rawDataA) + expect(valueA).to.equal('hello world') + + // Different lookup functions can be registered on different prefixes, and have different + // values for the same key underneath the different prefix. + const rawDataB = await sender.fetch(receiver.peerId, '/moduleB/foobar') + const valueB = (new TextDecoder()).decode(rawDataB) + expect(valueB).to.equal('goodnight moon') + }) + + it('fetch key that does not exist in receivers datastore', async () => { + receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) + const result = await sender.fetch(receiver.peerId, '/moduleA/garbage') + + expect(result).to.equal(null) + }) + + it('fetch key with unknown prefix throws error', async () => { + receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) + + await expect(sender.fetch(receiver.peerId, '/moduleUNKNOWN/foobar')) + .to.eventually.be.rejected.with.property('code', codes.ERR_INVALID_PARAMETERS) + }) + + it('registering multiple handlers for same prefix errors', async () => { + receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) + + expect(() => receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_B))) + .to.throw().with.property('code', codes.ERR_KEY_ALREADY_EXISTS) + }) + + it('can unregister handler', async () => { + const lookupFunction = generateLookupFunction(PREFIX_A, DATA_A) + receiver.fetchService.registerLookupFunction(PREFIX_A, lookupFunction) + const rawDataA = await sender.fetch(receiver.peerId, '/moduleA/foobar') + const valueA = (new TextDecoder()).decode(rawDataA) + expect(valueA).to.equal('hello world') + + receiver.fetchService.unregisterLookupFunction(PREFIX_A, lookupFunction) + + await expect(sender.fetch(receiver.peerId, '/moduleA/foobar')) + .to.eventually.be.rejectedWith(/No lookup function registered for key/) + }) + + it('can unregister all handlers', async () => { + const lookupFunction = generateLookupFunction(PREFIX_A, DATA_A) + receiver.fetchService.registerLookupFunction(PREFIX_A, lookupFunction) + const rawDataA = await sender.fetch(receiver.peerId, '/moduleA/foobar') + const valueA = (new TextDecoder()).decode(rawDataA) + expect(valueA).to.equal('hello world') + + receiver.fetchService.unregisterLookupFunction(PREFIX_A) + + await expect(sender.fetch(receiver.peerId, '/moduleA/foobar')) + .to.eventually.be.rejectedWith(/No lookup function registered for key/) + }) + + it('does not unregister wrong handlers', async () => { + const lookupFunction = generateLookupFunction(PREFIX_A, DATA_A) + receiver.fetchService.registerLookupFunction(PREFIX_A, lookupFunction) + const rawDataA = await sender.fetch(receiver.peerId, '/moduleA/foobar') + const valueA = (new TextDecoder()).decode(rawDataA) + expect(valueA).to.equal('hello world') + + receiver.fetchService.unregisterLookupFunction(PREFIX_A, () => {}) + + const rawDataB = await sender.fetch(receiver.peerId, '/moduleA/foobar') + const valueB = (new TextDecoder()).decode(rawDataB) + expect(valueB).to.equal('hello world') + }) +}) diff --git a/tsconfig.json b/tsconfig.json index 0f357f9363..eafbf8ca8a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ ], "exclude": [ "src/circuit/protocol/index.js", // exclude generated file + "src/fetch/proto.js", // exclude generated file "src/identify/message.js", // exclude generated file "src/insecure/proto.js", // exclude generated file "src/peer-store/pb/peer.js", // exclude generated file From 9b22c6e2f987a20c6639cd07f31fe9c824e24923 Mon Sep 17 00:00:00 2001 From: Robert Kiel Date: Mon, 24 Jan 2022 18:59:14 +0100 Subject: [PATCH 339/447] fix: prevent auto-dialer from dialing self (#1104) Co-authored-by: Robert Kiel Co-authored-by: achingbrain --- package.json | 1 + src/connection-manager/auto-dialler.js | 21 +++++-- test/connection-manager/auto-dialler.spec.js | 64 ++++++++++++++++++++ 3 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 test/connection-manager/auto-dialler.spec.js diff --git a/package.json b/package.json index 357ddd9213..5165c8cf10 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "it-map": "^1.0.4", "it-merge": "^1.0.0", "it-pipe": "^1.1.0", + "it-sort": "^1.0.1", "it-take": "^1.0.0", "libp2p-crypto": "^0.21.2", "libp2p-interfaces": "^4.0.0", diff --git a/src/connection-manager/auto-dialler.js b/src/connection-manager/auto-dialler.js index d09273875d..1468c48ee7 100644 --- a/src/connection-manager/auto-dialler.js +++ b/src/connection-manager/auto-dialler.js @@ -5,6 +5,9 @@ const mergeOptions = require('merge-options') // @ts-ignore retimer does not have types const retimer = require('retimer') const all = require('it-all') +const { pipe } = require('it-pipe') +const filter = require('it-filter') +const sort = require('it-sort') const log = Object.assign(debug('libp2p:connection-manager:auto-dialler'), { error: debug('libp2p:connection-manager:auto-dialler:err') @@ -90,21 +93,27 @@ class AutoDialler { // Sort peers on whether we know protocols of public keys for them // TODO: assuming the `peerStore.getPeers()` order is stable this will mean // we keep trying to connect to the same peers? - const peers = (await all(this._libp2p.peerStore.getPeers())) - .sort((a, b) => { + const peers = await pipe( + this._libp2p.peerStore.getPeers(), + (source) => filter(source, (peer) => !peer.id.equals(this._libp2p.peerId)), + (source) => sort(source, (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 - }) + }), + (source) => all(source) + ) for (let i = 0; this._running && i < peers.length && this._libp2p.connections.size < minConnections; i++) { - if (!this._libp2p.connectionManager.get(peers[i].id)) { - log('connecting to a peerStore stored peer %s', peers[i].id.toB58String()) + const peer = peers[i] + + if (!this._libp2p.connectionManager.get(peer.id)) { + log('connecting to a peerStore stored peer %s', peer.id.toB58String()) try { - await this._libp2p.dialer.connectToPeer(peers[i].id) + await this._libp2p.dialer.connectToPeer(peer.id) } catch (/** @type {any} */ err) { log.error('could not connect to peerStore stored peer', err) } diff --git a/test/connection-manager/auto-dialler.spec.js b/test/connection-manager/auto-dialler.spec.js new file mode 100644 index 0000000000..4b69adfd6f --- /dev/null +++ b/test/connection-manager/auto-dialler.spec.js @@ -0,0 +1,64 @@ +'use strict' +/* eslint-env mocha */ + +const { expect } = require('aegir/utils/chai') +const sinon = require('sinon') +const AutoDialler = require('../../src/connection-manager/auto-dialler') +const pWaitFor = require('p-wait-for') +const PeerId = require('peer-id') +const delay = require('delay') + +describe('Auto-dialler', () => { + let autoDialler + let libp2p + let options + + beforeEach(async () => { + libp2p = {} + options = {} + autoDialler = new AutoDialler(libp2p, options) + }) + + afterEach(async () => { + sinon.restore() + }) + + it('should not dial self', async () => { + // peers with protocols are dialled before peers without protocols + const self = { + id: await PeerId.create(), + protocols: [ + '/foo/bar' + ] + } + const other = { + id: await PeerId.create(), + protocols: [] + } + + autoDialler._options.minConnections = 10 + libp2p.peerId = self.id + libp2p.connections = { + size: 1 + } + libp2p.peerStore = { + getPeers: sinon.stub().returns([self, other]) + } + libp2p.connectionManager = { + get: () => {} + } + libp2p.dialer = { + connectToPeer: sinon.stub().resolves() + } + + await autoDialler.start() + + await pWaitFor(() => libp2p.dialer.connectToPeer.callCount === 1) + await delay(1000) + + await autoDialler.stop() + + expect(libp2p.dialer.connectToPeer.callCount).to.equal(1) + expect(libp2p.dialer.connectToPeer.calledWith(self.id)).to.be.false() + }) +}) From ff32eba6a0fa222af1a7a46775d5e0346ad6ebdf Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 25 Jan 2022 16:27:01 +0000 Subject: [PATCH 340/447] feat: connection gater (#1142) Port of https://github.com/libp2p/go-libp2p-core/blob/master/connmgr/gater.go Adds a new configuration key `connectionGater` which allows denying the dialing of certain peers, individual multiaddrs and the creation of connections at certain points in the connection flow. Fixes: https://github.com/libp2p/js-libp2p/issues/175 Refs: https://github.com/libp2p/js-libp2p/issues/744 Refs: https://github.com/libp2p/js-libp2p/issues/769 Co-authored-by: mzdws <8580712+mzdws@user.noreply.gitee.com> --- doc/CONFIGURATION.md | 124 ++++++++++++ package.json | 1 + src/config.js | 2 + src/dialer/index.js | 21 +- src/errors.js | 2 + src/index.js | 21 +- src/peer-store/address-book.js | 69 ++++--- src/peer-store/index.js | 6 +- src/peer-store/store.js | 25 ++- src/types.ts | 98 +++++++++ src/upgrader.js | 29 +++ test/connection-manager/index.node.js | 233 +++++++++++++++++++++- test/dialing/direct.node.js | 51 +++-- test/dialing/direct.spec.js | 68 +++++-- test/identify/index.spec.js | 23 ++- test/peer-store/address-book.spec.js | 24 ++- test/peer-store/peer-store.spec.js | 11 +- test/registrar/registrar.spec.js | 6 +- test/transports/transport-manager.node.js | 5 +- test/upgrading/upgrader.spec.js | 30 ++- test/utils/mock-connection-gater.js | 19 ++ 21 files changed, 770 insertions(+), 98 deletions(-) create mode 100644 src/types.ts create mode 100644 test/utils/mock-connection-gater.js diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 73ef515898..0276c014eb 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -23,6 +23,9 @@ - [Setup with Keychain](#setup-with-keychain) - [Configuring Dialing](#configuring-dialing) - [Configuring Connection Manager](#configuring-connection-manager) + - [Configuring Connection Gater](#configuring-connection-gater) + - [Outgoing connections](#outgoing-connections) + - [Incoming connections](#incoming-connections) - [Configuring Transport Manager](#configuring-transport-manager) - [Configuring Metrics](#configuring-metrics) - [Configuring PeerStore](#configuring-peerstore) @@ -590,6 +593,127 @@ const node = await Libp2p.create({ }) ``` +#### Configuring Connection Gater + +The Connection Gater allows us to prevent making incoming and outgoing connections to peers and storing +multiaddrs in the address book. + +The order in which methods are called is as follows: + +##### Outgoing connections + +1. `connectionGater.denyDialPeer(...)` +2. `connectionGater.denyDialMultiaddr(...)` +3. `connectionGater.denyOutboundConnection(...)` +4. `connectionGater.denyOutboundEncryptedConnection(...)` +5. `connectionGater.denyOutboundUpgradedConnection(...)` + +##### Incoming connections + +1. `connectionGater.denyInboundConnection(...)` +2. `connectionGater.denyInboundEncryptedConnection(...)` +3. `connectionGater.denyInboundUpgradedConnection(...)` + +```js +const node = await Libp2p.create({ + // .. other config + connectionGater: { + /** + * denyDialMultiaddr tests whether we're permitted to Dial the + * specified peer. + * + * This is called by the dialer.connectToPeer implementation before + * dialling a peer. + * + * Return true to prevent dialing the passed peer. + */ + denyDialPeer: (peerId: PeerId) => Promise + + /** + * denyDialMultiaddr tests whether we're permitted to dial the specified + * multiaddr for the given peer. + * + * This is called by the dialer.connectToPeer implementation after it has + * resolved the peer's addrs, and prior to dialling each. + * + * Return true to prevent dialing the passed peer on the passed multiaddr. + */ + denyDialMultiaddr: (peerId: PeerId, multiaddr: Multiaddr) => Promise + + /** + * denyInboundConnection tests whether an incipient inbound connection is allowed. + * + * This is called by the upgrader, or by the transport directly (e.g. QUIC, + * Bluetooth), straight after it has accepted a connection from its socket. + * + * Return true to deny the incoming passed connection. + */ + denyInboundConnection: (maConn: MultiaddrConnection) => Promise + + /** + * denyOutboundConnection tests whether an incipient outbound connection is allowed. + * + * This is called by the upgrader, or by the transport directly (e.g. QUIC, + * Bluetooth), straight after it has created a connection with its socket. + * + * Return true to deny the incoming passed connection. + */ + denyOutboundConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * denyInboundEncryptedConnection tests whether a given connection, now encrypted, + * is allowed. + * + * This is called by the upgrader, after it has performed the security + * handshake, and before it negotiates the muxer, or by the directly by the + * transport, at the exact same checkpoint. + * + * Return true to deny the passed secured connection. + */ + denyInboundEncryptedConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * denyOutboundEncryptedConnection tests whether a given connection, now encrypted, + * is allowed. + * + * This is called by the upgrader, after it has performed the security + * handshake, and before it negotiates the muxer, or by the directly by the + * transport, at the exact same checkpoint. + * + * Return true to deny the passed secured connection. + */ + denyOutboundEncryptedConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * denyInboundUpgradedConnection tests whether a fully capable connection is allowed. + * + * This is called after encryption has been negotiated and the connection has been + * multiplexed, if a multiplexer is configured. + * + * Return true to deny the passed upgraded connection. + */ + denyInboundUpgradedConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * denyOutboundUpgradedConnection tests whether a fully capable connection is allowed. + * + * This is called after encryption has been negotiated and the connection has been + * multiplexed, if a multiplexer is configured. + * + * Return true to deny the passed upgraded connection. + */ + denyOutboundUpgradedConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * Used by the address book to filter passed addresses. + * + * Return true to allow storing the passed multiaddr for the passed peer. + */ + filterMultiaddrForPeer: (peer: PeerId, multiaddr: Multiaddr) => Promise + } +}) +``` + #### Configuring Transport Manager The Transport Manager is responsible for managing the libp2p transports life cycle. This includes starting listeners for the provided listen addresses, closing these listeners and dialing using the provided transports. By default, if a libp2p node has a list of multiaddrs for listening on and there are no valid transports for those multiaddrs, libp2p will throw an error on startup and shutdown. However, for some applications it is perfectly acceptable for libp2p nodes to start in dial only mode if all the listen multiaddrs failed. This error tolerance can be enabled as follows: diff --git a/package.json b/package.json index 5165c8cf10..7fc234eb18 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "it-drain": "^1.0.3", "it-filter": "^1.0.1", "it-first": "^1.0.4", + "it-foreach": "^0.1.1", "it-handshake": "^2.0.0", "it-length-prefixed": "^5.0.2", "it-map": "^1.0.4", diff --git a/src/config.js b/src/config.js index 0d9bbc4a84..b2d3f6044e 100644 --- a/src/config.js +++ b/src/config.js @@ -13,6 +13,7 @@ const { FaultTolerance } = require('./transport-manager') /** * @typedef {import('multiaddr').Multiaddr} Multiaddr + * @typedef {import('./types').ConnectionGater} ConnectionGater * @typedef {import('.').Libp2pOptions} Libp2pOptions * @typedef {import('.').constructorOptions} constructorOptions */ @@ -27,6 +28,7 @@ const DefaultConfig = { connectionManager: { minConnections: 25 }, + connectionGater: /** @type {ConnectionGater} */ {}, transportManager: { faultTolerance: FaultTolerance.FATAL_ALL }, diff --git a/src/dialer/index.js b/src/dialer/index.js index 06fd4d0c65..f2e82cd30b 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -1,6 +1,9 @@ 'use strict' const debug = require('debug') +const all = require('it-all') +const filter = require('it-filter') +const { pipe } = require('it-pipe') const log = Object.assign(debug('libp2p:dialer'), { error: debug('libp2p:dialer:err') }) @@ -33,12 +36,14 @@ const METRICS_PENDING_DIAL_TARGETS = 'pending-dial-targets' * @typedef {import('../peer-store/types').PeerStore} PeerStore * @typedef {import('../peer-store/types').Address} Address * @typedef {import('../transport-manager')} TransportManager + * @typedef {import('../types').ConnectionGater} ConnectionGater */ /** * @typedef {Object} DialerProperties * @property {PeerStore} peerStore * @property {TransportManager} transportManager + * @property {ConnectionGater} connectionGater * * @typedef {(addr:Multiaddr) => Promise} Resolver * @@ -70,6 +75,7 @@ class Dialer { constructor ({ transportManager, peerStore, + connectionGater, addressSorter = publicAddressesFirst, maxParallelDials = MAX_PARALLEL_DIALS, maxAddrsToDial = MAX_ADDRS_TO_DIAL, @@ -78,6 +84,7 @@ class Dialer { resolvers = {}, metrics }) { + this.connectionGater = connectionGater this.transportManager = transportManager this.peerStore = peerStore this.addressSorter = addressSorter @@ -136,6 +143,12 @@ class Dialer { * @returns {Promise} */ async connectToPeer (peer, options = {}) { + const { id } = getPeer(peer) + + if (await this.connectionGater.denyDialPeer(id)) { + throw errCode(new Error('The dial request is blocked by gater.allowDialPeer'), codes.ERR_PEER_DIAL_INTERCEPTED) + } + const dialTarget = await this._createCancellableDialTarget(peer) if (!dialTarget.addrs.length) { @@ -203,7 +216,13 @@ class Dialer { await this.peerStore.addressBook.add(id, multiaddrs) } - let knownAddrs = await this.peerStore.addressBook.getMultiaddrsForPeer(id, this.addressSorter) || [] + let knownAddrs = await pipe( + await this.peerStore.addressBook.getMultiaddrsForPeer(id, this.addressSorter), + (source) => filter(source, async (multiaddr) => { + return !(await this.connectionGater.denyDialMultiaddr(id, multiaddr)) + }), + (source) => all(source) + ) // 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. diff --git a/src/errors.js b/src/errors.js index 61f308667e..4c34ae511a 100644 --- a/src/errors.js +++ b/src/errors.js @@ -12,6 +12,8 @@ exports.codes = { PUBSUB_NOT_STARTED: 'ERR_PUBSUB_NOT_STARTED', DHT_NOT_STARTED: 'ERR_DHT_NOT_STARTED', CONN_ENCRYPTION_REQUIRED: 'ERR_CONN_ENCRYPTION_REQUIRED', + ERR_PEER_DIAL_INTERCEPTED: 'ERR_PEER_DIAL_INTERCEPTED', + ERR_CONNECTION_INTERCEPTED: 'ERR_CONNECTION_INTERCEPTED', ERR_INVALID_PROTOCOLS_FOR_STREAM: 'ERR_INVALID_PROTOCOLS_FOR_STREAM', ERR_CONNECTION_ENDED: 'ERR_CONNECTION_ENDED', ERR_CONNECTION_FAILED: 'ERR_CONNECTION_FAILED', diff --git a/src/index.js b/src/index.js index 966eb77bbd..1f1b745f9c 100644 --- a/src/index.js +++ b/src/index.js @@ -48,6 +48,7 @@ const { updateSelfPeerRecord } = require('./record/utils') * @typedef {import('libp2p-interfaces/src/pubsub').PubsubOptions} PubsubOptions * @typedef {import('interface-datastore').Datastore} Datastore * @typedef {import('./pnet')} Protector + * @typedef {import('./types').ConnectionGater} ConnectionGater * @typedef {Object} PersistentPeerStoreOptions * @property {number} [threshold] */ @@ -106,6 +107,7 @@ const { updateSelfPeerRecord } = require('./record/utils') * @property {Libp2pModules} modules libp2p modules to use * @property {import('./address-manager').AddressManagerOptions} [addresses] * @property {import('./connection-manager').ConnectionManagerOptions} [connectionManager] + * @property {Partial} [connectionGater] * @property {Datastore} [datastore] * @property {import('./dialer').DialerOptions} [dialer] * @property {import('./identify/index').HostProperties} [host] libp2p host @@ -172,10 +174,25 @@ class Libp2p extends EventEmitter { this.metrics = metrics } + /** @type {ConnectionGater} */ + this.connectionGater = { + denyDialPeer: async () => Promise.resolve(false), + denyDialMultiaddr: async () => Promise.resolve(false), + denyInboundConnection: async () => Promise.resolve(false), + denyOutboundConnection: async () => Promise.resolve(false), + denyInboundEncryptedConnection: async () => Promise.resolve(false), + denyOutboundEncryptedConnection: async () => Promise.resolve(false), + denyInboundUpgradedConnection: async () => Promise.resolve(false), + denyOutboundUpgradedConnection: async () => Promise.resolve(false), + filterMultiaddrForPeer: async () => Promise.resolve(true), + ...this._options.connectionGater + } + /** @type {import('./peer-store/types').PeerStore} */ this.peerStore = new PeerStore({ peerId: this.peerId, - datastore: (this.datastore && this._options.peerStore.persistence) ? this.datastore : new MemoryDatastore() + datastore: (this.datastore && this._options.peerStore.persistence) ? this.datastore : new MemoryDatastore(), + addressFilter: this.connectionGater.filterMultiaddrForPeer }) // Addresses {listen, announce, noAnnounce} @@ -220,6 +237,7 @@ class Libp2p extends EventEmitter { // Setup the Upgrader this.upgrader = new Upgrader({ + connectionGater: this.connectionGater, localPeer: this.peerId, metrics: this.metrics, onConnection: (connection) => this.connectionManager.onConnect(connection), @@ -262,6 +280,7 @@ class Libp2p extends EventEmitter { this.dialer = new Dialer({ transportManager: this.transportManager, + connectionGater: this.connectionGater, peerStore: this.peerStore, metrics: this.metrics, ...this._options.dialer diff --git a/src/peer-store/address-book.js b/src/peer-store/address-book.js index 05581a2eb0..b54ce98136 100644 --- a/src/peer-store/address-book.js +++ b/src/peer-store/address-book.js @@ -7,6 +7,11 @@ const PeerId = require('peer-id') const { codes } = require('../errors') const PeerRecord = require('../record/peer-record') const Envelope = require('../record/envelope') +const { pipe } = require('it-pipe') +const all = require('it-all') +const filter = require('it-filter') +const map = require('it-map') +const each = require('it-foreach') /** * @typedef {import('./types').PeerStore} PeerStore @@ -27,10 +32,12 @@ class PeerStoreAddressBook { /** * @param {PeerStore["emit"]} emit * @param {import('./types').Store} store + * @param {(peerId: PeerId, multiaddr: Multiaddr) => Promise} addressFilter */ - constructor (emit, store) { + constructor (emit, store, addressFilter) { this._emit = emit this._store = store + this._addressFilter = addressFilter } /** @@ -88,7 +95,7 @@ class PeerStoreAddressBook { // Replace unsigned addresses by the new ones from the record // TODO: Once we have ttls for the addresses, we should merge these in updatedPeer = await this._store.patchOrCreate(peerId, { - addresses: convertMultiaddrsToAddresses(multiaddrs, true), + addresses: await filterMultiaddrs(peerId, multiaddrs, this._addressFilter, true), peerRecordEnvelope: envelope.marshal() }) @@ -180,6 +187,11 @@ class PeerStoreAddressBook { throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } + if (!Array.isArray(multiaddrs)) { + log.error('multiaddrs must be an array of Multiaddrs') + throw errcode(new Error('multiaddrs must be an array of Multiaddrs'), codes.ERR_INVALID_PARAMETERS) + } + log('set await write lock') const release = await this._store.lock.writeLock() log('set got write lock') @@ -188,7 +200,7 @@ class PeerStoreAddressBook { let updatedPeer try { - const addresses = convertMultiaddrsToAddresses(multiaddrs) + const addresses = await filterMultiaddrs(peerId, multiaddrs, this._addressFilter) // No valid addresses found if (!addresses.length) { @@ -238,6 +250,11 @@ class PeerStoreAddressBook { throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } + if (!Array.isArray(multiaddrs)) { + log.error('multiaddrs must be an array of Multiaddrs') + throw errcode(new Error('multiaddrs must be an array of Multiaddrs'), codes.ERR_INVALID_PARAMETERS) + } + log('add await write lock') const release = await this._store.lock.writeLock() log('add got write lock') @@ -246,7 +263,7 @@ class PeerStoreAddressBook { let updatedPeer try { - const addresses = convertMultiaddrsToAddresses(multiaddrs) + const addresses = await filterMultiaddrs(peerId, multiaddrs, this._addressFilter) // No valid addresses found if (!addresses.length) { @@ -337,33 +354,29 @@ class PeerStoreAddressBook { } /** - * Transforms received multiaddrs into Address. - * - * @private + * @param {PeerId} peerId * @param {Multiaddr[]} multiaddrs - * @param {boolean} [isCertified] - * @returns {Address[]} + * @param {(peerId: PeerId, multiaddr: Multiaddr) => Promise} addressFilter + * @param {boolean} isCertified */ -function convertMultiaddrsToAddresses (multiaddrs, isCertified = false) { - if (!multiaddrs) { - log.error('multiaddrs must be provided to store data') - throw errcode(new Error('multiaddrs must be provided'), codes.ERR_INVALID_PARAMETERS) - } - - // create Address for each address with no duplicates - return Array.from( - new Set(multiaddrs.map(ma => ma.toString())) - ) - .map(addr => { - try { - return { - multiaddr: new Multiaddr(addr), - isCertified - } - } catch (err) { - throw errcode(err, codes.ERR_INVALID_PARAMETERS) +function filterMultiaddrs (peerId, multiaddrs, addressFilter, isCertified = false) { + return pipe( + multiaddrs, + (source) => each(source, (multiaddr) => { + if (!Multiaddr.isMultiaddr(multiaddr)) { + log.error('multiaddr must be an instance of Multiaddr') + throw errcode(new Error('multiaddr must be an instance of Multiaddr'), codes.ERR_INVALID_PARAMETERS) } - }) + }), + (source) => filter(source, (multiaddr) => addressFilter(peerId, multiaddr)), + (source) => map(source, (multiaddr) => { + return { + multiaddr: new Multiaddr(multiaddr.toString()), + isCertified + } + }), + (source) => all(source) + ) } module.exports = PeerStoreAddressBook diff --git a/src/peer-store/index.js b/src/peer-store/index.js index d17a9f2dc2..2dceac374a 100644 --- a/src/peer-store/index.js +++ b/src/peer-store/index.js @@ -12,6 +12,7 @@ const Store = require('./store') * @typedef {import('./types').PeerStore} PeerStore * @typedef {import('./types').Peer} Peer * @typedef {import('peer-id')} PeerId + * @typedef {import('multiaddr').Multiaddr} Multiaddr */ const log = Object.assign(debug('libp2p:peer-store'), { @@ -28,14 +29,15 @@ class DefaultPeerStore extends EventEmitter { * @param {object} properties * @param {PeerId} properties.peerId * @param {import('interface-datastore').Datastore} properties.datastore + * @param {(peerId: PeerId, multiaddr: Multiaddr) => Promise} properties.addressFilter */ - constructor ({ peerId, datastore }) { + constructor ({ peerId, datastore, addressFilter }) { super() this._peerId = peerId this._store = new Store(datastore) - this.addressBook = new AddressBook(this.emit.bind(this), this._store) + this.addressBook = new AddressBook(this.emit.bind(this), this._store, addressFilter) this.keyBook = new KeyBook(this.emit.bind(this), this._store) this.metadataBook = new MetadataBook(this.emit.bind(this), this._store) this.protoBook = new ProtoBook(this.emit.bind(this), this._store) diff --git a/src/peer-store/store.js b/src/peer-store/store.js index 8b3d29f429..34dcce366b 100644 --- a/src/peer-store/store.js +++ b/src/peer-store/store.js @@ -100,13 +100,26 @@ class PersistentStore { throw errcode(new Error('publicKey bytes do not match peer id publicKey bytes'), codes.ERR_INVALID_PARAMETERS) } + // dedupe addresses + const addressSet = new Set() + const buf = PeerPB.encode({ - addresses: peer.addresses.sort((a, b) => { - return a.multiaddr.toString().localeCompare(b.multiaddr.toString()) - }).map(({ multiaddr, isCertified }) => ({ - multiaddr: multiaddr.bytes, - isCertified - })), + addresses: peer.addresses + .filter(address => { + if (addressSet.has(address.multiaddr.toString())) { + return false + } + + addressSet.add(address.multiaddr.toString()) + return true + }) + .sort((a, b) => { + return a.multiaddr.toString().localeCompare(b.multiaddr.toString()) + }) + .map(({ multiaddr, isCertified }) => ({ + multiaddr: multiaddr.bytes, + isCertified + })), protocols: peer.protocols.sort(), pubKey: peer.pubKey ? marshalPublicKey(peer.pubKey) : undefined, metadata: [...peer.metadata.keys()].sort().map(key => ({ key, value: peer.metadata.get(key) })), diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000000..b8af9a6f08 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,98 @@ +import type PeerId from 'peer-id' +import type { Multiaddr } from 'multiaddr' +import type { MultiaddrConnection } from 'libp2p-interfaces/src/transport/types' + +export interface ConnectionGater { + /** + * denyDialMultiaddr tests whether we're permitted to Dial the + * specified peer. + * + * This is called by the dialer.connectToPeer implementation before + * dialling a peer. + * + * Return true to prevent dialing the passed peer. + */ + denyDialPeer: (peerId: PeerId) => Promise + + /** + * denyDialMultiaddr tests whether we're permitted to dial the specified + * multiaddr for the given peer. + * + * This is called by the dialer.connectToPeer implementation after it has + * resolved the peer's addrs, and prior to dialling each. + * + * Return true to prevent dialing the passed peer on the passed multiaddr. + */ + denyDialMultiaddr: (peerId: PeerId, multiaddr: Multiaddr) => Promise + + /** + * denyInboundConnection tests whether an incipient inbound connection is allowed. + * + * This is called by the upgrader, or by the transport directly (e.g. QUIC, + * Bluetooth), straight after it has accepted a connection from its socket. + * + * Return true to deny the incoming passed connection. + */ + denyInboundConnection: (maConn: MultiaddrConnection) => Promise + + /** + * denyOutboundConnection tests whether an incipient outbound connection is allowed. + * + * This is called by the upgrader, or by the transport directly (e.g. QUIC, + * Bluetooth), straight after it has created a connection with its socket. + * + * Return true to deny the incoming passed connection. + */ + denyOutboundConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * denyInboundEncryptedConnection tests whether a given connection, now encrypted, + * is allowed. + * + * This is called by the upgrader, after it has performed the security + * handshake, and before it negotiates the muxer, or by the directly by the + * transport, at the exact same checkpoint. + * + * Return true to deny the passed secured connection. + */ + denyInboundEncryptedConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * denyOutboundEncryptedConnection tests whether a given connection, now encrypted, + * is allowed. + * + * This is called by the upgrader, after it has performed the security + * handshake, and before it negotiates the muxer, or by the directly by the + * transport, at the exact same checkpoint. + * + * Return true to deny the passed secured connection. + */ + denyOutboundEncryptedConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * denyInboundUpgradedConnection tests whether a fully capable connection is allowed. + * + * This is called after encryption has been negotiated and the connection has been + * multiplexed, if a multiplexer is configured. + * + * Return true to deny the passed upgraded connection. + */ + denyInboundUpgradedConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * denyOutboundUpgradedConnection tests whether a fully capable connection is allowed. + * + * This is called after encryption has been negotiated and the connection has been + * multiplexed, if a multiplexer is configured. + * + * Return true to deny the passed upgraded connection. + */ + denyOutboundUpgradedConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * Used by the address book to filter passed addresses. + * + * Return true to allow storing the passed multiaddr for the passed peer. + */ + filterMultiaddrForPeer: (peer: PeerId, multiaddr: Multiaddr) => Promise +} diff --git a/src/upgrader.js b/src/upgrader.js index 58a6418f0a..cbfc77c3e4 100644 --- a/src/upgrader.js +++ b/src/upgrader.js @@ -22,6 +22,7 @@ const { codes } = require('./errors') * @typedef {import('libp2p-interfaces/src/crypto/types').Crypto} Crypto * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection * @typedef {import('multiaddr').Multiaddr} Multiaddr + * @typedef {import('./types').ConnectionGater} ConnectionGater */ /** @@ -35,6 +36,8 @@ class Upgrader { /** * @param {object} options * @param {PeerId} options.localPeer + * @param {ConnectionGater} options.connectionGater + * * @param {import('./metrics')} [options.metrics] * @param {Map} [options.cryptos] * @param {Map} [options.muxers] @@ -44,11 +47,13 @@ class Upgrader { constructor ({ localPeer, metrics, + connectionGater, cryptos = new Map(), muxers = new Map(), onConnectionEnd = () => {}, onConnection = () => {} }) { + this.connectionGater = connectionGater this.localPeer = localPeer this.metrics = metrics this.cryptos = cryptos @@ -76,6 +81,10 @@ class Upgrader { let setPeer let proxyPeer + if (await this.connectionGater.denyInboundConnection(maConn)) { + throw errCode(new Error('The multiaddr connection is blocked by gater.acceptConnection'), codes.ERR_CONNECTION_INTERCEPTED) + } + if (this.metrics) { ({ setTarget: setPeer, proxy: proxyPeer } = mutableProxy()) const idString = (Math.random() * 1e9).toString(36) + Date.now() @@ -99,6 +108,10 @@ class Upgrader { protocol: cryptoProtocol } = await this._encryptInbound(this.localPeer, protectedConn, this.cryptos)) + if (await this.connectionGater.denyInboundEncryptedConnection(remotePeer, encryptedConn)) { + throw errCode(new Error('The multiaddr connection is blocked by gater.acceptEncryptedConnection'), codes.ERR_CONNECTION_INTERCEPTED) + } + // Multiplex the connection if (this.muxers.size) { ({ stream: upgradedConn, Muxer } = await this._multiplexInbound(encryptedConn, this.muxers)) @@ -111,6 +124,10 @@ class Upgrader { throw err } + if (await this.connectionGater.denyInboundUpgradedConnection(remotePeer, encryptedConn)) { + throw errCode(new Error('The multiaddr connection is blocked by gater.acceptEncryptedConnection'), codes.ERR_CONNECTION_INTERCEPTED) + } + if (this.metrics) { this.metrics.updatePlaceholder(proxyPeer, remotePeer) setPeer(remotePeer) @@ -143,6 +160,10 @@ class Upgrader { const remotePeerId = PeerId.createFromB58String(idStr) + if (await this.connectionGater.denyOutboundConnection(remotePeerId, maConn)) { + throw errCode(new Error('The multiaddr connection is blocked by connectionGater.denyOutboundConnection'), codes.ERR_CONNECTION_INTERCEPTED) + } + let encryptedConn let remotePeer let upgradedConn @@ -174,6 +195,10 @@ class Upgrader { protocol: cryptoProtocol } = await this._encryptOutbound(this.localPeer, protectedConn, remotePeerId, this.cryptos)) + if (await this.connectionGater.denyOutboundEncryptedConnection(remotePeer, encryptedConn)) { + throw errCode(new Error('The multiaddr connection is blocked by gater.acceptEncryptedConnection'), codes.ERR_CONNECTION_INTERCEPTED) + } + // Multiplex the connection if (this.muxers.size) { ({ stream: upgradedConn, Muxer } = await this._multiplexOutbound(encryptedConn, this.muxers)) @@ -186,6 +211,10 @@ class Upgrader { throw err } + if (await this.connectionGater.denyOutboundUpgradedConnection(remotePeer, encryptedConn)) { + throw errCode(new Error('The multiaddr connection is blocked by gater.acceptEncryptedConnection'), codes.ERR_CONNECTION_INTERCEPTED) + } + if (this.metrics) { this.metrics.updatePlaceholder(proxyPeer, remotePeer) setPeer(remotePeer) diff --git a/test/connection-manager/index.node.js b/test/connection-manager/index.node.js index d187895c6a..19f59bffc5 100644 --- a/test/connection-manager/index.node.js +++ b/test/connection-manager/index.node.js @@ -7,12 +7,11 @@ const { CLOSED } = require('libp2p-interfaces/src/connection/status') 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') - -const listenMultiaddr = '/ip4/127.0.0.1/tcp/15002/ws' +const { codes } = require('../../src/errors') +const { Multiaddr } = require('multiaddr') describe('Connection Manager', () => { let libp2p @@ -27,7 +26,7 @@ describe('Connection Manager', () => { config: { peerId: peerIds[0], addresses: { - listen: [listenMultiaddr] + listen: ['/ip4/127.0.0.1/tcp/0/ws'] }, modules: baseOptions.modules } @@ -305,4 +304,230 @@ describe('libp2p.connections', () => { await remoteLibp2p.stop() }) }) + + describe('connection gater', () => { + let libp2p + let remoteLibp2p + + beforeEach(async () => { + [remoteLibp2p] = await peerUtils.createPeer({ + config: { + peerId: peerIds[1], + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0/ws'] + }, + modules: baseOptions.modules + } + }) + }) + + afterEach(async () => { + remoteLibp2p && await remoteLibp2p.stop() + libp2p && await libp2p.stop() + }) + + it('intercept peer dial', async () => { + const denyDialPeer = sinon.stub().returns(true) + + ;[libp2p] = await peerUtils.createPeer({ + config: { + peerId: peerIds[0], + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0/ws'] + }, + modules: baseOptions.modules, + connectionGater: { + denyDialPeer + } + } + }) + await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) + + await expect(libp2p.dial(remoteLibp2p.peerId)) + .to.eventually.be.rejected().with.property('code', codes.ERR_PEER_DIAL_INTERCEPTED) + }) + + it('intercept addr dial', async () => { + const denyDialMultiaddr = sinon.stub().returns(false) + + ;[libp2p] = await peerUtils.createPeer({ + config: { + peerId: peerIds[0], + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0/ws'] + }, + modules: baseOptions.modules, + connectionGater: { + denyDialMultiaddr + } + } + }) + await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) + await libp2p.dialer.connectToPeer(remoteLibp2p.peerId) + + const peerIdMultiaddr = new Multiaddr(`/p2p/${remoteLibp2p.peerId}`) + + for (const multiaddr of remoteLibp2p.multiaddrs) { + expect(denyDialMultiaddr.calledWith(remoteLibp2p.peerId, multiaddr.encapsulate(peerIdMultiaddr))).to.be.true() + } + }) + + it('intercept multiaddr store during multiaddr dial', async () => { + const filterMultiaddrForPeer = sinon.stub().returns(true) + + ;[libp2p] = await peerUtils.createPeer({ + config: { + peerId: peerIds[0], + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0/ws'] + }, + modules: baseOptions.modules, + connectionGater: { + filterMultiaddrForPeer + } + } + }) + + const peerIdMultiaddr = new Multiaddr(`/p2p/${remoteLibp2p.peerId}`) + const fullMultiaddr = remoteLibp2p.multiaddrs[0].encapsulate(peerIdMultiaddr) + + await libp2p.dialer.connectToPeer(fullMultiaddr) + + expect(filterMultiaddrForPeer.callCount).to.equal(2) + + const args = filterMultiaddrForPeer.getCall(1).args + expect(args[0].toString()).to.equal(remoteLibp2p.peerId.toString()) + expect(args[1].toString()).to.equal(fullMultiaddr.toString()) + }) + + it('intercept accept inbound connection', async () => { + const denyInboundConnection = sinon.stub().returns(false) + + ;[libp2p] = await peerUtils.createPeer({ + config: { + peerId: peerIds[0], + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0/ws'] + }, + modules: baseOptions.modules, + connectionGater: { + denyInboundConnection + } + } + }) + await remoteLibp2p.peerStore.addressBook.set(libp2p.peerId, libp2p.multiaddrs) + await remoteLibp2p.dial(libp2p.peerId) + + expect(denyInboundConnection.called).to.be.true() + }) + + it('intercept accept outbound connection', async () => { + const denyOutboundConnection = sinon.stub().returns(false) + + ;[libp2p] = await peerUtils.createPeer({ + config: { + peerId: peerIds[0], + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0/ws'] + }, + modules: baseOptions.modules, + connectionGater: { + denyOutboundConnection + } + } + }) + await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) + await libp2p.dial(remoteLibp2p.peerId) + + expect(denyOutboundConnection.called).to.be.true() + }) + + it('intercept inbound encrypted', async () => { + const denyInboundEncryptedConnection = sinon.stub().returns(false) + + ;[libp2p] = await peerUtils.createPeer({ + config: { + peerId: peerIds[0], + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0/ws'] + }, + modules: baseOptions.modules, + connectionGater: { + denyInboundEncryptedConnection + } + } + }) + await remoteLibp2p.peerStore.addressBook.set(libp2p.peerId, libp2p.multiaddrs) + await remoteLibp2p.dial(libp2p.peerId) + + expect(denyInboundEncryptedConnection.called).to.be.true() + expect(denyInboundEncryptedConnection.getCall(0)).to.have.nested.property('args[0].id').that.equalBytes(remoteLibp2p.peerId.id) + }) + + it('intercept outbound encrypted', async () => { + const denyOutboundEncryptedConnection = sinon.stub().returns(false) + + ;[libp2p] = await peerUtils.createPeer({ + config: { + peerId: peerIds[0], + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0/ws'] + }, + modules: baseOptions.modules, + connectionGater: { + denyOutboundEncryptedConnection + } + } + }) + await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) + await libp2p.dial(remoteLibp2p.peerId) + + expect(denyOutboundEncryptedConnection.called).to.be.true() + expect(denyOutboundEncryptedConnection.getCall(0)).to.have.nested.property('args[0].id').that.equalBytes(remoteLibp2p.peerId.id) + }) + + it('intercept inbound upgraded', async () => { + const denyInboundUpgradedConnection = sinon.stub().returns(false) + + ;[libp2p] = await peerUtils.createPeer({ + config: { + peerId: peerIds[0], + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0/ws'] + }, + modules: baseOptions.modules, + connectionGater: { + denyInboundUpgradedConnection + } + } + }) + await remoteLibp2p.peerStore.addressBook.set(libp2p.peerId, libp2p.multiaddrs) + await remoteLibp2p.dial(libp2p.peerId) + + expect(denyInboundUpgradedConnection.called).to.be.true() + expect(denyInboundUpgradedConnection.getCall(0)).to.have.nested.property('args[0].id').that.equalBytes(remoteLibp2p.peerId.id) + }) + + it('intercept outbound upgraded', async () => { + const denyOutboundUpgradedConnection = sinon.stub().returns(false) + + ;[libp2p] = await peerUtils.createPeer({ + config: { + peerId: peerIds[0], + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0/ws'] + }, + modules: baseOptions.modules, + connectionGater: { + denyOutboundUpgradedConnection + } + } + }) + await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) + await libp2p.dial(remoteLibp2p.peerId) + + expect(denyOutboundUpgradedConnection.called).to.be.true() + expect(denyOutboundUpgradedConnection.getCall(0)).to.have.nested.property('args[0].id').that.equalBytes(remoteLibp2p.peerId.id) + }) + }) }) diff --git a/test/dialing/direct.node.js b/test/dialing/direct.node.js index b321fc2da7..6842811a18 100644 --- a/test/dialing/direct.node.js +++ b/test/dialing/direct.node.js @@ -27,7 +27,7 @@ const TransportManager = require('../../src/transport-manager') const { codes: ErrorCodes } = require('../../src/errors') const Protector = require('../../src/pnet') const swarmKeyBuffer = uint8ArrayFromString(require('../fixtures/swarm.key')) - +const { mockConnectionGater } = require('../utils/mock-connection-gater') const mockUpgrader = require('../utils/mockUpgrader') const createMockConnection = require('../utils/mockConnection') const Peers = require('../fixtures/peers') @@ -37,6 +37,7 @@ const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') const unsupportedAddr = new Multiaddr('/ip4/127.0.0.1/tcp/9999/ws/p2p/QmckxVrJw1Yo8LqvmDJNUmdAsKtSbiKWmrXJFyKmUraBoN') describe('Dialing (direct, TCP)', () => { + const connectionGater = mockConnectionGater() let remoteTM let localTM let peerStore @@ -50,7 +51,8 @@ describe('Dialing (direct, TCP)', () => { peerStore = new PeerStore({ peerId: remotePeerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) remoteTM = new TransportManager({ libp2p: { @@ -67,7 +69,8 @@ describe('Dialing (direct, TCP)', () => { peerId: localPeerId, peerStore: new PeerStore({ peerId: localPeerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) }, upgrader: mockUpgrader @@ -86,7 +89,11 @@ describe('Dialing (direct, TCP)', () => { }) it('should be able to connect to a remote node via its multiaddr', async () => { - const dialer = new Dialer({ transportManager: localTM, peerStore }) + const dialer = new Dialer({ + transportManager: localTM, + peerStore, + connectionGater + }) const connection = await dialer.connectToPeer(remoteAddr) expect(connection).to.exist() @@ -94,14 +101,22 @@ describe('Dialing (direct, TCP)', () => { }) it('should be able to connect to a remote node via its stringified multiaddr', async () => { - const dialer = new Dialer({ transportManager: localTM, peerStore }) + const dialer = new Dialer({ + transportManager: localTM, + peerStore, + connectionGater + }) const connection = await dialer.connectToPeer(remoteAddr.toString()) expect(connection).to.exist() await connection.close() }) it('should fail to connect to an unsupported multiaddr', async () => { - const dialer = new Dialer({ transportManager: localTM, peerStore }) + const dialer = new Dialer({ + transportManager: localTM, + peerStore, + connectionGater + }) await expect(dialer.connectToPeer(unsupportedAddr)) .to.eventually.be.rejectedWith(Error) @@ -109,7 +124,11 @@ describe('Dialing (direct, TCP)', () => { }) it('should fail to connect if peer has no known addresses', async () => { - const dialer = new Dialer({ transportManager: localTM, peerStore }) + const dialer = new Dialer({ + transportManager: localTM, + peerStore, + connectionGater + }) const peerId = await PeerId.createFromJSON(Peers[1]) await expect(dialer.connectToPeer(peerId)) @@ -121,11 +140,13 @@ describe('Dialing (direct, TCP)', () => { const peerId = await PeerId.createFromJSON(Peers[0]) const peerStore = new PeerStore({ peerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) const dialer = new Dialer({ transportManager: localTM, - peerStore + peerStore, + connectionGater }) await peerStore.addressBook.set(peerId, remoteTM.getAddrs()) @@ -143,7 +164,8 @@ describe('Dialing (direct, TCP)', () => { add: () => {}, getMultiaddrsForPeer: () => [unsupportedAddr] } - } + }, + connectionGater }) const peerId = await PeerId.createFromJSON(Peers[0]) @@ -161,7 +183,8 @@ describe('Dialing (direct, TCP)', () => { add: () => { }, getMultiaddrsForPeer: () => [...remoteAddrs, unsupportedAddr] } - } + }, + connectionGater }) const peerId = await PeerId.createFromJSON(Peers[0]) @@ -176,7 +199,8 @@ describe('Dialing (direct, TCP)', () => { const dialer = new Dialer({ transportManager: localTM, peerStore, - dialTimeout: 50 + dialTimeout: 50, + connectionGater }) sinon.stub(localTM, 'dial').callsFake(async (addr, options) => { expect(options.signal).to.exist() @@ -206,7 +230,8 @@ describe('Dialing (direct, TCP)', () => { add: () => {}, getMultiaddrsForPeer: () => addrs } - } + }, + connectionGater }) expect(dialer.tokens).to.have.length(2) diff --git a/test/dialing/direct.spec.js b/test/dialing/direct.spec.js index ba97352668..40193a905f 100644 --- a/test/dialing/direct.spec.js +++ b/test/dialing/direct.spec.js @@ -21,6 +21,7 @@ const addressSort = require('libp2p-utils/src/address-sort') const PeerStore = require('../../src/peer-store') const TransportManager = require('../../src/transport-manager') const Libp2p = require('../../src') +const { mockConnectionGater } = require('../utils/mock-connection-gater') const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser') const mockUpgrader = require('../utils/mockUpgrader') @@ -30,6 +31,7 @@ const unsupportedAddr = new Multiaddr('/ip4/127.0.0.1/tcp/9999/ws/p2p/QmckxVrJw1 const remoteAddr = MULTIADDRS_WEBSOCKETS[0] describe('Dialing (direct, WebSockets)', () => { + const connectionGater = mockConnectionGater() let localTM let peerStore let peerId @@ -38,7 +40,8 @@ describe('Dialing (direct, WebSockets)', () => { [peerId] = await createPeerId() peerStore = new PeerStore({ peerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) localTM = new TransportManager({ libp2p: {}, @@ -54,13 +57,21 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should have appropriate defaults', () => { - const dialer = new Dialer({ transportManager: localTM, peerStore }) + const dialer = new Dialer({ + transportManager: localTM, + peerStore, + connectionGater + }) expect(dialer.maxParallelDials).to.equal(Constants.MAX_PARALLEL_DIALS) expect(dialer.timeout).to.equal(Constants.DIAL_TIMEOUT) }) it('should limit the number of tokens it provides', () => { - const dialer = new Dialer({ transportManager: localTM, peerStore }) + const dialer = new Dialer({ + transportManager: localTM, + peerStore, + connectionGater + }) const maxPerPeer = Constants.MAX_PER_PEER_DIALS expect(dialer.tokens).to.have.length(Constants.MAX_PARALLEL_DIALS) const tokens = dialer.getTokens(maxPerPeer + 1) @@ -69,14 +80,22 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should not return tokens if non are left', () => { - const dialer = new Dialer({ transportManager: localTM, peerStore }) + const dialer = new Dialer({ + transportManager: localTM, + peerStore, + connectionGater + }) sinon.stub(dialer, 'tokens').value([]) const tokens = dialer.getTokens(1) expect(tokens.length).to.equal(0) }) it('should NOT be able to return a token twice', () => { - const dialer = new Dialer({ transportManager: localTM, peerStore }) + const dialer = new Dialer({ + transportManager: localTM, + peerStore, + connectionGater + }) const tokens = dialer.getTokens(1) expect(tokens).to.have.length(1) expect(dialer.tokens).to.have.length(Constants.MAX_PARALLEL_DIALS - 1) @@ -93,7 +112,8 @@ describe('Dialing (direct, WebSockets)', () => { add: () => {}, getMultiaddrsForPeer: () => [remoteAddr] } - } + }, + connectionGater }) const connection = await dialer.connectToPeer(remoteAddr) @@ -109,7 +129,8 @@ describe('Dialing (direct, WebSockets)', () => { add: () => {}, getMultiaddrsForPeer: () => [remoteAddr] } - } + }, + connectionGater }) const connection = await dialer.connectToPeer(remoteAddr.toString()) @@ -118,7 +139,11 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should fail to connect to an unsupported multiaddr', async () => { - const dialer = new Dialer({ transportManager: localTM, peerStore }) + const dialer = new Dialer({ + transportManager: localTM, + peerStore, + connectionGater + }) await expect(dialer.connectToPeer(unsupportedAddr)) .to.eventually.be.rejectedWith(AggregateError) @@ -132,7 +157,8 @@ describe('Dialing (direct, WebSockets)', () => { add: () => {}, getMultiaddrsForPeer: () => [remoteAddr] } - } + }, + connectionGater }) const connection = await dialer.connectToPeer(peerId) @@ -148,7 +174,8 @@ describe('Dialing (direct, WebSockets)', () => { set: () => {}, getMultiaddrsForPeer: () => [unsupportedAddr] } - } + }, + connectionGater }) await expect(dialer.connectToPeer(peerId)) @@ -164,7 +191,8 @@ describe('Dialing (direct, WebSockets)', () => { add: () => {}, getMultiaddrsForPeer: () => [remoteAddr] } - } + }, + connectionGater }) sinon.stub(localTM, 'dial').callsFake(async (addr, options) => { expect(options.signal).to.exist() @@ -191,7 +219,8 @@ describe('Dialing (direct, WebSockets)', () => { add: () => { }, getMultiaddrsForPeer: () => Array.from({ length: 11 }, (_, i) => new Multiaddr(`/ip4/127.0.0.1/tcp/1500${i}/ws/p2p/12D3KooWHFKTMzwerBtsVmtz4ZZEQy2heafxzWw6wNn5PPYkBxJ5`)) } - } + }, + connectionGater }) await expect(dialer.connectToPeer(remoteAddr)) @@ -214,7 +243,8 @@ describe('Dialing (direct, WebSockets)', () => { transportManager: localTM, addressSorter: addressSort.publicAddressesFirst, maxParallelDials: 3, - peerStore + peerStore, + connectionGater }) // Inject data in the AddressBook @@ -240,7 +270,8 @@ describe('Dialing (direct, WebSockets)', () => { set: () => {}, getMultiaddrsForPeer: () => [remoteAddr, remoteAddr, remoteAddr] } - } + }, + connectionGater }) expect(dialer.tokens).to.have.length(2) @@ -278,7 +309,8 @@ describe('Dialing (direct, WebSockets)', () => { set: () => {}, getMultiaddrsForPeer: () => [remoteAddr, remoteAddr, remoteAddr] } - } + }, + connectionGater }) expect(dialer.tokens).to.have.length(2) @@ -320,7 +352,8 @@ describe('Dialing (direct, WebSockets)', () => { addressBook: { set: () => { } } - } + }, + connectionGater }) sinon.stub(dialer, '_createDialTarget').callsFake(() => { @@ -365,7 +398,8 @@ describe('Dialing (direct, WebSockets)', () => { filter: filters.all } } - } + }, + connectionGater }) expect(libp2p.dialer).to.exist() diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js index 7e960dfe1b..8b8269d558 100644 --- a/test/identify/index.spec.js +++ b/test/identify/index.spec.js @@ -23,10 +23,12 @@ const pkg = require('../../package.json') const AddressManager = require('../../src/address-manager') const { MemoryDatastore } = require('datastore-core/memory') const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser') +const { mockConnectionGater } = require('../utils/mock-connection-gater') const remoteAddr = MULTIADDRS_WEBSOCKETS[0] const listenMaddrs = [new Multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] describe('Identify', () => { + const connectionGater = mockConnectionGater() let localPeer, localPeerStore, localAddressManager let remotePeer, remotePeerStore, remoteAddressManager const protocols = [multicodecs.IDENTIFY, multicodecs.IDENTIFY_PUSH] @@ -39,13 +41,15 @@ describe('Identify', () => { localPeerStore = new PeerStore({ peerId: localPeer, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) await localPeerStore.protoBook.set(localPeer, protocols) remotePeerStore = new PeerStore({ peerId: remotePeer, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) await remotePeerStore.protoBook.set(remotePeer, protocols) @@ -230,7 +234,8 @@ describe('Identify', () => { const agentVersion = 'js-project/1.0.0' const peerStore = new PeerStore({ peerId: localPeer, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) sinon.spy(peerStore.metadataBook, 'setValue') @@ -272,7 +277,8 @@ describe('Identify', () => { const localPeerStore = new PeerStore({ peerId: localPeer, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) await localPeerStore.protoBook.set(localPeer, storedProtocols) @@ -290,7 +296,8 @@ describe('Identify', () => { const remotePeerStore = new PeerStore({ peerId: remotePeer, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) await remotePeerStore.protoBook.set(remotePeer, storedProtocols) @@ -352,7 +359,8 @@ describe('Identify', () => { const localPeerStore = new PeerStore({ peerId: localPeer, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) await localPeerStore.protoBook.set(localPeer, storedProtocols) @@ -370,7 +378,8 @@ describe('Identify', () => { const remotePeerStore = new PeerStore({ peerId: remotePeer, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) await remotePeerStore.protoBook.set(remotePeer, storedProtocols) diff --git a/test/peer-store/address-book.spec.js b/test/peer-store/address-book.spec.js index 671e94f932..e67a899c73 100644 --- a/test/peer-store/address-book.spec.js +++ b/test/peer-store/address-book.spec.js @@ -13,7 +13,7 @@ const { MemoryDatastore } = require('datastore-core/memory') const PeerStore = require('../../src/peer-store') const Envelope = require('../../src/record/envelope') const PeerRecord = require('../../src/record/peer-record') - +const { mockConnectionGater } = require('../utils/mock-connection-gater') const peerUtils = require('../utils/creators/peer') const { codes: { ERR_INVALID_PARAMETERS } @@ -29,6 +29,7 @@ const addr2 = new Multiaddr('/ip4/20.0.0.1/tcp/8001') const addr3 = new Multiaddr('/ip4/127.0.0.1/tcp/8002') describe('addressBook', () => { + const connectionGater = mockConnectionGater() let peerId before(async () => { @@ -44,7 +45,8 @@ describe('addressBook', () => { beforeEach(() => { peerStore = new PeerStore({ peerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) ab = peerStore.addressBook }) @@ -164,7 +166,8 @@ describe('addressBook', () => { beforeEach(() => { peerStore = new PeerStore({ peerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) ab = peerStore.addressBook }) @@ -323,7 +326,8 @@ describe('addressBook', () => { beforeEach(() => { peerStore = new PeerStore({ peerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) ab = peerStore.addressBook }) @@ -364,7 +368,8 @@ describe('addressBook', () => { beforeEach(() => { peerStore = new PeerStore({ peerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) ab = peerStore.addressBook }) @@ -418,7 +423,8 @@ describe('addressBook', () => { beforeEach(() => { peerStore = new PeerStore({ peerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) ab = peerStore.addressBook }) @@ -478,7 +484,8 @@ describe('addressBook', () => { beforeEach(() => { peerStore = new PeerStore({ peerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) ab = peerStore.addressBook }) @@ -670,7 +677,8 @@ describe('addressBook', () => { beforeEach(() => { peerStore = new PeerStore({ peerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) ab = peerStore.addressBook }) diff --git a/test/peer-store/peer-store.spec.js b/test/peer-store/peer-store.spec.js index 3456417a1b..d0380025ab 100644 --- a/test/peer-store/peer-store.spec.js +++ b/test/peer-store/peer-store.spec.js @@ -8,6 +8,7 @@ const { Multiaddr } = require('multiaddr') const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') const { MemoryDatastore } = require('datastore-core/memory') const peerUtils = require('../utils/creators/peer') +const { mockConnectionGater } = require('../utils/mock-connection-gater') const addr1 = new Multiaddr('/ip4/127.0.0.1/tcp/8000') const addr2 = new Multiaddr('/ip4/127.0.0.1/tcp/8001') @@ -23,6 +24,7 @@ const proto3 = '/protocol3' */ describe('peer-store', () => { + const connectionGater = mockConnectionGater() let peerIds before(async () => { peerIds = await peerUtils.createPeerId({ @@ -37,7 +39,8 @@ describe('peer-store', () => { beforeEach(() => { peerStore = new PeerStore({ peerId: peerIds[4], - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) }) @@ -66,7 +69,8 @@ describe('peer-store', () => { beforeEach(async () => { peerStore = new PeerStore({ peerId: peerIds[4], - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) // Add peer0 with { addr1, addr2 } and { proto1 } @@ -170,7 +174,8 @@ describe('peer-store', () => { beforeEach(() => { peerStore = new PeerStore({ peerId: peerIds[4], - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) }) diff --git a/test/registrar/registrar.spec.js b/test/registrar/registrar.spec.js index b86f52a8fa..83f87fd695 100644 --- a/test/registrar/registrar.spec.js +++ b/test/registrar/registrar.spec.js @@ -9,7 +9,7 @@ const { MemoryDatastore } = require('datastore-core/memory') const Topology = require('libp2p-interfaces/src/topology/multicodec-topology') const PeerStore = require('../../src/peer-store') const Registrar = require('../../src/registrar') - +const { mockConnectionGater } = require('../utils/mock-connection-gater') const createMockConnection = require('../utils/mockConnection') const peerUtils = require('../utils/creators/peer') const baseOptions = require('../utils/base-options.browser') @@ -17,6 +17,7 @@ const baseOptions = require('../utils/base-options.browser') const multicodec = '/test/1.0.0' describe('registrar', () => { + const connectionGater = mockConnectionGater() let peerStore let registrar let peerId @@ -29,7 +30,8 @@ describe('registrar', () => { beforeEach(() => { peerStore = new PeerStore({ peerId, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) registrar = new Registrar({ peerStore, connectionManager: new EventEmitter() }) }) diff --git a/test/transports/transport-manager.node.js b/test/transports/transport-manager.node.js index f762bbe452..d22d091d5e 100644 --- a/test/transports/transport-manager.node.js +++ b/test/transports/transport-manager.node.js @@ -14,12 +14,14 @@ const mockUpgrader = require('../utils/mockUpgrader') const sinon = require('sinon') const Peers = require('../fixtures/peers') const pWaitFor = require('p-wait-for') +const { mockConnectionGater } = require('../utils/mock-connection-gater') const addrs = [ new Multiaddr('/ip4/127.0.0.1/tcp/0'), new Multiaddr('/ip4/127.0.0.1/tcp/0') ] describe('Transport Manager (TCP)', () => { + const connectionGater = mockConnectionGater() let tm let localPeer @@ -35,7 +37,8 @@ describe('Transport Manager (TCP)', () => { addressManager: new AddressManager({ listen: addrs }), peerStore: new PeerStore({ peerId: localPeer, - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + addressFilter: connectionGater.filterMultiaddrForPeer }) }, upgrader: mockUpgrader, diff --git a/test/upgrading/upgrader.spec.js b/test/upgrading/upgrader.spec.js index e78f7007c3..176c8c0a9f 100644 --- a/test/upgrading/upgrader.spec.js +++ b/test/upgrading/upgrader.spec.js @@ -18,7 +18,7 @@ const swarmKeyBuffer = uint8ArrayFromString(require('../fixtures/swarm.key')) const Libp2p = require('../../src') const Upgrader = require('../../src/upgrader') const { codes } = require('../../src/errors') - +const { mockConnectionGater } = require('../utils/mock-connection-gater') const mockMultiaddrConnPair = require('../utils/mockMultiaddrConn') const Peers = require('../fixtures/peers') const addrs = [ @@ -31,6 +31,17 @@ describe('Upgrader', () => { let remoteUpgrader let localPeer let remotePeer + const connectionGater = mockConnectionGater() + + const mockConnectionManager = { + gater: { + allowDialPeer: async () => true, + allowDialMultiaddr: async () => true, + acceptConnection: async () => true, + acceptEncryptedConnection: async () => true, + acceptUpgradedConnection: async () => true + } + } before(async () => { ([ @@ -42,10 +53,14 @@ describe('Upgrader', () => { ])) localUpgrader = new Upgrader({ - localPeer + connectionManager: mockConnectionManager, + localPeer, + connectionGater }) remoteUpgrader = new Upgrader({ - localPeer: remotePeer + connectionManager: mockConnectionManager, + localPeer: remotePeer, + connectionGater }) localUpgrader.protocols.set('/echo/1.0.0', ({ stream }) => pipe(stream, stream)) @@ -321,6 +336,7 @@ describe('Upgrader', () => { describe('libp2p.upgrader', () => { let peers let libp2p + const connectionGater = mockConnectionGater() before(async () => { peers = await Promise.all([ @@ -392,8 +408,10 @@ describe('libp2p.upgrader', () => { const remoteUpgrader = new Upgrader({ localPeer: remotePeer, + connectionManager: libp2p.connectionManager, muxers: new Map([[Muxer.multicodec, Muxer]]), - cryptos: new Map([[Crypto.protocol, Crypto]]) + cryptos: new Map([[Crypto.protocol, Crypto]]), + connectionGater }) remoteUpgrader.protocols.set('/echo/1.0.0', echoHandler) @@ -424,8 +442,10 @@ describe('libp2p.upgrader', () => { const remoteUpgrader = new Upgrader({ localPeer: remotePeer, + connectionManager: libp2p.connectionManager, muxers: new Map([[Muxer.multicodec, Muxer]]), - cryptos: new Map([[Crypto.protocol, Crypto]]) + cryptos: new Map([[Crypto.protocol, Crypto]]), + connectionGater }) const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) diff --git a/test/utils/mock-connection-gater.js b/test/utils/mock-connection-gater.js new file mode 100644 index 0000000000..b1081f4958 --- /dev/null +++ b/test/utils/mock-connection-gater.js @@ -0,0 +1,19 @@ +'use strict' + +function mockConnectionGater () { + return { + denyDialPeer: async () => Promise.resolve(false), + denyDialMultiaddr: async () => Promise.resolve(false), + denyInboundConnection: async () => Promise.resolve(false), + denyOutboundConnection: async () => Promise.resolve(false), + denyInboundEncryptedConnection: async () => Promise.resolve(false), + denyOutboundEncryptedConnection: async () => Promise.resolve(false), + denyInboundUpgradedConnection: async () => Promise.resolve(false), + denyOutboundUpgradedConnection: async () => Promise.resolve(false), + filterMultiaddrForPeer: async () => Promise.resolve(true) + } +} + +module.exports = { + mockConnectionGater +} From 831ed3970113a2d7f5a0563c0ea7dbc060eecc53 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 25 Jan 2022 16:43:46 +0000 Subject: [PATCH 341/447] chore: release 0.36.0 (#1127) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [0.36.0](https://www.github.com/libp2p/js-libp2p/compare/v0.35.8...v0.36.0) (2022-01-25) ### ⚠ BREAKING CHANGES * abort-controller dep is gone from dependency tree * `libp2p.handle`, `libp2p.registrar.register` and the peerstore methods have become async ### Features * add fetch protocol ([#1036](https://www.github.com/libp2p/js-libp2p/issues/1036)) ([d8ceb0b](https://www.github.com/libp2p/js-libp2p/commit/d8ceb0bc66fe225d1335d3f05b9a3a30983c2a57)) * async peerstore backed by datastores ([#1058](https://www.github.com/libp2p/js-libp2p/issues/1058)) ([978eb36](https://www.github.com/libp2p/js-libp2p/commit/978eb3676fad5d5d50ddb28d1a7868f448cbb20b)) * connection gater ([#1142](https://www.github.com/libp2p/js-libp2p/issues/1142)) ([ff32eba](https://www.github.com/libp2p/js-libp2p/commit/ff32eba6a0fa222af1a7a46775d5e0346ad6ebdf)) ### Bug Fixes * cache build artefacts ([#1091](https://www.github.com/libp2p/js-libp2p/issues/1091)) ([5043cd5](https://www.github.com/libp2p/js-libp2p/commit/5043cd56435a264e83db4fb8388d33e9a0442fff)) * catch errors during identify ([#1138](https://www.github.com/libp2p/js-libp2p/issues/1138)) ([12f1bb0](https://www.github.com/libp2p/js-libp2p/commit/12f1bb0aeec4b639bd2af05807215f3b4284e379)) * import uint8arrays package in example ([#1083](https://www.github.com/libp2p/js-libp2p/issues/1083)) ([c3700f5](https://www.github.com/libp2p/js-libp2p/commit/c3700f55d5a0b62182d683ca37258887b24065b9)) * make tests more reliable ([#1139](https://www.github.com/libp2p/js-libp2p/issues/1139)) ([b7e8706](https://www.github.com/libp2p/js-libp2p/commit/b7e87066a69970f1adca4ba552c7fdf624916a7e)) * prevent auto-dialer from dialing self ([#1104](https://www.github.com/libp2p/js-libp2p/issues/1104)) ([9b22c6e](https://www.github.com/libp2p/js-libp2p/commit/9b22c6e2f987a20c6639cd07f31fe9c824e24923)) * remove abort-controller dep ([#1095](https://www.github.com/libp2p/js-libp2p/issues/1095)) ([0a4dc54](https://www.github.com/libp2p/js-libp2p/commit/0a4dc54d084c901df47cce1788bd5922090ee037)) * try all peer addresses when dialing a relay ([#1140](https://www.github.com/libp2p/js-libp2p/issues/1140)) ([63aa480](https://www.github.com/libp2p/js-libp2p/commit/63aa480800974515f44d3b7e013da9c8ccaae8ad)) * update any-signal and timeout-abort-controller ([#1128](https://www.github.com/libp2p/js-libp2p/issues/1128)) ([e0354b4](https://www.github.com/libp2p/js-libp2p/commit/e0354b4c6b95bb90656b868849182eb3efddf096)) * update multistream select ([#1136](https://www.github.com/libp2p/js-libp2p/issues/1136)) ([00e4959](https://www.github.com/libp2p/js-libp2p/commit/00e49592a356e39b20c889d5f40b9bb37d4bf293)) * update node-forge ([#1133](https://www.github.com/libp2p/js-libp2p/issues/1133)) ([a4bba35](https://www.github.com/libp2p/js-libp2p/commit/a4bba35948e1cd8dbe5147f2c8d6385b1fbb6fae)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 31 ++++++++++++++++++++++++++++--- package.json | 2 +- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ee66cfe9c..64fe745e10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,34 @@ +## [0.36.0](https://www.github.com/libp2p/js-libp2p/compare/v0.35.8...v0.36.0) (2022-01-25) + + +### ⚠ BREAKING CHANGES + +* abort-controller dep is gone from dependency tree +* `libp2p.handle`, `libp2p.registrar.register` and the peerstore methods have become async + +### Features + +* add fetch protocol ([#1036](https://www.github.com/libp2p/js-libp2p/issues/1036)) ([d8ceb0b](https://www.github.com/libp2p/js-libp2p/commit/d8ceb0bc66fe225d1335d3f05b9a3a30983c2a57)) +* async peerstore backed by datastores ([#1058](https://www.github.com/libp2p/js-libp2p/issues/1058)) ([978eb36](https://www.github.com/libp2p/js-libp2p/commit/978eb3676fad5d5d50ddb28d1a7868f448cbb20b)) +* connection gater ([#1142](https://www.github.com/libp2p/js-libp2p/issues/1142)) ([ff32eba](https://www.github.com/libp2p/js-libp2p/commit/ff32eba6a0fa222af1a7a46775d5e0346ad6ebdf)) + + +### Bug Fixes + +* cache build artefacts ([#1091](https://www.github.com/libp2p/js-libp2p/issues/1091)) ([5043cd5](https://www.github.com/libp2p/js-libp2p/commit/5043cd56435a264e83db4fb8388d33e9a0442fff)) +* catch errors during identify ([#1138](https://www.github.com/libp2p/js-libp2p/issues/1138)) ([12f1bb0](https://www.github.com/libp2p/js-libp2p/commit/12f1bb0aeec4b639bd2af05807215f3b4284e379)) +* import uint8arrays package in example ([#1083](https://www.github.com/libp2p/js-libp2p/issues/1083)) ([c3700f5](https://www.github.com/libp2p/js-libp2p/commit/c3700f55d5a0b62182d683ca37258887b24065b9)) +* make tests more reliable ([#1139](https://www.github.com/libp2p/js-libp2p/issues/1139)) ([b7e8706](https://www.github.com/libp2p/js-libp2p/commit/b7e87066a69970f1adca4ba552c7fdf624916a7e)) +* prevent auto-dialer from dialing self ([#1104](https://www.github.com/libp2p/js-libp2p/issues/1104)) ([9b22c6e](https://www.github.com/libp2p/js-libp2p/commit/9b22c6e2f987a20c6639cd07f31fe9c824e24923)) +* remove abort-controller dep ([#1095](https://www.github.com/libp2p/js-libp2p/issues/1095)) ([0a4dc54](https://www.github.com/libp2p/js-libp2p/commit/0a4dc54d084c901df47cce1788bd5922090ee037)) +* try all peer addresses when dialing a relay ([#1140](https://www.github.com/libp2p/js-libp2p/issues/1140)) ([63aa480](https://www.github.com/libp2p/js-libp2p/commit/63aa480800974515f44d3b7e013da9c8ccaae8ad)) +* update any-signal and timeout-abort-controller ([#1128](https://www.github.com/libp2p/js-libp2p/issues/1128)) ([e0354b4](https://www.github.com/libp2p/js-libp2p/commit/e0354b4c6b95bb90656b868849182eb3efddf096)) +* update multistream select ([#1136](https://www.github.com/libp2p/js-libp2p/issues/1136)) ([00e4959](https://www.github.com/libp2p/js-libp2p/commit/00e49592a356e39b20c889d5f40b9bb37d4bf293)) +* update node-forge ([#1133](https://www.github.com/libp2p/js-libp2p/issues/1133)) ([a4bba35](https://www.github.com/libp2p/js-libp2p/commit/a4bba35948e1cd8dbe5147f2c8d6385b1fbb6fae)) + ## [0.35.7](https://github.com/libp2p/js-libp2p/compare/v0.35.2...v0.35.7) (2021-12-24) @@ -1761,6 +1789,3 @@ for subscribe to see how it should be used. ## [0.5.5](https://github.com/libp2p/js-libp2p/compare/v0.5.4...v0.5.5) (2017-03-21) - - - diff --git a/package.json b/package.json index 7fc234eb18..a8f8b967cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.35.8", + "version": "0.36.0", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From d44bd9094fe9545054eb8eff68f81bc52ece03e7 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 25 Jan 2022 19:56:56 +0000 Subject: [PATCH 342/447] fix: await unhandle of protocols (#1144) To allow us to shut down cleanly, we must wait the unhandling of protocols - this is because they write the new list of protocols into the datastore which might also be in the process of shutting down. --- src/index.js | 18 +++--- src/ping/index.js | 103 ++++++++++++++++---------------- test/upgrading/upgrader.spec.js | 6 +- 3 files changed, 64 insertions(+), 63 deletions(-) diff --git a/src/index.js b/src/index.js index 1f1b745f9c..f5e0f6c041 100644 --- a/src/index.js +++ b/src/index.js @@ -29,9 +29,9 @@ const Upgrader = require('./upgrader') const PeerStore = require('./peer-store') const PubsubAdapter = require('./pubsub-adapter') const Registrar = require('./registrar') -const ping = require('./ping') const IdentifyService = require('./identify') const FetchService = require('./fetch') +const PingService = require('./ping') const NatManager = require('./nat-manager') const { updateSelfPeerRecord } = require('./record/utils') @@ -339,12 +339,10 @@ class Libp2p extends EventEmitter { this.peerRouting = new PeerRouting(this) this.contentRouting = new ContentRouting(this) - // Mount default protocols - ping.mount(this) - this._onDiscoveryPeer = this._onDiscoveryPeer.bind(this) this.fetchService = new FetchService(this) + this.pingService = new PingService(this) } /** @@ -382,6 +380,10 @@ class Libp2p extends EventEmitter { await this.handle(FetchService.PROTOCOL, this.fetchService.handleMessage) } + if (this.pingService) { + await this.handle(PingService.getProtocolStr(this), this.pingService.handleMessage) + } + try { await this._onStarting() await this._onDidStart() @@ -433,9 +435,9 @@ class Libp2p extends EventEmitter { await this.natManager.stop() await this.transportManager.close() - this.unhandle(FetchService.PROTOCOL) + await this.unhandle(FetchService.PROTOCOL) + await this.unhandle(PingService.getProtocolStr(this)) - ping.unmount(this) this.dialer.destroy() } catch (/** @type {any} */ err) { if (err) { @@ -609,10 +611,10 @@ class Libp2p extends EventEmitter { // If received multiaddr, ping it if (multiaddrs) { - return ping(this, multiaddrs[0]) + return this.pingService.ping(multiaddrs[0]) } - return ping(this, id) + return this.pingService.ping(id) } /** diff --git a/src/ping/index.js b/src/ping/index.js index 75e13b9a53..46f87e3a6d 100644 --- a/src/ping/index.js +++ b/src/ping/index.js @@ -22,64 +22,63 @@ const { PROTOCOL_NAME, PING_LENGTH, PROTOCOL_VERSION } = require('./constants') * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream */ -/** - * Ping a given peer and wait for its response, getting the operation latency. - * - * @param {Libp2p} node - * @param {PeerId|Multiaddr} peer - * @returns {Promise} - */ -async function ping (node, peer) { - const protocol = `/${node._config.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}` - // @ts-ignore multiaddr might not have toB58String - log('dialing %s to %s', protocol, peer.toB58String ? peer.toB58String() : peer) +class PingService { + /** + * @param {import('../')} libp2p + */ + static getProtocolStr (libp2p) { + return `/${libp2p._config.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}` + } - const connection = await node.dial(peer) - const { stream } = await connection.newStream(protocol) + /** + * @param {Libp2p} libp2p + */ + constructor (libp2p) { + this._libp2p = libp2p + } - const start = Date.now() - const data = crypto.randomBytes(PING_LENGTH) + /** + * A handler to register with Libp2p to process ping messages + * + * @param {Object} options + * @param {MuxedStream} options.stream + */ + handleMessage ({ stream }) { + return pipe(stream, stream) + } - const [result] = await pipe( - [data], - stream, - (/** @type {MuxedStream} */ stream) => take(1, stream), - toBuffer, - collect - ) - const end = Date.now() + /** + * Ping a given peer and wait for its response, getting the operation latency. + * + * @param {PeerId|Multiaddr} peer + * @returns {Promise} + */ + async ping (peer) { + const protocol = `/${this._libp2p._config.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}` + // @ts-ignore multiaddr might not have toB58String + log('dialing %s to %s', protocol, peer.toB58String ? peer.toB58String() : peer) - if (!equals(data, result)) { - throw errCode(new Error('Received wrong ping ack'), codes.ERR_WRONG_PING_ACK) - } + const connection = await this._libp2p.dial(peer) + const { stream } = await connection.newStream(protocol) - return end - start -} + const start = Date.now() + const data = crypto.randomBytes(PING_LENGTH) -/** - * Subscribe ping protocol handler. - * - * @param {Libp2p} node - */ -function mount (node) { - node.handle(`/${node._config.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}`, ({ stream }) => pipe(stream, stream)) - .catch(err => { - log.error(err) - }) -} + const [result] = await pipe( + [data], + stream, + (/** @type {MuxedStream} */ stream) => take(1, stream), + toBuffer, + collect + ) + const end = Date.now() -/** - * Unsubscribe ping protocol handler. - * - * @param {Libp2p} node - */ -function unmount (node) { - node.unhandle(`/${node._config.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}`) - .catch(err => { - log.error(err) - }) + if (!equals(data, result)) { + throw errCode(new Error('Received wrong ping ack'), codes.ERR_WRONG_PING_ACK) + } + + return end - start + } } -exports = module.exports = ping -exports.mount = mount -exports.unmount = unmount +module.exports = PingService diff --git a/test/upgrading/upgrader.spec.js b/test/upgrading/upgrader.spec.js index 176c8c0a9f..6726b747d5 100644 --- a/test/upgrading/upgrader.spec.js +++ b/test/upgrading/upgrader.spec.js @@ -371,7 +371,7 @@ describe('libp2p.upgrader', () => { expect(libp2p.upgrader).to.equal(libp2p.transportManager.upgrader) }) - it('should be able to register and unregister a handler', () => { + it('should be able to register and unregister a handler', async () => { libp2p = new Libp2p({ peerId: peers[0], modules: { @@ -384,11 +384,11 @@ describe('libp2p.upgrader', () => { expect(libp2p.upgrader.protocols).to.not.have.any.keys(['/echo/1.0.0', '/echo/1.0.1']) const echoHandler = () => {} - libp2p.handle(['/echo/1.0.0', '/echo/1.0.1'], echoHandler) + await libp2p.handle(['/echo/1.0.0', '/echo/1.0.1'], echoHandler) expect(libp2p.upgrader.protocols.get('/echo/1.0.0')).to.equal(echoHandler) expect(libp2p.upgrader.protocols.get('/echo/1.0.1')).to.equal(echoHandler) - libp2p.unhandle(['/echo/1.0.0']) + await libp2p.unhandle(['/echo/1.0.0']) expect(libp2p.upgrader.protocols.get('/echo/1.0.0')).to.equal(undefined) expect(libp2p.upgrader.protocols.get('/echo/1.0.1')).to.equal(echoHandler) }) From fc12973344df43ec4c3d390b1eb8ea0da61857eb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 25 Jan 2022 20:09:03 +0000 Subject: [PATCH 343/447] chore: release 0.36.1 (#1145) ### [0.36.1](https://www.github.com/libp2p/js-libp2p/compare/v0.36.0...v0.36.1) (2022-01-25) ### Bug Fixes * await unhandle of protocols ([#1144](https://www.github.com/libp2p/js-libp2p/issues/1144)) ([d44bd90](https://www.github.com/libp2p/js-libp2p/commit/d44bd9094fe9545054eb8eff68f81bc52ece03e7)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64fe745e10..f5628f6d60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,13 @@ +### [0.36.1](https://www.github.com/libp2p/js-libp2p/compare/v0.36.0...v0.36.1) (2022-01-25) + + +### Bug Fixes + +* await unhandle of protocols ([#1144](https://www.github.com/libp2p/js-libp2p/issues/1144)) ([d44bd90](https://www.github.com/libp2p/js-libp2p/commit/d44bd9094fe9545054eb8eff68f81bc52ece03e7)) + ## [0.36.0](https://www.github.com/libp2p/js-libp2p/compare/v0.35.8...v0.36.0) (2022-01-25) diff --git a/package.json b/package.json index a8f8b967cb..78b8a568e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.36.0", + "version": "0.36.1", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From 902f10d58d1062e812eb27aa0e2256e3fde5d3f6 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 26 Jan 2022 10:52:23 +0000 Subject: [PATCH 344/447] fix: reject connections when not running (#1146) When the node is shutting down, new connections can still be received. If this happens we can end up writing into the datastore when it's been closed which throws an error. Instead, if we're not running, have the connection manager close new incoming connections. --- src/connection-manager/index.js | 11 +++++++++++ src/peer-store/address-book.js | 2 +- test/dialing/direct.node.js | 31 ++++++++++++++++++++++++++----- test/dialing/direct.spec.js | 14 +++++++++++++- test/dialing/resolver.spec.js | 3 +++ test/registrar/registrar.spec.js | 4 ++++ test/upgrading/upgrader.spec.js | 3 +++ 7 files changed, 61 insertions(+), 7 deletions(-) diff --git a/src/connection-manager/index.js b/src/connection-manager/index.js index fa2cb40225..eeae15597b 100644 --- a/src/connection-manager/index.js +++ b/src/connection-manager/index.js @@ -223,6 +223,12 @@ class ConnectionManager extends EventEmitter { * @param {Connection} connection */ async onConnect (connection) { + if (!this._started) { + // This can happen when we are in the process of shutting down the node + await connection.close() + return + } + const peerId = connection.remotePeer const peerIdStr = peerId.toB58String() const storedConn = this.connections.get(peerIdStr) @@ -251,6 +257,11 @@ class ConnectionManager extends EventEmitter { * @returns {void} */ onDisconnect (connection) { + if (!this._started) { + // This can happen when we are in the process of shutting down the node + return + } + const peerId = connection.remotePeer.toB58String() let storedConn = this.connections.get(peerId) diff --git a/src/peer-store/address-book.js b/src/peer-store/address-book.js index b54ce98136..391f960888 100644 --- a/src/peer-store/address-book.js +++ b/src/peer-store/address-book.js @@ -288,7 +288,7 @@ class PeerStoreAddressBook { updatedPeer = await this._store.mergeOrCreate(peerId, { addresses }) - log(`added multiaddrs for ${peerId}`) + log(`added multiaddrs for ${peerId.toB58String()}`) } finally { log('set release write lock') release() diff --git a/test/dialing/direct.node.js b/test/dialing/direct.node.js index 6842811a18..81d8205d2b 100644 --- a/test/dialing/direct.node.js +++ b/test/dialing/direct.node.js @@ -307,6 +307,8 @@ describe('Dialing (direct, TCP)', () => { } }) + await libp2p.start() + sinon.spy(libp2p.dialer, 'connectToPeer') try { @@ -329,6 +331,8 @@ describe('Dialing (direct, TCP)', () => { } }) + await libp2p.start() + sinon.spy(libp2p.dialer, 'connectToPeer') const connection = await libp2p.dial(remoteAddr) @@ -337,7 +341,7 @@ describe('Dialing (direct, TCP)', () => { expect(stream).to.exist() expect(protocol).to.equal('/echo/1.0.0') await connection.close() - expect(libp2p.dialer.connectToPeer.callCount).to.equal(1) + expect(libp2p.dialer.connectToPeer.callCount).to.be.greaterThan(0) }) it('should use the dialer for connecting to a peer', async () => { @@ -350,6 +354,8 @@ describe('Dialing (direct, TCP)', () => { } }) + await libp2p.start() + sinon.spy(libp2p.dialer, 'connectToPeer') await libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) @@ -359,7 +365,7 @@ describe('Dialing (direct, TCP)', () => { expect(stream).to.exist() expect(protocol).to.equal('/echo/1.0.0') await connection.close() - expect(libp2p.dialer.connectToPeer.callCount).to.equal(1) + expect(libp2p.dialer.connectToPeer.callCount).to.be.greaterThan(0) }) it('should close all streams when the connection closes', async () => { @@ -372,6 +378,8 @@ describe('Dialing (direct, TCP)', () => { } }) + await libp2p.start() + // register some stream handlers to simulate several protocols await libp2p.handle('/stream-count/1', ({ stream }) => pipe(stream, stream)) await libp2p.handle('/stream-count/2', ({ stream }) => pipe(stream, stream)) @@ -397,8 +405,8 @@ describe('Dialing (direct, TCP)', () => { // Verify stream count const remoteConn = remoteLibp2p.connectionManager.get(libp2p.peerId) - expect(connection.streams).to.have.length(6) - expect(remoteConn.streams).to.have.length(6) + 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() @@ -416,6 +424,8 @@ describe('Dialing (direct, TCP)', () => { } }) + await libp2p.start() + await expect(libp2p.dialProtocol(remotePeerId)) .to.eventually.be.rejectedWith(Error) .and.to.have.property('code', ErrorCodes.ERR_INVALID_PROTOCOLS_FOR_STREAM) @@ -435,6 +445,8 @@ describe('Dialing (direct, TCP)', () => { } }) + await libp2p.start() + const connection = await libp2p.dial(remoteAddr) expect(connection).to.exist() expect(connection.stat.timeline.close).to.not.exist() @@ -452,6 +464,8 @@ describe('Dialing (direct, TCP)', () => { } }) + await libp2p.start() + const connection = await libp2p.dial(`${remoteAddr.toString()}`) expect(connection).to.exist() expect(connection.stat.timeline.close).to.not.exist() @@ -474,6 +488,8 @@ describe('Dialing (direct, TCP)', () => { sinon.spy(libp2p.upgrader.protector, 'protect') sinon.stub(remoteLibp2p.upgrader, 'protector').value(new Protector(swarmKeyBuffer)) + await libp2p.start() + const connection = await libp2p.dialer.connectToPeer(remoteAddr) expect(connection).to.exist() const { stream, protocol } = await connection.newStream('/echo/1.0.0') @@ -492,8 +508,10 @@ describe('Dialing (direct, TCP)', () => { connEncryption: [Crypto] } }) - const dials = 10 + await libp2p.start() + + const dials = 10 const fullAddress = remoteAddr.encapsulate(`/p2p/${remoteLibp2p.peerId.toB58String()}`) await libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) @@ -522,6 +540,9 @@ describe('Dialing (direct, TCP)', () => { connEncryption: [Crypto] } }) + + await libp2p.start() + const dials = 10 const error = new Error('Boom') sinon.stub(libp2p.transportManager, 'dial').callsFake(() => Promise.reject(error)) diff --git a/test/dialing/direct.spec.js b/test/dialing/direct.spec.js index 40193a905f..aa0e45bbc4 100644 --- a/test/dialing/direct.spec.js +++ b/test/dialing/direct.spec.js @@ -459,14 +459,18 @@ describe('Dialing (direct, WebSockets)', () => { sinon.spy(libp2p.dialer, 'connectToPeer') sinon.spy(libp2p.peerStore.addressBook, 'add') + await libp2p.start() + const connection = await libp2p.dial(remoteAddr) expect(connection).to.exist() const { stream, protocol } = await connection.newStream('/echo/1.0.0') expect(stream).to.exist() expect(protocol).to.equal('/echo/1.0.0') await connection.close() - expect(libp2p.dialer.connectToPeer.callCount).to.equal(1) + expect(libp2p.dialer.connectToPeer.callCount).to.be.at.least(1) expect(libp2p.peerStore.addressBook.add.callCount).to.be.at.least(1) + + await libp2p.stop() }) it('should run identify automatically after connecting', async () => { @@ -489,6 +493,8 @@ describe('Dialing (direct, WebSockets)', () => { sinon.spy(libp2p.identifyService, 'identify') sinon.spy(libp2p.upgrader, 'onConnection') + await libp2p.start() + const connection = await libp2p.dial(remoteAddr) expect(connection).to.exist() @@ -501,6 +507,8 @@ describe('Dialing (direct, WebSockets)', () => { await libp2p.identifyService.identify.firstCall.returnValue expect(libp2p.peerStore.protoBook.set.callCount).to.equal(1) + + await libp2p.stop() }) it('should be able to use hangup to close connections', async () => { @@ -520,11 +528,15 @@ describe('Dialing (direct, WebSockets)', () => { } }) + await libp2p.start() + const connection = await libp2p.dial(remoteAddr) expect(connection).to.exist() expect(connection.stat.timeline.close).to.not.exist() await libp2p.hangUp(connection.remotePeer) expect(connection.stat.timeline.close).to.exist() + + await libp2p.stop() }) it('should be able to use hangup when no connection exists', async () => { diff --git a/test/dialing/resolver.spec.js b/test/dialing/resolver.spec.js index f5088ae982..ab36881fb8 100644 --- a/test/dialing/resolver.spec.js +++ b/test/dialing/resolver.spec.js @@ -51,6 +51,9 @@ describe('Dialing (resolvable addresses)', () => { started: true, populateAddressBooks: false }) + + await libp2p.start() + await remoteLibp2p.start() }) afterEach(async () => { diff --git a/test/registrar/registrar.spec.js b/test/registrar/registrar.spec.js index 83f87fd695..fa9be0c88c 100644 --- a/test/registrar/registrar.spec.js +++ b/test/registrar/registrar.spec.js @@ -123,6 +123,8 @@ describe('registrar', () => { } }) + await libp2p.start() + // Register protocol const identifier = await libp2p.registrar.register(topologyProps) const topology = libp2p.registrar.topologies.get(identifier) @@ -164,6 +166,8 @@ describe('registrar', () => { } }) + await libp2p.start() + // Register protocol const identifier = await libp2p.registrar.register(topologyProps) const topology = libp2p.registrar.topologies.get(identifier) diff --git a/test/upgrading/upgrader.spec.js b/test/upgrading/upgrader.spec.js index 6726b747d5..5caf63f25e 100644 --- a/test/upgrading/upgrader.spec.js +++ b/test/upgrading/upgrader.spec.js @@ -403,6 +403,7 @@ describe('libp2p.upgrader', () => { connEncryption: [Crypto] } }) + await libp2p.start() const echoHandler = () => {} libp2p.handle(['/echo/1.0.0'], echoHandler) @@ -448,6 +449,8 @@ describe('libp2p.upgrader', () => { connectionGater }) + await libp2p.start() + const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) // Spy on emit for easy verification From bad9e8c0ff58d60a78314077720c82ae331cc55b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 26 Jan 2022 11:03:09 +0000 Subject: [PATCH 345/447] chore: release 0.36.2 (#1147) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5628f6d60..b44b2e033c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,13 @@ +### [0.36.2](https://www.github.com/libp2p/js-libp2p/compare/v0.36.1...v0.36.2) (2022-01-26) + + +### Bug Fixes + +* reject connections when not running ([#1146](https://www.github.com/libp2p/js-libp2p/issues/1146)) ([902f10d](https://www.github.com/libp2p/js-libp2p/commit/902f10d58d1062e812eb27aa0e2256e3fde5d3f6)) + ### [0.36.1](https://www.github.com/libp2p/js-libp2p/compare/v0.36.0...v0.36.1) (2022-01-25) diff --git a/package.json b/package.json index 78b8a568e5..54fdb30acc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.36.1", + "version": "0.36.2", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From 6a3e37f2506e0ae07a0b154dc9e127e30d65d7c2 Mon Sep 17 00:00:00 2001 From: Aleksei Date: Tue, 1 Feb 2022 12:11:19 +0300 Subject: [PATCH 346/447] chore: update js-interfaces URL in examples/transports README (#1150) --- examples/transports/README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/transports/README.md b/examples/transports/README.md index e2f41e3ae4..03d46012e9 100644 --- a/examples/transports/README.md +++ b/examples/transports/README.md @@ -1,10 +1,10 @@ # [Transports](http://libp2p.io/implementations/#transports) -libp2p doesn't make assumptions for you, instead, it enables you as the developer of the application to pick the modules you need to run your application, which can vary depending on the runtime you are executing. A libp2p node can use one or more Transports to dial and listen for Connections. These transports are modules that offer a clean interface for dialing and listening, defined by the [interface-transport](https://github.com/libp2p/js-interfaces/tree/master/src/transport) specification. Some examples of possible transports are: TCP, UTP, WebRTC, QUIC, HTTP, Pigeon and so on. +libp2p doesn't make assumptions for you, instead, it enables you as the developer of the application to pick the modules you need to run your application, which can vary depending on the runtime you are executing. A libp2p node can use one or more Transports to dial and listen for Connections. These transports are modules that offer a clean interface for dialing and listening, defined by the [interface-transport] specification. Some examples of possible transports are: TCP, UTP, WebRTC, QUIC, HTTP, Pigeon and so on. -A more complete definition of what is a transport can be found on the [interface-transport](https://github.com/libp2p/js-interfaces/tree/master/src/transport) specification. A way to recognize a candidate transport is through the badge: +A more complete definition of what is a transport can be found on the [interface-transport] specification. A way to recognize a candidate transport is through the badge: -[![](https://raw.githubusercontent.com/diasdavid/interface-transport/master/img/badge.png)](https://raw.githubusercontent.com/diasdavid/interface-transport/master/img/badge.png) +![][interface-transport badge] ## 1. Creating a libp2p node with TCP @@ -288,10 +288,15 @@ As expected, we created 3 nodes: node 1 with TCP, node 2 with TCP+WebSockets and ## 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] 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 in 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][interface-transport api] it will be able to use it. If you decide to implement a transport yourself, please consider adding to the list so that others can use it as well. Hope this tutorial was useful. We are always looking to improve it, so contributions are welcome! + +[interface-transport]: https://github.com/libp2p/js-libp2p-interfaces/tree/master/packages/libp2p-interfaces/src/transport +[interface-transport badge]: https://raw.githubusercontent.com/libp2p/js-libp2p-interfaces/master/packages/libp2p-interfaces/src/transport/img/badge.png +[interface-transport implementations]: https://github.com/libp2p/js-libp2p-interfaces/tree/master/packages/libp2p-interfaces/src/transport#modules-that-implement-the-interface +[interface-transport api]: https://github.com/libp2p/js-libp2p-interfaces/tree/master/packages/libp2p-interfaces/src/transport#api From 3cfe4bbfac46dac3c7875f891909b17f39fa7c85 Mon Sep 17 00:00:00 2001 From: Rakesh Shrestha <38497578+aomini@users.noreply.github.com> Date: Mon, 28 Feb 2022 22:31:56 +0545 Subject: [PATCH 347/447] docs: update auto relay example code usage (#1163) Co-authored-by: aomini daiki --- examples/auto-relay/README.md | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/examples/auto-relay/README.md b/examples/auto-relay/README.md index 17ed555477..0f88b7580e 100644 --- a/examples/auto-relay/README.md +++ b/examples/auto-relay/README.md @@ -106,18 +106,15 @@ console.log(`Node started with id ${node.peerId.toB58String()}`) const conn = await node.dial(relayAddr) +console.log(`Connected to the HOP relay ${conn.remotePeer.toString()}`) + // Wait for connection and relay to be bind for the example purpose -await new Promise((resolve) => { - node.peerStore.on('change:multiaddrs', ({ peerId }) => { - // Updated self multiaddrs? - if (peerId.equals(node.peerId)) { - resolve() - } - }) +node.peerStore.on('change:multiaddrs', ({ peerId }) => { + // Updated self multiaddrs? + if (peerId.equals(node.peerId)) { + console.log(`Advertising with a relay address of ${node.multiaddrs[0].toString()}/p2p/${node.peerId.toB58String()}`) + } }) - -console.log(`Connected to the HOP relay ${conn.remotePeer.toString()}`) -console.log(`Advertising with a relay address of ${node.multiaddrs[0].toString()}/p2p/${node.peerId.toB58String()}`) ``` As you can see in the code, we need to provide the relay address, `relayAddr`, as a process argument. This node will dial the provided relay address and automatically bind to it. @@ -189,4 +186,4 @@ As you can see from the output, the remote address of the established connection Before moving into production, there are a few things that you should take into account. -A relay node should not advertise its private address in a real world scenario, as the node would not be reachable by others. You should provide an array of public addresses in the libp2p `addresses.announce` option. If you are using websockets, bear in mind that due to browser’s security policies you cannot establish unencrypted connection from secure context. The simplest solution is to setup SSL with nginx and proxy to the node and setup a domain name for the certificate. +A relay node should not advertise its private address in a real world scenario, as the node would not be reachable by others. You should provide an array of public addresses in the libp2p `addresses.announce` option. If you are using websockets, bear in mind that due to browser’s security policies you cannot establish unencrypted connection from secure context. The simplest solution is to setup SSL with nginx and proxy to the node and setup a domain name for the certificate. \ No newline at end of file From 199395de4d8139cc77d0b408626f37c9b8520d28 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Mon, 28 Mar 2022 14:30:27 +0100 Subject: [PATCH 348/447] feat: convert to typescript (#1172) Converts this module to typescript. - Ecosystem modules renamed from (e.g.) `libp2p-tcp` to `@libp2p/tcp` - Ecosystem module now have named exports - Configuration has been updated, now pass instances of modules instead of classes: - Some configuration keys have been renamed to make them more descriptive. `transport` -> `transports`, `connEncryption` -> `connectionEncryption`. In general where we pass multiple things, the key is now plural, e.g. `streamMuxer` -> `streamMuxers`, `contentRouting` -> `contentRouters`, etc. Where we are configuring a singleton the config key is singular, e.g. `connProtector` -> `connectionProtector` etc. - Properties of the `modules` config key have been moved to the root - Properties of the `config` config key have been moved to the root ```js // before import Libp2p from 'libp2p' import TCP from 'libp2p-tcp' await Libp2p.create({ modules: { transport: [ TCP ], } config: { transport: { [TCP.tag]: { foo: 'bar' } }, relay: { enabled: true, hop: { enabled: true, active: true } } } }) ``` ```js // after import { createLibp2p } from 'libp2p' import { TCP } from '@libp2p/tcp' await createLibp2p({ transports: [ new TCP({ foo: 'bar' }) ], relay: { enabled: true, hop: { enabled: true, active: true } } }) ``` - Use of `enabled` flag has been reduced - previously you could pass a module but disable it with config. Now if you don't want a feature, just don't pass an implementation. Eg: ```js // before await Libp2p.create({ modules: { transport: [ TCP ], pubsub: Gossipsub }, config: { pubsub: { enabled: false } } }) ``` ```js // after await createLibp2p({ transports: [ new TCP() ] }) ``` - `.multiaddrs` renamed to `.getMultiaddrs()` because it's not a property accessor, work is done by that method to calculate announce addresses, observed addresses, etc - `/p2p/${peerId}` is now appended to all addresses returned by `.getMultiaddrs()` so they can be used opaquely (every consumer has to append the peer ID to the address to actually use it otherwise). If you need low-level unadulterated addresses, call methods on the address manager. BREAKING CHANGE: types are no longer hand crafted, this module is now ESM only --- .aegir.cjs | 63 ++ .aegir.js | 70 -- .github/dependabot.yml | 2 +- .github/workflows/main.yml | 72 +- LICENSE | 24 +- LICENSE-APACHE | 5 + LICENSE-MIT | 19 + README.md | 23 +- doc/API.md | 50 +- doc/CONFIGURATION.md | 525 ++++++----- doc/GETTING_STARTED.md | 111 ++- doc/PEER_DISCOVERY.md | 2 +- doc/STREAMING_ITERABLES.md | 4 +- doc/migrations/v0.26-v0.27.md | 30 +- doc/migrations/v0.27-v0.28.md | 6 +- doc/migrations/v0.28-v0.29.md | 14 +- doc/migrations/v0.29-v0.30.md | 44 +- doc/migrations/v0.30-v0.31.md | 2 +- examples/auto-relay/README.md | 80 +- examples/auto-relay/dialer.js | 28 +- examples/auto-relay/listener.js | 46 +- examples/auto-relay/relay.js | 44 +- examples/auto-relay/test.js | 17 +- examples/chat/src/dialer.js | 21 +- examples/chat/src/libp2p.js | 35 +- examples/chat/src/listener.js | 19 +- ...{peer-id-dialer.json => peer-id-dialer.js} | 2 +- .../src/peer-id-listener.js} | 2 +- examples/chat/src/stream.js | 21 +- examples/chat/test.js | 15 +- examples/connection-encryption/1.js | 31 +- examples/connection-encryption/README.md | 22 +- examples/connection-encryption/test.js | 12 +- examples/delegated-routing/package.json | 23 +- .../delegated-routing/src/libp2p-bundle.js | 82 +- examples/discovery-mechanisms/1.js | 50 +- examples/discovery-mechanisms/2.js | 45 +- examples/discovery-mechanisms/3.js | 92 +- examples/discovery-mechanisms/README.md | 160 ++-- .../{bootstrapers.js => bootstrappers.js} | 6 +- examples/discovery-mechanisms/test-1.js | 11 +- examples/discovery-mechanisms/test-2.js | 32 +- examples/discovery-mechanisms/test-3.js | 32 +- examples/discovery-mechanisms/test.js | 12 +- examples/echo/src/dialer.js | 24 +- examples/echo/src/{id-d.json => id-d.js} | 2 +- .../src/id-l.js} | 2 +- examples/echo/src/libp2p.js | 36 +- examples/echo/src/listener.js | 19 +- examples/echo/test.js | 15 +- examples/libp2p-in-the-browser/.babelrc | 3 - examples/libp2p-in-the-browser/index.js | 74 +- examples/libp2p-in-the-browser/package.json | 25 +- examples/libp2p-in-the-browser/test.js | 29 +- examples/package.json | 8 +- examples/peer-and-content-routing/1.js | 37 +- examples/peer-and-content-routing/2.js | 43 +- examples/peer-and-content-routing/README.md | 30 +- examples/peer-and-content-routing/test-1.js | 11 +- examples/peer-and-content-routing/test-2.js | 11 +- examples/peer-and-content-routing/test.js | 10 +- examples/pnet/index.js | 16 +- examples/pnet/libp2p-node.js | 45 +- examples/pnet/test.js | 11 +- examples/pnet/utils.js | 4 +- examples/protocol-and-stream-muxing/1.js | 33 +- examples/protocol-and-stream-muxing/2.js | 35 +- examples/protocol-and-stream-muxing/3.js | 34 +- examples/protocol-and-stream-muxing/README.md | 22 +- examples/protocol-and-stream-muxing/test-1.js | 11 +- examples/protocol-and-stream-muxing/test-2.js | 11 +- examples/protocol-and-stream-muxing/test-3.js | 11 +- examples/protocol-and-stream-muxing/test.js | 12 +- examples/pubsub/1.js | 42 +- examples/pubsub/README.md | 28 +- examples/pubsub/message-filtering/1.js | 48 +- examples/pubsub/message-filtering/README.md | 22 +- examples/pubsub/message-filtering/test.js | 15 +- examples/pubsub/test-1.js | 15 +- examples/pubsub/test.js | 10 +- examples/test-all.js | 14 +- examples/test.js | 15 +- examples/transports/1.js | 25 +- examples/transports/2.js | 36 +- examples/transports/3.js | 48 +- examples/transports/4.js | 65 +- examples/transports/README.md | 54 +- examples/transports/test-1.js | 11 +- examples/transports/test-2.js | 11 +- examples/transports/test-3.js | 11 +- examples/transports/test-4.js | 11 +- examples/transports/test.js | 14 +- examples/utils.js | 18 +- examples/webrtc-direct/README.md | 9 +- examples/webrtc-direct/dialer.js | 50 +- examples/webrtc-direct/listener.js | 37 +- examples/webrtc-direct/package.json | 33 +- examples/webrtc-direct/test.js | 48 +- package.json | 340 ++++---- scripts/node-globals.js | 2 - src/address-manager/index.js | 96 --- src/address-manager/index.ts | 129 +++ src/circuit/README.md | 27 +- src/circuit/auto-relay.js | 302 ------- src/circuit/auto-relay.ts | 284 ++++++ src/circuit/circuit/hop.js | 205 ----- src/circuit/circuit/hop.ts | 211 +++++ src/circuit/circuit/stop.js | 81 -- src/circuit/circuit/stop.ts | 78 ++ src/circuit/circuit/stream-handler.js | 94 -- src/circuit/circuit/stream-handler.ts | 87 ++ src/circuit/circuit/{utils.js => utils.ts} | 34 +- src/circuit/constants.js | 12 - src/circuit/constants.ts | 31 + src/circuit/index.js | 102 --- src/circuit/index.ts | 120 +++ src/circuit/{listener.js => listener.ts} | 48 +- src/circuit/multicodec.js | 5 - src/circuit/multicodec.ts | 2 + src/circuit/{protocol => pb}/index.d.ts | 0 src/circuit/{protocol => pb}/index.js | 16 +- src/circuit/{protocol => pb}/index.proto | 0 src/circuit/transport.js | 229 ----- src/circuit/transport.ts | 216 +++++ src/circuit/utils.js | 17 - src/circuit/utils.ts | 12 + src/config.js | 114 --- src/config.ts | 101 +++ src/connection-manager/auto-dialler.js | 132 --- src/connection-manager/auto-dialler.ts | 154 ++++ src/connection-manager/index.js | 374 -------- src/connection-manager/index.ts | 422 +++++++++ src/connection-manager/latency-monitor.js | 264 ------ src/connection-manager/latency-monitor.ts | 319 +++++++ ...mitter.js => visibility-change-emitter.ts} | 86 +- src/constants.js | 18 - src/constants.ts | 31 + src/content-routing/index.js | 163 ---- src/content-routing/index.ts | 143 +++ src/content-routing/utils.js | 89 -- src/content-routing/utils.ts | 54 ++ src/dht/dht-content-routing.js | 44 - src/dht/dht-content-routing.ts | 43 + src/dht/dht-peer-routing.js | 51 -- src/dht/dht-peer-routing.ts | 35 + src/dialer/auto-dialer.ts | 40 + .../{dial-request.js => dial-request.ts} | 111 +-- src/dialer/index.js | 376 -------- src/dialer/index.ts | 374 ++++++++ src/errors.js | 66 -- src/errors.ts | 71 ++ src/fetch/README.md | 2 +- src/fetch/constants.js | 6 - src/fetch/constants.ts | 3 + src/fetch/{index.js => index.ts} | 149 ++-- src/fetch/{ => pb}/proto.d.ts | 0 src/fetch/{ => pb}/proto.js | 16 +- src/fetch/{ => pb}/proto.proto | 0 src/get-peer.js | 49 -- src/get-peer.ts | 57 ++ src/identify/consts.js | 15 - src/identify/consts.ts | 13 + src/identify/index.js | 384 --------- src/identify/index.ts | 445 ++++++++++ src/identify/{ => pb}/message.d.ts | 25 +- src/identify/{ => pb}/message.js | 125 ++- src/identify/{ => pb}/message.proto | 0 src/index.js | 813 ------------------ src/index.ts | 234 +++++ src/insecure/index.ts | 97 +++ src/insecure/{ => pb}/proto.d.ts | 8 +- src/insecure/{ => pb}/proto.js | 61 +- src/insecure/{ => pb}/proto.proto | 0 src/insecure/plaintext.js | 95 -- src/keychain/{cms.js => cms.ts} | 120 +-- src/keychain/index.js | 561 ------------ src/keychain/index.ts | 588 +++++++++++++ src/keychain/{util.js => util.ts} | 24 +- src/libp2p.ts | 501 +++++++++++ src/metrics/index.js | 290 ------- src/metrics/index.ts | 310 +++++++ src/metrics/moving-average.ts | 53 ++ src/metrics/old-peers.js | 16 - src/metrics/stats.js | 270 ------ src/metrics/stats.ts | 243 ++++++ src/metrics/tracked-map.js | 94 -- src/nat-manager.js | 197 ----- src/nat-manager.ts | 194 +++++ src/peer-record-updater.ts | 55 ++ src/peer-routing.js | 176 ---- src/peer-routing.ts | 185 ++++ src/peer-store/README.md | 145 ---- src/peer-store/address-book.js | 382 -------- src/peer-store/index.js | 121 --- src/peer-store/key-book.js | 141 --- src/peer-store/metadata-book.js | 250 ------ src/peer-store/pb/peer.d.ts | 222 ----- src/peer-store/pb/peer.js | 643 -------------- src/peer-store/pb/peer.proto | 31 - src/peer-store/proto-book.js | 237 ----- src/peer-store/store.js | 263 ------ src/peer-store/types.ts | 245 ------ src/ping/README.md | 2 +- src/ping/constants.js | 8 - src/ping/constants.ts | 5 + src/ping/index.js | 84 -- src/ping/index.ts | 83 ++ src/ping/util.js | 18 - src/pnet/README.md | 4 +- src/pnet/crypto.js | 84 -- src/pnet/crypto.ts | 67 ++ src/pnet/errors.js | 7 - src/pnet/errors.ts | 5 + src/pnet/index.js | 86 -- src/pnet/index.ts | 95 ++ src/pnet/key-generator.js | 33 - src/pnet/key-generator.ts | 28 + src/pubsub-adapter.js | 61 -- src/record/README.md | 130 --- src/record/envelope/envelope.d.ts | 77 -- src/record/envelope/envelope.js | 243 ------ src/record/envelope/envelope.proto | 19 - src/record/envelope/index.js | 183 ---- src/record/peer-record/consts.js | 14 - src/record/peer-record/index.js | 113 --- src/record/peer-record/peer-record.d.ts | 133 --- src/record/peer-record/peer-record.js | 367 -------- src/record/peer-record/peer-record.proto | 18 - src/record/utils.js | 25 - src/registrar.js | 127 --- src/registrar.ts | 205 +++++ src/transport-manager.js | 269 ------ src/transport-manager.ts | 279 ++++++ src/types.ts | 98 --- src/upgrader.js | 492 ----------- src/upgrader.ts | 499 +++++++++++ src/version.ts | 3 + test/addresses/address-manager.spec.js | 146 ---- test/addresses/address-manager.spec.ts | 188 ++++ .../{addresses.node.js => addresses.node.ts} | 93 +- test/addresses/utils.js | 16 - test/addresses/utils.ts | 10 + ...prefix.node.js => protocol-prefix.node.ts} | 26 +- test/configuration/pubsub.spec.js | 129 --- test/configuration/pubsub.spec.ts | 106 +++ test/configuration/utils.js | 52 -- test/configuration/utils.ts | 76 ++ test/connection-manager/auto-dialler.spec.js | 64 -- test/connection-manager/auto-dialler.spec.ts | 61 ++ .../{index.node.js => index.node.ts} | 395 +++++---- test/connection-manager/index.spec.js | 132 --- test/connection-manager/index.spec.ts | 143 +++ ...outing.node.js => content-routing.node.ts} | 293 ++++--- .../content-routing/dht/configuration.node.js | 94 -- .../content-routing/dht/configuration.node.ts | 27 + test/content-routing/dht/operation.node.js | 146 ---- test/content-routing/dht/operation.node.ts | 178 ++++ test/content-routing/dht/utils.js | 35 - test/content-routing/dht/utils.ts | 15 + test/content-routing/utils.js | 21 - test/content-routing/utils.ts | 11 + test/core/consume-peer-record.spec.js | 46 - test/core/consume-peer-record.spec.ts | 51 ++ test/core/encryption.spec.js | 54 -- test/core/encryption.spec.ts | 54 ++ .../{listening.node.js => listening.node.ts} | 34 +- test/core/ping.node.js | 84 -- test/core/ping.node.ts | 75 ++ test/dialing/dial-request.spec.js | 224 ----- test/dialing/dial-request.spec.ts | 218 +++++ test/dialing/direct.node.js | 572 ------------ test/dialing/direct.node.ts | 572 ++++++++++++ test/dialing/direct.spec.js | 637 -------------- test/dialing/direct.spec.ts | 589 +++++++++++++ test/dialing/resolver.spec.js | 180 ---- test/dialing/resolver.spec.ts | 226 +++++ test/fetch/{fetch.node.js => fetch.node.ts} | 99 ++- test/fixtures/{browser.js => browser.ts} | 5 +- test/fixtures/{peers.js => peers.ts} | 4 +- test/fixtures/{swarm.key.js => swarm.key.ts} | 3 +- test/identify/index.spec.js | 605 ------------- test/identify/index.spec.ts | 619 +++++++++++++ test/insecure/compliance.spec.js | 13 - test/insecure/compliance.spec.ts | 15 + test/insecure/plaintext.spec.js | 67 -- test/insecure/plaintext.spec.ts | 74 ++ test/interop.ts | 159 ++++ ...ms-interop.spec.js => cms-interop.spec.ts} | 16 +- .../{keychain.spec.js => keychain.spec.ts} | 374 +++----- .../{peerid.spec.js => peerid.spec.ts} | 53 +- test/metrics/index.node.js | 146 ---- test/metrics/index.node.ts | 187 ++++ test/metrics/index.spec.js | 275 ------ test/metrics/index.spec.ts | 301 +++++++ test/nat-manager/nat-manager.node.js | 295 ------- test/nat-manager/nat-manager.node.ts | 231 +++++ test/peer-discovery/index.node.js | 206 ----- test/peer-discovery/index.node.ts | 217 +++++ test/peer-discovery/index.spec.js | 137 --- test/peer-discovery/index.spec.ts | 101 +++ test/peer-routing/peer-routing.node.js | 665 -------------- test/peer-routing/peer-routing.node.ts | 788 +++++++++++++++++ test/peer-routing/utils.js | 21 - test/peer-routing/utils.ts | 11 + test/peer-store/address-book.spec.js | 745 ---------------- test/peer-store/key-book.spec.js | 114 --- test/peer-store/metadata-book.spec.js | 384 --------- test/peer-store/peer-store.node.js | 50 -- test/peer-store/peer-store.spec.js | 227 ----- test/peer-store/proto-book.spec.js | 416 --------- test/pnet/index.spec.js | 92 -- test/pnet/index.spec.ts | 115 +++ test/record/envelope.spec.js | 87 -- test/record/peer-record.spec.js | 157 ---- test/registrar/registrar.spec.js | 198 ----- test/registrar/registrar.spec.ts | 228 +++++ ...{auto-relay.node.js => auto-relay.node.ts} | 379 ++++---- test/relay/relay.node.js | 165 ---- test/relay/relay.node.ts | 173 ++++ test/relay/utils.ts | 34 + test/transports/transport-manager.node.js | 106 --- test/transports/transport-manager.node.ts | 123 +++ test/transports/transport-manager.spec.js | 247 ------ test/transports/transport-manager.spec.ts | 158 ++++ test/ts-use/package.json | 25 - test/ts-use/src/main.ts | 195 ----- test/ts-use/tsconfig.json | 7 - test/upgrading/upgrader.spec.js | 477 ---------- test/upgrading/upgrader.spec.ts | 517 +++++++++++ test/utils/base-options.browser.js | 29 - test/utils/base-options.browser.ts | 31 + test/utils/base-options.js | 24 - test/utils/base-options.ts | 30 + test/utils/creators/peer.js | 72 -- test/utils/creators/peer.ts | 104 +++ test/utils/mock-connection-gater.js | 19 - test/utils/mockConnection.js | 155 ---- test/utils/mockCrypto.js | 24 - test/utils/mockMultiaddrConn.js | 44 - test/utils/mockUpgrader.js | 6 - tsconfig.json | 21 +- 341 files changed, 17059 insertions(+), 23572 deletions(-) create mode 100644 .aegir.cjs delete mode 100644 .aegir.js create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT rename examples/chat/src/{peer-id-dialer.json => peer-id-dialer.js} (99%) rename examples/{echo/src/id-l.json => chat/src/peer-id-listener.js} (99%) rename examples/discovery-mechanisms/{bootstrapers.js => bootstrappers.js} (90%) rename examples/echo/src/{id-d.json => id-d.js} (99%) rename examples/{chat/src/peer-id-listener.json => echo/src/id-l.js} (99%) delete mode 100644 examples/libp2p-in-the-browser/.babelrc delete mode 100644 scripts/node-globals.js delete mode 100644 src/address-manager/index.js create mode 100644 src/address-manager/index.ts delete mode 100644 src/circuit/auto-relay.js create mode 100644 src/circuit/auto-relay.ts delete mode 100644 src/circuit/circuit/hop.js create mode 100644 src/circuit/circuit/hop.ts delete mode 100644 src/circuit/circuit/stop.js create mode 100644 src/circuit/circuit/stop.ts delete mode 100644 src/circuit/circuit/stream-handler.js create mode 100644 src/circuit/circuit/stream-handler.ts rename src/circuit/circuit/{utils.js => utils.ts} (50%) delete mode 100644 src/circuit/constants.js create mode 100644 src/circuit/constants.ts delete mode 100644 src/circuit/index.js create mode 100644 src/circuit/index.ts rename src/circuit/{listener.js => listener.ts} (54%) delete mode 100644 src/circuit/multicodec.js create mode 100644 src/circuit/multicodec.ts rename src/circuit/{protocol => pb}/index.d.ts (100%) rename src/circuit/{protocol => pb}/index.js (97%) rename src/circuit/{protocol => pb}/index.proto (100%) delete mode 100644 src/circuit/transport.js create mode 100644 src/circuit/transport.ts delete mode 100644 src/circuit/utils.js create mode 100644 src/circuit/utils.ts delete mode 100644 src/config.js create mode 100644 src/config.ts delete mode 100644 src/connection-manager/auto-dialler.js create mode 100644 src/connection-manager/auto-dialler.ts delete mode 100644 src/connection-manager/index.js create mode 100644 src/connection-manager/index.ts delete mode 100644 src/connection-manager/latency-monitor.js create mode 100644 src/connection-manager/latency-monitor.ts rename src/connection-manager/{visibility-change-emitter.js => visibility-change-emitter.ts} (53%) delete mode 100644 src/constants.js create mode 100644 src/constants.ts delete mode 100644 src/content-routing/index.js create mode 100644 src/content-routing/index.ts delete mode 100644 src/content-routing/utils.js create mode 100644 src/content-routing/utils.ts delete mode 100644 src/dht/dht-content-routing.js create mode 100644 src/dht/dht-content-routing.ts delete mode 100644 src/dht/dht-peer-routing.js create mode 100644 src/dht/dht-peer-routing.ts create mode 100644 src/dialer/auto-dialer.ts rename src/dialer/{dial-request.js => dial-request.ts} (51%) delete mode 100644 src/dialer/index.js create mode 100644 src/dialer/index.ts delete mode 100644 src/errors.js create mode 100644 src/errors.ts delete mode 100644 src/fetch/constants.js create mode 100644 src/fetch/constants.ts rename src/fetch/{index.js => index.ts} (52%) rename src/fetch/{ => pb}/proto.d.ts (100%) rename src/fetch/{ => pb}/proto.js (95%) rename src/fetch/{ => pb}/proto.proto (100%) delete mode 100644 src/get-peer.js create mode 100644 src/get-peer.ts delete mode 100644 src/identify/consts.js create mode 100644 src/identify/consts.ts delete mode 100644 src/identify/index.js create mode 100644 src/identify/index.ts rename src/identify/{ => pb}/message.d.ts (80%) rename src/identify/{ => pb}/message.js (77%) rename src/identify/{ => pb}/message.proto (100%) delete mode 100644 src/index.js create mode 100644 src/index.ts create mode 100644 src/insecure/index.ts rename src/insecure/{ => pb}/proto.d.ts (96%) rename src/insecure/{ => pb}/proto.js (88%) rename src/insecure/{ => pb}/proto.proto (100%) delete mode 100644 src/insecure/plaintext.js rename src/keychain/{cms.js => cms.ts} (50%) delete mode 100644 src/keychain/index.js create mode 100644 src/keychain/index.ts rename src/keychain/{util.js => util.ts} (78%) create mode 100644 src/libp2p.ts delete mode 100644 src/metrics/index.js create mode 100644 src/metrics/index.ts create mode 100644 src/metrics/moving-average.ts delete mode 100644 src/metrics/old-peers.js delete mode 100644 src/metrics/stats.js create mode 100644 src/metrics/stats.ts delete mode 100644 src/metrics/tracked-map.js delete mode 100644 src/nat-manager.js create mode 100644 src/nat-manager.ts create mode 100644 src/peer-record-updater.ts delete mode 100644 src/peer-routing.js create mode 100644 src/peer-routing.ts delete mode 100644 src/peer-store/README.md delete mode 100644 src/peer-store/address-book.js delete mode 100644 src/peer-store/index.js delete mode 100644 src/peer-store/key-book.js delete mode 100644 src/peer-store/metadata-book.js delete mode 100644 src/peer-store/pb/peer.d.ts delete mode 100644 src/peer-store/pb/peer.js delete mode 100644 src/peer-store/pb/peer.proto delete mode 100644 src/peer-store/proto-book.js delete mode 100644 src/peer-store/store.js delete mode 100644 src/peer-store/types.ts delete mode 100644 src/ping/constants.js create mode 100644 src/ping/constants.ts delete mode 100644 src/ping/index.js create mode 100644 src/ping/index.ts delete mode 100644 src/ping/util.js delete mode 100644 src/pnet/crypto.js create mode 100644 src/pnet/crypto.ts delete mode 100644 src/pnet/errors.js create mode 100644 src/pnet/errors.ts delete mode 100644 src/pnet/index.js create mode 100644 src/pnet/index.ts delete mode 100644 src/pnet/key-generator.js create mode 100644 src/pnet/key-generator.ts delete mode 100644 src/pubsub-adapter.js delete mode 100644 src/record/README.md delete mode 100644 src/record/envelope/envelope.d.ts delete mode 100644 src/record/envelope/envelope.js delete mode 100644 src/record/envelope/envelope.proto delete mode 100644 src/record/envelope/index.js delete mode 100644 src/record/peer-record/consts.js delete mode 100644 src/record/peer-record/index.js delete mode 100644 src/record/peer-record/peer-record.d.ts delete mode 100644 src/record/peer-record/peer-record.js delete mode 100644 src/record/peer-record/peer-record.proto delete mode 100644 src/record/utils.js delete mode 100644 src/registrar.js create mode 100644 src/registrar.ts delete mode 100644 src/transport-manager.js create mode 100644 src/transport-manager.ts delete mode 100644 src/types.ts delete mode 100644 src/upgrader.js create mode 100644 src/upgrader.ts create mode 100644 src/version.ts delete mode 100644 test/addresses/address-manager.spec.js create mode 100644 test/addresses/address-manager.spec.ts rename test/addresses/{addresses.node.js => addresses.node.ts} (50%) delete mode 100644 test/addresses/utils.js create mode 100644 test/addresses/utils.ts rename test/configuration/{protocol-prefix.node.js => protocol-prefix.node.ts} (61%) delete mode 100644 test/configuration/pubsub.spec.js create mode 100644 test/configuration/pubsub.spec.ts delete mode 100644 test/configuration/utils.js create mode 100644 test/configuration/utils.ts delete mode 100644 test/connection-manager/auto-dialler.spec.js create mode 100644 test/connection-manager/auto-dialler.spec.ts rename test/connection-manager/{index.node.js => index.node.ts} (53%) delete mode 100644 test/connection-manager/index.spec.js create mode 100644 test/connection-manager/index.spec.ts rename test/content-routing/{content-routing.node.js => content-routing.node.ts} (56%) delete mode 100644 test/content-routing/dht/configuration.node.js create mode 100644 test/content-routing/dht/configuration.node.ts delete mode 100644 test/content-routing/dht/operation.node.js create mode 100644 test/content-routing/dht/operation.node.ts delete mode 100644 test/content-routing/dht/utils.js create mode 100644 test/content-routing/dht/utils.ts delete mode 100644 test/content-routing/utils.js create mode 100644 test/content-routing/utils.ts delete mode 100644 test/core/consume-peer-record.spec.js create mode 100644 test/core/consume-peer-record.spec.ts delete mode 100644 test/core/encryption.spec.js create mode 100644 test/core/encryption.spec.ts rename test/core/{listening.node.js => listening.node.ts} (57%) delete mode 100644 test/core/ping.node.js create mode 100644 test/core/ping.node.ts delete mode 100644 test/dialing/dial-request.spec.js create mode 100644 test/dialing/dial-request.spec.ts delete mode 100644 test/dialing/direct.node.js create mode 100644 test/dialing/direct.node.ts delete mode 100644 test/dialing/direct.spec.js create mode 100644 test/dialing/direct.spec.ts delete mode 100644 test/dialing/resolver.spec.js create mode 100644 test/dialing/resolver.spec.ts rename test/fetch/{fetch.node.js => fetch.node.ts} (72%) rename test/fixtures/{browser.js => browser.ts} (52%) rename test/fixtures/{peers.js => peers.ts} (98%) rename test/fixtures/{swarm.key.js => swarm.key.ts} (60%) delete mode 100644 test/identify/index.spec.js create mode 100644 test/identify/index.spec.ts delete mode 100644 test/insecure/compliance.spec.js create mode 100644 test/insecure/compliance.spec.ts delete mode 100644 test/insecure/plaintext.spec.js create mode 100644 test/insecure/plaintext.spec.ts create mode 100644 test/interop.ts rename test/keychain/{cms-interop.spec.js => cms-interop.spec.ts} (84%) rename test/keychain/{keychain.spec.js => keychain.spec.ts} (52%) rename test/keychain/{peerid.spec.js => peerid.spec.ts} (71%) delete mode 100644 test/metrics/index.node.js create mode 100644 test/metrics/index.node.ts delete mode 100644 test/metrics/index.spec.js create mode 100644 test/metrics/index.spec.ts delete mode 100644 test/nat-manager/nat-manager.node.js create mode 100644 test/nat-manager/nat-manager.node.ts delete mode 100644 test/peer-discovery/index.node.js create mode 100644 test/peer-discovery/index.node.ts delete mode 100644 test/peer-discovery/index.spec.js create mode 100644 test/peer-discovery/index.spec.ts delete mode 100644 test/peer-routing/peer-routing.node.js create mode 100644 test/peer-routing/peer-routing.node.ts delete mode 100644 test/peer-routing/utils.js create mode 100644 test/peer-routing/utils.ts delete mode 100644 test/peer-store/address-book.spec.js delete mode 100644 test/peer-store/key-book.spec.js delete mode 100644 test/peer-store/metadata-book.spec.js delete mode 100644 test/peer-store/peer-store.node.js delete mode 100644 test/peer-store/peer-store.spec.js delete mode 100644 test/peer-store/proto-book.spec.js delete mode 100644 test/pnet/index.spec.js create mode 100644 test/pnet/index.spec.ts delete mode 100644 test/record/envelope.spec.js delete mode 100644 test/record/peer-record.spec.js delete mode 100644 test/registrar/registrar.spec.js create mode 100644 test/registrar/registrar.spec.ts rename test/relay/{auto-relay.node.js => auto-relay.node.ts} (56%) delete mode 100644 test/relay/relay.node.js create mode 100644 test/relay/relay.node.ts create mode 100644 test/relay/utils.ts delete mode 100644 test/transports/transport-manager.node.js create mode 100644 test/transports/transport-manager.node.ts delete mode 100644 test/transports/transport-manager.spec.js create mode 100644 test/transports/transport-manager.spec.ts delete mode 100644 test/ts-use/package.json delete mode 100644 test/ts-use/src/main.ts delete mode 100644 test/ts-use/tsconfig.json delete mode 100644 test/upgrading/upgrader.spec.js create mode 100644 test/upgrading/upgrader.spec.ts delete mode 100644 test/utils/base-options.browser.js create mode 100644 test/utils/base-options.browser.ts delete mode 100644 test/utils/base-options.js create mode 100644 test/utils/base-options.ts delete mode 100644 test/utils/creators/peer.js create mode 100644 test/utils/creators/peer.ts delete mode 100644 test/utils/mock-connection-gater.js delete mode 100644 test/utils/mockConnection.js delete mode 100644 test/utils/mockCrypto.js delete mode 100644 test/utils/mockMultiaddrConn.js delete mode 100644 test/utils/mockUpgrader.js diff --git a/.aegir.cjs b/.aegir.cjs new file mode 100644 index 0000000000..48d036975f --- /dev/null +++ b/.aegir.cjs @@ -0,0 +1,63 @@ +'use strict' + +/** @type {import('aegir').PartialOptions} */ +module.exports = { + build: { + bundlesizeMax: '253kB' + }, + test: { + before: async () => { + const { createLibp2p } = await import('./dist/src/index.js') + const { MULTIADDRS_WEBSOCKETS } = await import('./dist/test/fixtures/browser.js') + const { default: Peers } = await import('./dist/test/fixtures/peers.js') + const { WebSockets } = await import('@libp2p/websockets') + const { Mplex } = await import('@libp2p/mplex') + const { NOISE } = await import('@chainsafe/libp2p-noise') + const { Plaintext } = await import('./dist/src/insecure/index.js') + const { pipe } = await import('it-pipe') + const { createFromJSON } = await import('@libp2p/peer-id-factory') + + // Use the last peer + const peerId = await createFromJSON(Peers[Peers.length - 1]) + const libp2p = await createLibp2p({ + addresses: { + listen: [MULTIADDRS_WEBSOCKETS[0]] + }, + peerId, + transports: [ + new WebSockets() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE, + new Plaintext() + ], + relay: { + enabled: true, + hop: { + enabled: true, + active: false + } + }, + nat: { + enabled: false + } + }) + // Add the echo protocol + await libp2p.handle('/echo/1.0.0', ({ stream }) => { + pipe(stream, stream) + .catch() // sometimes connections are closed before multistream-select finishes which causes an error + }) + await libp2p.start() + + return { + libp2p + } + }, + after: async (_, before) => { + await before.libp2p.stop() + } + } +} diff --git a/.aegir.js b/.aegir.js deleted file mode 100644 index 92fe923d7c..0000000000 --- a/.aegir.js +++ /dev/null @@ -1,70 +0,0 @@ -'use strict' - -const path = require('path') -const Libp2p = require('./src') -const { MULTIADDRS_WEBSOCKETS } = require('./test/fixtures/browser') -const Peers = require('./test/fixtures/peers') -const PeerId = require('peer-id') -const WebSockets = require('libp2p-websockets') -const Muxer = require('libp2p-mplex') -const { NOISE: Crypto } = require('@chainsafe/libp2p-noise') -const pipe = require('it-pipe') -let libp2p - -const before = async () => { - // Use the last peer - const peerId = await PeerId.createFromJSON(Peers[Peers.length - 1]) - - libp2p = new Libp2p({ - addresses: { - listen: [MULTIADDRS_WEBSOCKETS[0]] - }, - peerId, - modules: { - transport: [WebSockets], - streamMuxer: [Muxer], - connEncryption: [Crypto] - }, - config: { - relay: { - enabled: true, - hop: { - enabled: true, - active: false - } - }, - nat: { - enabled: false - } - } - }) - // Add the echo protocol - libp2p.handle('/echo/1.0.0', ({ stream }) => pipe(stream, stream)) - - await libp2p.start() -} - -const after = async () => { - await libp2p.stop() -} - -/** @type {import('aegir').Options["build"]["config"]} */ -const esbuild = { - inject: [path.join(__dirname, './scripts/node-globals.js')] -} - -/** @type {import('aegir').PartialOptions} */ -module.exports = { - build: { - bundlesizeMax: '253kB' - }, - test: { - before, - after, - browser: { - config: { - buildConfig: esbuild - } - } - } -} diff --git a/.github/dependabot.yml b/.github/dependabot.yml index de46e32616..290ad02837 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,5 +4,5 @@ updates: directory: "/" schedule: interval: daily - time: "11:00" + time: "10:00" open-pull-requests-limit: 10 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4054a7b061..1e08cff4d4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,21 +8,8 @@ on: - '**' jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest] - node: [16] - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: ipfs/aegir/actions/cache-node-modules@master check: - needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -30,15 +17,11 @@ jobs: with: node-version: lts/* - uses: ipfs/aegir/actions/cache-node-modules@master - - run: npx aegir lint - - run: npx aegir dep-check - - uses: ipfs/aegir/actions/bundle-size@master - name: size - with: - github_token: ${{ secrets.GITHUB_TOKEN }} + - run: npm run --if-present lint + - run: npm run --if-present dep-check test-node: - needs: build + needs: check runs-on: ${{ matrix.os }} strategy: matrix: @@ -51,14 +34,14 @@ jobs: with: node-version: ${{ matrix.node }} - uses: ipfs/aegir/actions/cache-node-modules@master - - run: npm run test:node -- --cov --bail + - run: npm run --if-present test:node - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 with: directory: ./.nyc_output flags: node test-chrome: - needs: build + needs: check runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -66,14 +49,14 @@ jobs: with: node-version: lts/* - uses: ipfs/aegir/actions/cache-node-modules@master - - run: npm run test:browser -- -t browser --cov --bail + - run: npm run --if-present test:chrome - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 with: directory: ./.nyc_output flags: chrome test-chrome-webworker: - needs: build + needs: check runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -81,14 +64,14 @@ jobs: with: node-version: lts/* - uses: ipfs/aegir/actions/cache-node-modules@master - - run: npm run test:browser -- -t webworker --bail + - run: npm run --if-present test:chrome-webworker - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 with: directory: ./.nyc_output flags: chrome-webworker test-firefox: - needs: build + needs: check runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -96,14 +79,14 @@ jobs: with: node-version: lts/* - uses: ipfs/aegir/actions/cache-node-modules@master - - run: npm run test:browser -- -t browser --bail -- --browser firefox + - run: npm run --if-present test:firefox - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 with: directory: ./.nyc_output flags: firefox test-firefox-webworker: - needs: build + needs: check runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -111,14 +94,29 @@ jobs: with: node-version: lts/* - uses: ipfs/aegir/actions/cache-node-modules@master - - run: npm run test:browser -- -t webworker --bail -- --browser firefox + - run: npm run --if-present test:firefox-webworker - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 with: directory: ./.nyc_output flags: firefox-webworker - test-ts: - needs: build + test-electron-main: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npx xvfb-maybe npm run --if-present test:electron-main + - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 + with: + directory: ./.nyc_output + flags: electron-main + + test-electron-renderer: + needs: check runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -126,10 +124,14 @@ jobs: with: node-version: lts/* - uses: ipfs/aegir/actions/cache-node-modules@master - - run: npm run test:ts + - run: npx xvfb-maybe npm run --if-present test:electron-renderer + - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 + with: + directory: ./.nyc_output + flags: electron-renderer test-interop: - needs: build + needs: check runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -137,11 +139,11 @@ jobs: with: node-version: lts/* - uses: ipfs/aegir/actions/cache-node-modules@master - - run: npm run test:interop -- --bail -- --exit + - run: npm run test:interop -- --bail release: runs-on: ubuntu-latest - needs: [test-node, test-chrome, test-chrome-webworker, test-firefox, test-firefox-webworker, test-ts, test-interop] + needs: [test-node, test-chrome, test-chrome-webworker, test-firefox, test-firefox-webworker, test-electron-main, test-electron-renderer, test-interop] if: github.event_name == 'push' && github.ref == 'refs/heads/master' steps: - uses: GoogleCloudPlatform/release-please-action@v2 diff --git a/LICENSE b/LICENSE index 59a33bab48..20ce483c86 100644 --- a/LICENSE +++ b/LICENSE @@ -1,22 +1,4 @@ -The MIT License (MIT) - -Copyright (c) 2015 David Dias - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +This project is dual licensed under MIT and Apache-2.0. +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index 90f3ac9f24..028a459406 100644 --- a/README.md +++ b/README.md @@ -39,26 +39,22 @@ If you are looking for the documentation of the latest release, you can view the **Want to update libp2p in your project?** Check our [migrations folder](./doc/migrations). -[**`Weekly Core Dev Calls`**](https://github.com/libp2p/team-mgmt/issues/16) - -## Lead Maintainer - -[Jacob Heun](https://github.com/jacobheun/) - -## Table of Contents +## Table of Contents - [Background](#background) - [Install](#install) - [Usage](#usage) - [Configuration](#configuration) - [API](#api) - - [Getting Started](#getting-started) + - [Getting started](#getting-started) - [Tutorials and Examples](#tutorials-and-examples) - [Development](#development) - [Tests](#tests) + - [Run unit tests](#run-unit-tests) - [Packages](#packages) - [Contribute](#contribute) - [License](#license) + - [Contribution](#contribution) ## Background @@ -123,7 +119,7 @@ You can find multiple examples on the [examples folder](./examples) that will gu > npm run test:node # run just Browser tests (Chrome) -> npm run test:browser +> npm run test:chrome ``` ### Packages @@ -183,4 +179,11 @@ The libp2p implementation in JavaScript is a work in progress. As such, there ar ## License -[MIT](LICENSE) © Protocol Labs +Licensed under either of + + * Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / http://www.apache.org/licenses/LICENSE-2.0) + * MIT ([LICENSE-MIT](LICENSE-MIT) / http://opensource.org/licenses/MIT) + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/doc/API.md b/doc/API.md index fd2cd82452..908fff2314 100644 --- a/doc/API.md +++ b/doc/API.md @@ -119,23 +119,21 @@ For Libp2p configurations and modules details read the [Configuration Document]( #### Example ```js -const Libp2p = require('libp2p') -const TCP = require('libp2p-tcp') -const MPLEX = require('libp2p-mplex') -const { NOISE } = require('libp2p-noise') +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' async function main () { // specify options const options = { - modules: { - transport: [TCP], - streamMuxer: [MPLEX], - connEncryption: [NOISE] - } + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()] } // create libp2p - const libp2p = await Libp2p.create(options) + const libp2p = await createLibp2p(options) } main() @@ -149,11 +147,11 @@ As an alternative, it is possible to create a Libp2p instance with the construct #### Example ```js -const Libp2p = require('libp2p') -const TCP = require('libp2p-tcp') -const MPLEX = require('libp2p-mplex') -const { NOISE } = require('libp2p-noise') -const PeerId = require('peer-id') +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' + async function main () { const peerId = await PeerId.create(); @@ -162,11 +160,9 @@ async function main () { // peerId is required when Libp2p is instantiated via the constructor const options = { peerId, - modules: { - transport: [TCP], - streamMuxer: [MPLEX], - connEncryption: [NOISE] - } + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()] } // create libp2p @@ -200,11 +196,11 @@ Load keychain keys from the datastore, importing the private key as 'self', if n #### Example ```js -const Libp2p = require('libp2p') +import { createLibp2p } from 'libp2p' // ... -const libp2p = await Libp2p.create({ +const libp2p = await createLibp2p({ // ... keychain: { pass: '0123456789pass1234567890' @@ -230,11 +226,11 @@ Starts the libp2p node. #### Example ```js -const Libp2p = require('libp2p') +import { createLibp2p } from 'libp2p' // ... -const libp2p = await Libp2p.create(options) +const libp2p = await createLibp2p(options) // start libp2p await libp2p.start() @@ -255,10 +251,10 @@ Stops the libp2p node. #### Example ```js -const Libp2p = require('libp2p') +import { createLibp2p } from 'libp2p' // ... -const libp2p = await Libp2p.create(options) +const libp2p = await createLibp2p(options) // ... // stop libp2p @@ -354,7 +350,7 @@ Dials to another peer in the network and selects a protocol to communicate with ```js // ... -const pipe = require('it-pipe') +import { pipe } from 'it-pipe' const { stream, protocol } = await libp2p.dialProtocol(remotePeerId, protocols) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 0276c014eb..41cd0e7aba 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -199,9 +199,9 @@ When [creating a libp2p node](./API.md#create), the modules needed should be spe ```js const modules = { - transport: [], - streamMuxer: [], - connEncryption: [], + transports: [], + streamMuxers: [], + connectionEncryption: [], contentRouting: [], peerRouting: [], peerDiscovery: [], @@ -235,67 +235,59 @@ Besides the `modules` and `config`, libp2p allows other internal options and con // dht: kad-dht // pubsub: gossipsub -const Libp2p = require('libp2p') -const TCP = require('libp2p-tcp') -const WS = require('libp2p-websockets') -const MPLEX = require('libp2p-mplex') -const { NOISE } = require('libp2p-noise') -const MulticastDNS = require('libp2p-mdns') -const DHT = require('libp2p-kad-dht') -const GossipSub = require('libp2p-gossipsub') - -const node = await Libp2p.create({ - modules: { - transport: [ - TCP, - new WS() // It can take instances too! - ], - streamMuxer: [MPLEX], - connEncryption: [NOISE], - peerDiscovery: [MulticastDNS], - dht: DHT, - pubsub: GossipSub - } +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { WebSockets } from '@libp2p/websockets' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { MulticastDNS } from '@libp2p/mdns' +import { KadDHT } from '@libp2p/kad-dht' +import { GossipSub } from 'libp2p-gossipsub' + +const node = await createLibp2p({ + transports: [ + TCP, + new WS() // It can take instances too! + ], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()], + peerDiscovery: [MulticastDNS], + dht: DHT, + pubsub: GossipSub }) ``` #### Customizing Peer Discovery ```js -const Libp2p = require('libp2p') -const TCP = require('libp2p-tcp') -const MPLEX = require('libp2p-mplex') -const { NOISE } = require('libp2p-noise') -const MulticastDNS = require('libp2p-mdns') -const Bootstrap = require('libp2p-bootstrap') - -const node = await Libp2p.create({ - modules: { - transport: [TCP], - streamMuxer: [MPLEX], - connEncryption: [NOISE], - peerDiscovery: [MulticastDNS, Bootstrap] - }, - config: { - peerDiscovery: { - 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]: { - interval: 1000, - enabled: true - }, - [Bootstrap.tag]: { - list: [ // A list of bootstrap peers to connect to starting up the node - "/ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", - "/dnsaddr/bootstrap.libp2p.io/ipfs/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", - "/dnsaddr/bootstrap.libp2p.io/ipfs/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa", - ], - interval: 2000, - enabled: true - } - // .. other discovery module options. - } +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { MulticastDNS } from '@libp2p/mdns' +import { Bootstrap } from '@libp2p/bootstrap' + +const node = await createLibp2p({ + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()], + peerDiscovery: [ + new MulticastDNS({ + interval: 1000 + }), + new Bootstrap( + list: [ // A list of bootstrap peers to connect to starting up the node + "/ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", + "/dnsaddr/bootstrap.libp2p.io/ipfs/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + "/dnsaddr/bootstrap.libp2p.io/ipfs/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa", + ], + interval: 2000 + ) + ], + connectionManager: { + 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. } }) ``` @@ -303,56 +295,50 @@ const node = await Libp2p.create({ #### Setup webrtc transport and discovery ```js - -const Libp2p = require('libp2p') -const WS = require('libp2p-websockets') -const WebRTCStar = require('libp2p-webrtc-star') -const MPLEX = require('libp2p-mplex') -const { NOISE } = require('libp2p-noise') - -const node = await Libp2p.create({ - modules: { - transport: [ - WS, - WebRTCStar - ], - streamMuxer: [MPLEX], - connEncryption: [NOISE], - }, - config: { - peerDiscovery: { - [WebRTCStar.tag]: { - enabled: true - } - } - } +import { createLibp2p } from 'libp2p' +import { WebSockets } from '@libp2p/websockets' +import { WebRTCStar } from '@libp2p/webrtc-star' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' + +const node = await createLibp2p({ + transports: [ + new WebSockets(), + new WebRTCStar() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + new Noise() + ] }) ``` #### Customizing Pubsub ```js -const Libp2p = require('libp2p') -const TCP = require('libp2p-tcp') -const MPLEX = require('libp2p-mplex') -const { NOISE } = require('libp2p-noise') -const GossipSub = require('libp2p-gossipsub') - -const { SignaturePolicy } = require('libp2p-interfaces/src/pubsub/signature-policy') - -const node = await Libp2p.create({ - modules: { - transport: [TCP], - streamMuxer: [MPLEX], - connEncryption: [NOISE], - pubsub: GossipSub - }, - config: { - pubsub: { // The pubsub options (and defaults) can be found in the pubsub router documentation - enabled: true, +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { GossipSub } from 'libp2p-gossipsub' +import { SignaturePolicy } from '@libp2p/interfaces/pubsub' + +const node = await createLibp2p({ + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + new Noise() + ], + pubsub: new GossipSub({ emitSelf: false, // whether the node should emit to self on publish globalSignaturePolicy: SignaturePolicy.StrictSign // message signing policy - } + }) } }) ``` @@ -360,64 +346,66 @@ const node = await Libp2p.create({ #### Customizing DHT ```js -const Libp2p = require('libp2p') -const TCP = require('libp2p-tcp') -const MPLEX = require('libp2p-mplex') -const { NOISE } = require('libp2p-noise') -const DHT = require('libp2p-kad-dht') - -const node = await Libp2p.create({ - modules: { - transport: [TCP], - streamMuxer: [MPLEX], - connEncryption: [NOISE], - dht: DHT - }, - config: { - dht: { // The DHT options (and defaults) can be found in its documentation - kBucketSize: 20, - enabled: true, // This flag is required for DHT to run (disabled by default) - clientMode: false // Whether to run the WAN DHT in client or server mode (default: client mode) - } - } +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { KadDHT } from '@libp2p/kad-dht' + +const node = await createLibp2p({ + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + new Noise() + ], + dht: new KadDHT({ + kBucketSize: 20, + clientMode: false // Whether to run the WAN DHT in client or server mode (default: client mode) + }) }) ``` #### Setup with Content and Peer Routing ```js -const Libp2p = require('libp2p') -const TCP = require('libp2p-tcp') -const MPLEX = require('libp2p-mplex') -const { NOISE } = require('libp2p-noise') -const ipfsHttpClient = require('ipfs-http-client') -const DelegatedPeerRouter = require('libp2p-delegated-peer-routing') -const DelegatedContentRouter = require('libp2p-delegated-content-routing') -const PeerId = require('peer-id') +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { create as ipfsHttpClient } from 'ipfs-http-client' +import { DelegatedPeerRouting } from '@libp2p/delegated-peer-routing' +import { DelegatedContentRouting} from '@libp2p/delegated-content-routing' + // create a peerId const peerId = await PeerId.create() -const delegatedPeerRouting = new DelegatedPeerRouter(ipfsHttpClient.create({ +const delegatedPeerRouting = new DelegatedPeerRouting(ipfsHttpClient.create({ host: 'node0.delegate.ipfs.io', // In production you should setup your own delegates protocol: 'https', port: 443 })) -const delegatedContentRouting = new DelegatedContentRouter(peerId, ipfsHttpClient.create({ +const delegatedContentRouting = new DelegatedContentRouting(peerId, ipfsHttpClient.create({ host: 'node0.delegate.ipfs.io', // In production you should setup your own delegates protocol: 'https', port: 443 })) -const node = await Libp2p.create({ - modules: { - transport: [TCP], - streamMuxer: [MPLEX], - connEncryption: [NOISE], - contentRouting: [delegatedContentRouting], - peerRouting: [delegatedPeerRouting], - }, +const node = await createLibp2p({ + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()], + contentRouting: [ + delegatedContentRouting + ], + peerRouting: [ + delegatedPeerRouting + ], peerId, peerRouting: { // Peer routing configuration refreshManager: { // Refresh known and connected closest peers @@ -432,29 +420,25 @@ 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 - } +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' + +const node = await createLibp2p({ + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()], + 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 } } }) @@ -463,24 +447,20 @@ const node = await Libp2p.create({ #### Setup with Auto 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. - 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 - } +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' + +const node = await createLibp2p({ + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()] + 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. + 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 } } }) @@ -496,21 +476,19 @@ Libp2p allows you to setup a secure keychain to manage your keys. The keychain c | datastore | `object` | must implement [ipfs/interface-datastore](https://github.com/ipfs/interface-datastore) | ```js -const Libp2p = require('libp2p') -const TCP = require('libp2p-tcp') -const MPLEX = require('libp2p-mplex') -const { NOISE } = require('libp2p-noise') -const { LevelDatastore } = require('datastore-level') +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { LevelDatastore } from 'datastore-level' const datastore = new LevelDatastore('path/to/store') await datastore.open() -const node = await Libp2p.create({ - modules: { - transport: [TCP], - streamMuxer: [MPLEX], - connEncryption: [NOISE] - }, +const node = await createLibp2p({ + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()], keychain: { pass: 'notsafepassword123456789', datastore: dsInstant, @@ -536,20 +514,18 @@ Dialing in libp2p can be configured to limit the rate of dialing, and how long d The below configuration example shows how the dialer should be configured, with the current defaults: ```js -const Libp2p = require('libp2p') -const TCP = require('libp2p-tcp') -const MPLEX = require('libp2p-mplex') -const { NOISE } = require('libp2p-noise') - -const { dnsaddrResolver } = require('multiaddr/src/resolvers') -const { publicAddressesFirst } = require('libp2p-utils/src/address-sort') - -const node = await Libp2p.create({ - modules: { - transport: [TCP], - streamMuxer: [MPLEX], - connEncryption: [NOISE] - }, +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' + +import { dnsaddrResolver } from '@multiformats/multiaddr/resolvers' +import { publicAddressesFirst } from '@libp2p-utils/address-sort' + +const node = await createLibp2p({ + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()], dialer: { maxParallelDials: 100, maxAddrsToDial: 25, @@ -567,17 +543,15 @@ const node = await Libp2p.create({ The Connection Manager prunes Connections in libp2p whenever certain limits are exceeded. If Metrics are enabled, you can also configure the Connection Manager to monitor the bandwidth of libp2p and prune connections as needed. You can read more about what Connection Manager does at [./CONNECTION_MANAGER.md](./CONNECTION_MANAGER.md). The configuration values below show the defaults for Connection Manager. See [./CONNECTION_MANAGER.md](./CONNECTION_MANAGER.md#options) for a full description of the parameters. ```js -const Libp2p = require('libp2p') -const TCP = require('libp2p-tcp') -const MPLEX = require('libp2p-mplex') -const { NOISE } = require('libp2p-noise') - -const node = await Libp2p.create({ - modules: { - transport: [TCP], - streamMuxer: [MPLEX], - connEncryption: [NOISE] - }, +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' + +const node = await createLibp2p({ + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()], connectionManager: { maxConnections: Infinity, minConnections: 0, @@ -615,7 +589,7 @@ The order in which methods are called is as follows: 3. `connectionGater.denyInboundUpgradedConnection(...)` ```js -const node = await Libp2p.create({ +const node = await createLibp2p({ // .. other config connectionGater: { /** @@ -719,19 +693,17 @@ const node = await Libp2p.create({ The Transport Manager is responsible for managing the libp2p transports life cycle. This includes starting listeners for the provided listen addresses, closing these listeners and dialing using the provided transports. By default, if a libp2p node has a list of multiaddrs for listening on and there are no valid transports for those multiaddrs, libp2p will throw an error on startup and shutdown. However, for some applications it is perfectly acceptable for libp2p nodes to start in dial only mode if all the listen multiaddrs failed. This error tolerance can be enabled as follows: ```js -const Libp2p = require('libp2p') -const TCP = require('libp2p-tcp') -const MPLEX = require('libp2p-mplex') -const { NOISE } = require('libp2p-noise') - -const { FaultTolerance } = require('libp2p/src/transport-manager') - -const node = await Libp2p.create({ - modules: { - transport: [TCP], - streamMuxer: [MPLEX], - connEncryption: [NOISE] - }, +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' + +const { FaultTolerance } from 'libp2p/src/transport-manager') + +const node = await createLibp2p({ + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()], transportManager: { faultTolerance: FaultTolerance.NO_FATAL } @@ -753,17 +725,15 @@ Metrics are disabled in libp2p by default. You can enable and configure them as The below configuration example shows how the metrics should be configured. Aside from enabled being `false` by default, the following default configuration options are listed below: ```js -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] - }, +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' + +const node = await createLibp2p({ + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()] metrics: { enabled: true, computeThrottleMaxQueueSize: 1000, @@ -792,22 +762,20 @@ The threshold number represents the maximum number of "dirty peers" allowed in t The below configuration example shows how the PeerStore should be configured. Aside from persistence being `false` by default, the following default configuration options are listed below: ```js -const Libp2p = require('libp2p') -const TCP = require('libp2p-tcp') -const MPLEX = require('libp2p-mplex') -const { NOISE } = require('libp2p-noise') -const LevelDatastore = require('datastore-level') +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { LevelDatastore } from 'datastore-level' const datastore = new LevelDatastore('path/to/store') await datastore.open() // level database must be ready before node boot -const node = await Libp2p.create({ +const node = await createLibp2p({ datastore, // pass the opened datastore - modules: { - transport: [TCP], - streamMuxer: [MPLEX], - connEncryption: [NOISE] - }, + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()], peerStore: { persistence: true, threshold: 5 @@ -820,19 +788,23 @@ const node = await Libp2p.create({ Some Transports can be passed additional options when they are created. For example, `libp2p-webrtc-star` accepts an optional, custom `wrtc` implementation. In addition to libp2p passing itself and an `Upgrader` to handle connection upgrading, libp2p will also pass the options, if they are provided, from `config.transport`. ```js -const Libp2p = require('libp2p') -const WebRTCStar = require('libp2p-webrtc-star') -const MPLEX = require('libp2p-mplex') -const { NOISE } = require('libp2p-noise') -const wrtc = require('wrtc') +import { createLibp2p } from 'libp2p' +import { WebRTCStar } from '@libp2p/webrtc-star' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import wrtc from 'wrtc' const transportKey = WebRTCStar.prototype[Symbol.toStringTag] -const node = await Libp2p.create({ - modules: { - transport: [WebRTCStar], - streamMuxer: [MPLEX], - connEncryption: [NOISE] - }, +const node = await createLibp2p({ + transports: [ + new WebRTCStar() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + new Noise() + ], config: { transport: { [transportKey]: { @@ -847,12 +819,16 @@ During Libp2p startup, transport listeners will be created for the configured li ```js const transportKey = WebRTCStar.prototype[Symbol.toStringTag] -const node = await Libp2p.create({ - modules: { - transport: [WebRTCStar], - streamMuxer: [MPLEX], - connEncryption: [NOISE] - }, +const node = await createLibp2p({ + transports: [ + new WebRTCStar() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + new Noise() + ], addresses: { listen: ['/dns4/your-wrtc-star.pub/tcp/443/wss/p2p-webrtc-star'] // your webrtc dns multiaddr }, @@ -879,18 +855,16 @@ Network Address Translation (NAT) is a function performed by your router to enab The NAT manager can be configured as follows: ```js -const node = await Libp2p.create({ +const node = await createLibp2p({ config: { nat: { description: 'my-node', // set as the port mapping description on the router, defaults the current libp2p version and your peer id enabled: true, // defaults to true gateway: '192.168.1.1', // leave unset to auto-discover externalIp: '80.1.1.1', // leave unset to auto-discover + localAddress: '129.168.1.123', // leave unset to auto-discover ttl: 7200, // TTL for port mappings (min 20 minutes) keepAlive: true, // Refresh port mapping after TTL expires - pmp: { - enabled: false, // defaults to false - } } } }) @@ -911,10 +885,8 @@ By default under nodejs libp2p will attempt to use [UPnP](https://en.wikipedia.o Changing the protocol name prefix can isolate default public network (IPFS) for custom purposes. ```js -const node = await Libp2p.create({ - config: { - protocolPrefix: 'ipfs' // default - } +const node = await createLibp2p({ + protocolPrefix: 'ipfs' // default }) /* protocols: [ @@ -925,7 +897,6 @@ protocols: [ */ ``` - ## Configuration examples As libp2p is designed to be a modular networking library, its usage will vary based on individual project needs. We've included links to some existing project configurations for your reference, in case you wish to replicate their configuration: diff --git a/doc/GETTING_STARTED.md b/doc/GETTING_STARTED.md index fd180e8428..c6169f1f82 100644 --- a/doc/GETTING_STARTED.md +++ b/doc/GETTING_STARTED.md @@ -12,7 +12,6 @@ Welcome to libp2p! This guide will walk you through setting up a fully functiona - [Running Libp2p](#running-libp2p) - [Custom setup](#custom-setup) - [Peer Discovery](#peer-discovery) - - [Pubsub](#pubsub) - [What is next](#what-is-next) ## Install @@ -46,13 +45,11 @@ npm install libp2p-websockets Now that we have the module installed, let's configure libp2p to use the Transport. We'll use the [`Libp2p.create`](./API.md#create) method, which takes a single configuration object as its only parameter. We can add the Transport by passing it into the `modules.transport` array: ```js -const Libp2p = require('libp2p') -const WebSockets = require('libp2p-websockets') +import { createLibp2p } from 'libp2p' +import { WebSockets } from '@libp2p/websockets' -const node = await Libp2p.create({ - modules: { - transport: [WebSockets] - } +const node = await createLibp2p({ + transports: [new WebSockets()] }) ``` @@ -78,15 +75,13 @@ npm install libp2p-noise With `libp2p-noise` installed, we can add it to our existing configuration by importing it and adding it to the `modules.connEncryption` array: ```js -const Libp2p = require('libp2p') -const WebSockets = require('libp2p-websockets') -const { NOISE } = require('libp2p-noise') - -const node = await Libp2p.create({ - modules: { - transport: [WebSockets], - connEncryption: [NOISE] - } +import { createLibp2p } from 'libp2p' +import { WebSockets } from '@libp2p/websockets' +import { Noise } from '@chainsafe/libp2p-noise' + +const node = await createLibp2p({ + transports: [new WebSockets()], + connectionEncryption: [new Noise()] }) ``` @@ -110,17 +105,15 @@ npm install libp2p-mplex ``` ```js -const Libp2p = require('libp2p') -const WebSockets = require('libp2p-websockets') -const { NOISE } = require('libp2p-noise') -const MPLEX = require('libp2p-mplex') - -const node = await Libp2p.create({ - modules: { - transport: [WebSockets], - connEncryption: [NOISE], - streamMuxer: [MPLEX] - } +import { createLibp2p } from 'libp2p' +import { WebSockets } from '@libp2p/websockets' +import { Noise } from '@chainsafe/libp2p-noise' +import { Mplex } from '@libp2p/mplex' + +const node = await createLibp2p({ + transports: [new WebSockets()], + connectionEncryption: [new Noise()], + streamMuxers: [new Mplex()] }) ``` @@ -137,20 +130,18 @@ If you want to know more about libp2p stream multiplexing, you should read the f Now that you have configured a [**Transport**][transport], [**Crypto**][crypto] and [**Stream Multiplexer**](streamMuxer) module, you can start your libp2p node. We can start and stop libp2p using the [`libp2p.start()`](./API.md#start) and [`libp2p.stop()`](./API.md#stop) methods. ```js -const Libp2p = require('libp2p') -const WebSockets = require('libp2p-websockets') -const { NOISE } = require('libp2p-noise') -const MPLEX = require('libp2p-mplex') +import { createLibp2p } from 'libp2p' +import { WebSockets } from '@libp2p/websockets' +import { Noise } from '@chainsafe/libp2p-noise' +import { Mplex } from '@libp2p/mplex' -const node = await Libp2p.create({ +const node = await createLibp2p({ addresses: { listen: ['/ip4/127.0.0.1/tcp/8000/ws'] }, - modules: { - transport: [WebSockets], - connEncryption: [NOISE], - streamMuxer: [MPLEX] - } + transports: [new WebSockets()], + connectionEncryption: [new Noise()], + streamMuxers: [new Mplex()] }) // start libp2p @@ -195,12 +186,12 @@ npm install libp2p-bootstrap We can provide specific configurations for each protocol within a `config.peerDiscovery` property in the options as shown below. ```js -const Libp2p = require('libp2p') -const WebSockets = require('libp2p-websockets') -const { NOISE } = require('libp2p-noise') -const MPLEX = require('libp2p-mplex') +import { createLibp2p } from 'libp2p' +import { WebSockets } from '@libp2p/websockets' +import { Noise } from '@chainsafe/libp2p-noise' +import { Mplex } from '@libp2p/mplex' -const Bootstrap = require('libp2p-bootstrap') +import { Bootstrap } from '@libp2p/bootstrap' // Known peers addresses const bootstrapMultiaddrs = [ @@ -208,23 +199,25 @@ const bootstrapMultiaddrs = [ '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN' ] -const node = await Libp2p.create({ - modules: { - transport: [WebSockets], - connEncryption: [NOISE], - streamMuxer: [MPLEX], - peerDiscovery: [Bootstrap] - }, - config: { - peerDiscovery: { - 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]: { - enabled: true, - list: bootstrapMultiaddrs // provide array of multiaddrs - } - } +const node = await createLibp2p({ + transports: [ + new WebSockets() + ], + connectionEncryption: [ + new Noise() + ], + streamMuxers: [ + new Mplex() + ], + peerDiscovery: [ + new Bootstrap({ + list: bootstrapMultiaddrs // provide array of multiaddrs + }) + ], + connectionManager: { + 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. } }) diff --git a/doc/PEER_DISCOVERY.md b/doc/PEER_DISCOVERY.md index 65f93e19f9..4b9aea09cc 100644 --- a/doc/PEER_DISCOVERY.md +++ b/doc/PEER_DISCOVERY.md @@ -3,7 +3,7 @@ **Synopsis**: * All peers discovered are emitted via `peer:discovery` so applications can take any desired action. * Libp2p defaults to automatically connecting to new peers, when under the [ConnectionManager](https://github.com/libp2p/js-libp2p-connection-manager) low watermark (minimum peers). - * Applications can disable this via the `peerDiscovery.autoDial` config property, and handle connections themselves. + * Applications can disable this via the `connectionManager.autoDial` config property, and handle connections themselves. * Applications who have not disabled this should **never** connect on peer discovery. Applications should use the `peer:connect` event if they wish to take a specific action on new peers. ## Scenarios diff --git a/doc/STREAMING_ITERABLES.md b/doc/STREAMING_ITERABLES.md index 5804ea48d0..fe6a349ded 100644 --- a/doc/STREAMING_ITERABLES.md +++ b/doc/STREAMING_ITERABLES.md @@ -22,8 +22,8 @@ Sometimes you may need to wrap an existing duplex stream in order to perform incoming and outgoing [transforms](#transform) on data. This type of wrapping is commonly used in stream encryption/decryption. Using [it-pair][it-pair] and [it-pipe][it-pipe], we can do this rather easily, given an existing [duplex iterable](#duplex). ```js -const duplexPair = require('it-pair/duplex') -const pipe = require('it-pipe') +const duplexPair from 'it-pair/duplex') +import { pipe } from 'it-pipe' // Wrapper is what we will write and read from // This gives us two duplex iterables that are internally connected diff --git a/doc/migrations/v0.26-v0.27.md b/doc/migrations/v0.26-v0.27.md index b7d3acaf3f..67dd44c639 100644 --- a/doc/migrations/v0.26-v0.27.md +++ b/doc/migrations/v0.26-v0.27.md @@ -4,16 +4,18 @@ A migration guide for refactoring your application code from libp2p v0.26.x to v ## Table of Contents -- [Migrating from callbacks](#migrating-from-callbacks) -- [Pull Streams to Streaming Iterables](#pull-streams-to-streaming-iterables) -- [Sample API Migrations](#sample-api-migrations) - - [Registering Protocol Handlers](#registering-protocol-handlers) - - [Dialing and Sending Data](#dialing-and-sending-data) - - [Checking if a peer is connected](#checking-if-a-peer-is-connected) - - [Pinging another peer](#pinging-another-peer) - - [Pubsub](#pubsub) - - [Getting subscribers](#getting-subscribers) - - [Getting subscribed topics](#getting-subscribed-topics) +- [Migrating to the libp2p@0.27 API](#migrating-to-the-libp2p027-api) + - [Table of Contents](#table-of-contents) + - [Migrating from callbacks](#migrating-from-callbacks) + - [Pull Streams to Streaming Iterables](#pull-streams-to-streaming-iterables) + - [Sample API Migrations](#sample-api-migrations) + - [Registering Protocol Handlers](#registering-protocol-handlers) + - [Dialing and Sending Data](#dialing-and-sending-data) + - [Checking if a peer is connected](#checking-if-a-peer-is-connected) + - [Pinging another peer](#pinging-another-peer) + - [Pubsub](#pubsub) + - [Getting subscribers](#getting-subscribers) + - [Getting subscribed topics](#getting-subscribed-topics) ## Migrating from callbacks @@ -47,13 +49,13 @@ Protocol registration is very similar to how it previously was, however, the han **Before** ```js -const pull = require('pull-stream') +const pull from 'pull-stream') libp2p.handle('/echo/1.0.0', (protocol, conn) => pull(conn, conn)) ``` **After** ```js -const pipe = require('it-pipe') +const pipe from 'it-pipe') libp2p.handle(['/echo/1.0.0'], ({ protocol, stream }) => pipe(stream, stream)) ``` @@ -63,7 +65,7 @@ libp2p.handle(['/echo/1.0.0'], ({ protocol, stream }) => pipe(stream, stream)) **Before** ```js -const pull = require('pull-stream') +const pull from 'pull-stream') libp2p.dialProtocol(peerInfo, '/echo/1.0.0', (err, conn) => { if (err) { throw err } pull( @@ -80,7 +82,7 @@ libp2p.dialProtocol(peerInfo, '/echo/1.0.0', (err, conn) => { **After** ```js -const pipe = require('it-pipe') +const pipe from 'it-pipe') const { protocol, stream } = await libp2p.dialProtocol(peerInfo, '/echo/1.0.0') await pipe( ['hey'], diff --git a/doc/migrations/v0.27-v0.28.md b/doc/migrations/v0.27-v0.28.md index 67c9882bb9..23c8f9d572 100644 --- a/doc/migrations/v0.27-v0.28.md +++ b/doc/migrations/v0.27-v0.28.md @@ -16,7 +16,7 @@ A migration guide for refactoring your application code from libp2p v0.27.x to v In `libp2p@0.27` we integrated the PeerStore (former [peer-book](https://github.com/libp2p/js-peer-book)) into the codebase. By that time, it was not documented in the [API DOC](../API.md) since it kept the same API as the `peer-book` and it was expected to be completelly rewritten in `libp2p@0.28`. -Moving towards a separation of concerns regarding known peers' data, as well as enabling PeerStore persistence, the PeerStore is now divided into four main components: `AddressBook`, `ProtoBook`, `KeyBook` and `MetadataBook`. This resulted in API changes in the PeerStore, since each type of peer data should now be added in an atomic fashion. +Moving towards a separation of concerns regarding known peers' data, as well as enabling PeerStore persistence, the PeerStore is now divided into four main components: `AddressBook`, `ProtoBook`, `KeyBook` and `MetadataBook`. This resulted in API changes in the PeerStore, since each type of peer data should now be added in an atomic fashion. ### Adding a Peer @@ -109,7 +109,7 @@ const peers = libp2p.peerStore.peers Since this PeerInfo instances were navigating through the entire codebases, some data inconsistencies could be observed in libp2p. Different libp2p subsystems were running with different visions of the known peers data. For instance, a libp2p subsystem receives a copy of this instance with the peer multiaddrs and protocols, but if new data of the peer is obtained from other subsystem, it would not be updated on the former. Moreover, considering that several subsystems were modifying the peer data, libp2p had no way to determine the accurate data. -Considering the complete revamp of the libp2p PeerStore towards its second version, the PeerStore now acts as the single source of truth, we do not need to carry [`PeerInfo`][peer-info] instances around. This also solves all the problems stated above, since subsystems will report new observations to the PeerStore. +Considering the complete revamp of the libp2p PeerStore towards its second version, the PeerStore now acts as the single source of truth, we do not need to carry [`PeerInfo`][peer-info] instances around. This also solves all the problems stated above, since subsystems will report new observations to the PeerStore. ### Create @@ -211,7 +211,7 @@ await libp2p.start() #### Peer Dialing, Hangup and Ping -`libp2p.dial`, `libp2p.dialProtocol`, `libp2p.hangup` and `libp2p.ping` supported as the target parameter a [`PeerInfo`](peer-info), a [`PeerId`](peer-id), a [`Multiaddr`][multiaddr] and a string representation of the multiaddr. Considering that [`PeerInfo`](peer-info) is being removed from libp2p, all these methods will now support the other 3 possibilities. +`libp2p.dial`, `libp2p.dialProtocol`, `libp2p.hangup` and `libp2p.ping` supported as the target parameter a [`PeerInfo`](peer-info), a [`PeerId`](peer-id), a [`Multiaddr`][multiaddr] and a string representation of the multiaddr. Considering that [`PeerInfo`](peer-info) is being removed from libp2p, all these methods will now support the other 3 possibilities. There is one relevant aspect to consider with this change. When using a [`PeerId`](peer-id), the PeerStore **MUST** have known addresses for that peer in its AddressBook, so that it can perform the request. This was also true in the past, but it is important pointing it out because it might not be enough to switch from using [`PeerInfo`](peer-info) to [`PeerId`](peer-id). When using a [`PeerInfo`](peer-info), the PeerStore was not required to have the multiaddrs when they existed on the PeerInfo instance. diff --git a/doc/migrations/v0.28-v0.29.md b/doc/migrations/v0.28-v0.29.md index 131b97ae8a..1856865291 100644 --- a/doc/migrations/v0.28-v0.29.md +++ b/doc/migrations/v0.28-v0.29.md @@ -46,18 +46,18 @@ Publish uses `Uint8Array` data instead of `Buffer`. const topic = 'topic' const data = Buffer.from('data') -await libp2p.pubsub.publish(topic, data) +await libp2p.pubsub.publish(topic, data) ``` **After** ```js -const uint8ArrayFromString = require('uint8arrays/from-string') +const uint8ArrayFromString from 'uint8arrays/from-string') const topic = 'topic' const data = uint8ArrayFromString('data') -await libp2p.pubsub.publish(topic, data) +await libp2p.pubsub.publish(topic, data) ``` #### Subscribe @@ -79,7 +79,7 @@ libp2p.pubsub.subscribe(topic, handler) **After** ```js -const uint8ArrayToString = require('uint8arrays/to-string') +const uint8ArrayToString from 'uint8arrays/to-string') const topic = 'topic' const handler = (msg) => { @@ -106,7 +106,7 @@ libp2p.pubsub.subscribe(topics, handler) **After** ```js -const uint8ArrayToString = require('uint8arrays/to-string') +const uint8ArrayToString from 'uint8arrays/to-string') const topics = ['a', 'b'] const handler = (msg) => { @@ -177,8 +177,8 @@ Aiming to improve libp2p browser support, we are moving away from node core modu We use the [uint8arrays](https://www.npmjs.com/package/uint8arrays) utilities module to deal with `Uint8Arrays` easily and we recommend its usage in the application layer. Thanks for the module [@achingbrain](https://github.com/achingbrain)! It includes utilities like `compare`, `concat`, `equals`, `fromString` and `toString`. In this migration examples, we will be using the following: ```js -const uint8ArrayFromString = require('uint8arrays/from-string') -const uint8ArrayToString = require('uint8arrays/to-string') +const uint8ArrayFromString from 'uint8arrays/from-string') +const uint8ArrayToString from 'uint8arrays/to-string') ``` #### contentRouting.put diff --git a/doc/migrations/v0.29-v0.30.md b/doc/migrations/v0.29-v0.30.md index ffec86f926..2d90ee0f8d 100644 --- a/doc/migrations/v0.29-v0.30.md +++ b/doc/migrations/v0.29-v0.30.md @@ -5,9 +5,13 @@ A migration guide for refactoring your application code from libp2p v0.29.x to v ## Table of Contents -- [API](#api) -- [Development and Testing](#development-and-testing) -- [Module Updates](#module-updates) +- [Migrating to libp2p@30](#migrating-to-libp2p30) + - [Table of Contents](#table-of-contents) + - [API](#api) + - [Pubsub](#pubsub) + - [Addresses](#addresses) + - [Development and Testing](#development-and-testing) + - [Module Updates](#module-updates) ## API @@ -20,8 +24,8 @@ Now `js-libp2p` does not overwrite the pubsub router options anymore. Upstream p **Before** ```js -const Gossipsub = require('libp2p-gossipsub') -const Libp2p = require('libp2p') +const Gossipsub from 'libp2p-gossipsub') +const Libp2p from 'libp2p') const libp2p = await Libp2p.create({ modules: { @@ -34,8 +38,8 @@ const libp2p = await Libp2p.create({ **After** ```js -const Gossipsub = require('libp2p-gossipsub') -const Libp2p = require('libp2p') +const Gossipsub from 'libp2p-gossipsub') +const Libp2p from 'libp2p') const libp2p = await Libp2p.create({ modules: { @@ -57,8 +61,8 @@ The signing property is now based on a `globalSignaturePolicy` option instead of **Before** ```js -const Gossipsub = require('libp2p-gossipsub') -const Libp2p = require('libp2p') +const Gossipsub from 'libp2p-gossipsub') +const Libp2p from 'libp2p') const libp2p = await Libp2p.create({ modules: { @@ -77,9 +81,9 @@ const libp2p = await Libp2p.create({ **After** ```js -const Gossipsub = require('libp2p-gossipsub') -const { SignaturePolicy } = require('libp2p-interfaces/src/pubsub/signature-policy') -const Libp2p = require('libp2p') +const Gossipsub from 'libp2p-gossipsub') +const { SignaturePolicy } from 'libp2p-interfaces/src/pubsub/signature-policy') +const Libp2p from 'libp2p') const libp2p = await Libp2p.create({ modules: { @@ -101,7 +105,7 @@ Libp2p has supported `noAnnounce` addresses configuration for some time now. How **Before** ```js -const Libp2p = require('libp2p') +const Libp2p from 'libp2p') const libp2p = await Libp2p.create({ addresses: { @@ -115,10 +119,10 @@ const libp2p = await Libp2p.create({ **After** ```js -const Libp2p = require('libp2p') +const Libp2p from 'libp2p') // Libp2p utils has several multiaddr utils you can leverage -const isPrivate = require('libp2p-utils/src/multiaddr/is-private') +const isPrivate from 'libp2p-utils/src/multiaddr/is-private') const libp2p = await Libp2p.create({ addresses: { @@ -131,7 +135,7 @@ const libp2p = await Libp2p.create({ ``` It is important pointing out another change regarding address advertising. This is not an API breaking change, but it might have influence on your libp2p setup. -Previously, when using the addresses `announce` property, its multiaddrs were concatenated with the `listen` multiaddrs and then they were filtered out by the `noAnnounce` multiaddrs, in order to create the list of multiaddrs to advertise. +Previously, when using the addresses `announce` property, its multiaddrs were concatenated with the `listen` multiaddrs and then they were filtered out by the `noAnnounce` multiaddrs, in order to create the list of multiaddrs to advertise. In `libp2p@0.30` the logic now operates as follows: - If `announce` addresses are provided, only they will be announced (no filters are applied) @@ -145,9 +149,9 @@ While this is not an API breaking change, there was a behavioral breaking change With this new behavior, if you need to use non DNS addresses, you can configure your libp2p node as follows: ```js -const Websockets = require('libp2p-websockets') -const filters = require('libp2p-websockets/src/filters') -const Libp2p = require('libp2p') +const Websockets from 'libp2p-websockets') +const filters from 'libp2p-websockets/src/filters') +const Libp2p from 'libp2p') const transportKey = Websockets.prototype[Symbol.toStringTag] const libp2p = await Libp2p.create({ @@ -170,7 +174,7 @@ const libp2p = await Libp2p.create({ With this release you should update the following libp2p modules if you are relying on them: diff --git a/doc/migrations/v0.30-v0.31.md b/doc/migrations/v0.30-v0.31.md index 8ccb12d1ba..9065adc5f9 100644 --- a/doc/migrations/v0.30-v0.31.md +++ b/doc/migrations/v0.30-v0.31.md @@ -100,7 +100,7 @@ const keychain = new Keychain(datastore, { With this release you should update the following libp2p modules if you are relying on them: diff --git a/examples/auto-relay/README.md b/examples/auto-relay/README.md index 0f88b7580e..e03d493939 100644 --- a/examples/auto-relay/README.md +++ b/examples/auto-relay/README.md @@ -16,31 +16,27 @@ In the first step of this example, we need to configure and run a relay node in The relay node will need to have its relay subsystem enabled, as well as its HOP capability. It can be configured as follows: ```js -const Libp2p = require('libp2p') -const Websockets = require('libp2p-websockets') -const { NOISE } = require('@chainsafe/libp2p-noise') -const MPLEX = require('libp2p-mplex') - -const node = await Libp2p.create({ - modules: { - transport: [Websockets], - connEncryption: [NOISE], - streamMuxer: [MPLEX] - }, +import { createLibp2p } from 'libp2p' +import { WebSockets } from '@libp2p/websockets' +import { Noise } from '@chainsafe/libp2p-noise' +import { Mplex } from '@libp2p/mplex' + +const node = await createLibp2p({ + transports: [new WebSockets()], + connectionEncryption: [new Noise()], + streamMuxers: [new Mplex()] addresses: { listen: ['/ip4/0.0.0.0/tcp/0/ws'] // TODO check "What is next?" section // announce: ['/dns4/auto-relay.libp2p.io/tcp/443/wss/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3'] }, - config: { - relay: { + relay: { + enabled: true, + hop: { + enabled: true + }, + advertise: { enabled: true, - hop: { - enabled: true - }, - advertise: { - enabled: true, - } } } }) @@ -74,29 +70,25 @@ Listening on: One of the typical use cases for Auto Relay is nodes behind a NAT or browser nodes due to their inability to expose a public address. For running a libp2p node that automatically binds itself to connected HOP relays, you can see the following: ```js -const Libp2p = require('libp2p') -const Websockets = require('libp2p-websockets') -const { NOISE } = require('@chainsafe/libp2p-noise') -const MPLEX = require('libp2p-mplex') +import { createLibp2p } from 'libp2p' +import { WebSockets } from '@libp2p/websockets' +import { Noise } from '@chainsafe/libp2p-noise' +import { Mplex } from '@libp2p/mplex' const relayAddr = process.argv[2] if (!relayAddr) { throw new Error('the relay address needs to be specified as a parameter') } -const node = await Libp2p.create({ - modules: { - transport: [Websockets], - connEncryption: [NOISE], - streamMuxer: [MPLEX] - }, - config: { - relay: { +const node = await createLibp2p({ + transports: [new WebSockets()], + connectionEncryption: [new Noise()], + streamMuxers: [new Mplex()], + relay: { + enabled: true, + autoRelay: { enabled: true, - autoRelay: { - enabled: true, - maxListeners: 2 - } + maxListeners: 2 } } }) @@ -142,22 +134,20 @@ Instead of dialing this relay manually, you could set up this node with the Boot Now that you have a relay node and a node bound to that relay, you can test connecting to the auto relay node via the relay. ```js -const Libp2p = require('libp2p') -const Websockets = require('libp2p-websockets') -const { NOISE } = require('@chainsafe/libp2p-noise') -const MPLEX = require('libp2p-mplex') +import { createLibp2p } from 'libp2p' +import { WebSockets } from '@libp2p/websockets' +import { Noise } from '@chainsafe/libp2p-noise' +import { Mplex } from '@libp2p/mplex' const autoRelayNodeAddr = process.argv[2] if (!autoRelayNodeAddr) { throw new Error('the auto relay node address needs to be specified') } -const node = await Libp2p.create({ - modules: { - transport: [Websockets], - connEncryption: [NOISE], - streamMuxer: [MPLEX] - } +const node = await createLibp2p({ + transports: [new WebSockets()], + connectionEncryption: [new Noise()], + streamMuxers: [new Mplex()] }) await node.start() diff --git a/examples/auto-relay/dialer.js b/examples/auto-relay/dialer.js index 5f6423ede5..e358e09959 100644 --- a/examples/auto-relay/dialer.js +++ b/examples/auto-relay/dialer.js @@ -1,9 +1,7 @@ -'use strict' - -const Libp2p = require('libp2p') -const Websockets = require('libp2p-websockets') -const { NOISE } = require('@chainsafe/libp2p-noise') -const MPLEX = require('libp2p-mplex') +import { createLibp2p } from 'libp2p' +import { WebSockets } from '@libp2p/websockets' +import { Noise } from '@chainsafe/libp2p-noise' +import { Mplex } from '@libp2p/mplex' async function main () { const autoRelayNodeAddr = process.argv[2] @@ -11,16 +9,20 @@ async function main () { throw new Error('the auto relay node address needs to be specified') } - const node = await Libp2p.create({ - modules: { - transport: [Websockets], - connEncryption: [NOISE], - streamMuxer: [MPLEX] - } + const node = await createLibp2p({ + transports: [ + new WebSockets() + ], + connectionEncryption: [ + new Noise() + ], + streamMuxers: [ + new Mplex() + ] }) await node.start() - console.log(`Node started with id ${node.peerId.toB58String()}`) + console.log(`Node started with id ${node.peerId.toString()}`) const conn = await node.dial(autoRelayNodeAddr) console.log(`Connected to the auto relay node via ${conn.remoteAddr.toString()}`) diff --git a/examples/auto-relay/listener.js b/examples/auto-relay/listener.js index 4f762c675d..50aae8bb09 100644 --- a/examples/auto-relay/listener.js +++ b/examples/auto-relay/listener.js @@ -1,9 +1,7 @@ -'use strict' - -const Libp2p = require('libp2p') -const Websockets = require('libp2p-websockets') -const { NOISE } = require('@chainsafe/libp2p-noise') -const MPLEX = require('libp2p-mplex') +import { createLibp2p } from 'libp2p' +import { WebSockets } from '@libp2p/websockets' +import { Noise } from '@chainsafe/libp2p-noise' +import { Mplex } from '@libp2p/mplex' async function main () { const relayAddr = process.argv[2] @@ -11,37 +9,41 @@ async function main () { throw new Error('the relay address needs to be specified as a parameter') } - const node = await Libp2p.create({ - modules: { - transport: [Websockets], - connEncryption: [NOISE], - streamMuxer: [MPLEX] - }, - config: { - relay: { + const node = await createLibp2p({ + transports: [ + new WebSockets() + ], + connectionEncryption: [ + new Noise() + ], + streamMuxers: [ + new Mplex() + ], + relay: { + enabled: true, + autoRelay: { enabled: true, - autoRelay: { - enabled: true, - maxListeners: 2 - } + maxListeners: 2 } } }) await node.start() - console.log(`Node started with id ${node.peerId.toB58String()}`) + console.log(`Node started with id ${node.peerId.toString()}`) const conn = await node.dial(relayAddr) console.log(`Connected to the HOP relay ${conn.remotePeer.toString()}`) // Wait for connection and relay to be bind for the example purpose - node.peerStore.on('change:multiaddrs', ({ peerId }) => { + node.peerStore.addEventListener('change:multiaddrs', (evt) => { + const { peerId } = evt.detail + // Updated self multiaddrs? if (peerId.equals(node.peerId)) { - console.log(`Advertising with a relay address of ${node.multiaddrs[0].toString()}/p2p/${node.peerId.toB58String()}`) + console.log(`Advertising with a relay address of ${node.getMultiaddrs()[0].toString()}`) } - }) + }) } main() diff --git a/examples/auto-relay/relay.js b/examples/auto-relay/relay.js index 18a1df51cf..94ff3aceb4 100644 --- a/examples/auto-relay/relay.js +++ b/examples/auto-relay/relay.js @@ -1,40 +1,40 @@ -'use strict' - -const Libp2p = require('libp2p') -const Websockets = require('libp2p-websockets') -const { NOISE } = require('@chainsafe/libp2p-noise') -const MPLEX = require('libp2p-mplex') +import { createLibp2p } from 'libp2p' +import { WebSockets } from '@libp2p/websockets' +import { Noise } from '@chainsafe/libp2p-noise' +import { Mplex } from '@libp2p/mplex' async function main () { - const node = await Libp2p.create({ - modules: { - transport: [Websockets], - connEncryption: [NOISE], - streamMuxer: [MPLEX] - }, + const node = await createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/tcp/0/ws'] // TODO check "What is next?" section // announce: ['/dns4/auto-relay.libp2p.io/tcp/443/wss/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3'] }, - config: { - relay: { + transports: [ + new WebSockets() + ], + connectionEncryption: [ + new Noise() + ], + streamMuxers: [ + new Mplex() + ], + relay: { + enabled: true, + hop: { + enabled: true + }, + advertise: { enabled: true, - hop: { - enabled: true - }, - advertise: { - enabled: true, - } } } }) await node.start() - console.log(`Node started with id ${node.peerId.toB58String()}`) + console.log(`Node started with id ${node.peerId.toString()}`) console.log('Listening on:') - node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`)) + node.getMultiaddrs().forEach((ma) => console.log(ma.toString())) } main() diff --git a/examples/auto-relay/test.js b/examples/auto-relay/test.js index ac5945ab4e..4c7541b962 100644 --- a/examples/auto-relay/test.js +++ b/examples/auto-relay/test.js @@ -1,9 +1,10 @@ -'use strict' +import path from 'path' +import execa from 'execa' +import pDefer from 'p-defer' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { fileURLToPath } from 'url' -const path = require('path') -const execa = require('execa') -const pDefer = require('p-defer') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) function startProcess (name, args = []) { return execa('node', [path.join(__dirname, name), ...args], { @@ -12,7 +13,7 @@ function startProcess (name, args = []) { }) } -async function test () { +export async function test () { let output1 = '' let output2 = '' let output3 = '' @@ -50,7 +51,7 @@ async function test () { output2 += uint8ArrayToString(data) if (output2.includes('Advertising with a relay address of') && output2.includes('/p2p/')) { - autoRelayAddr = output2.trim().split('Advertising with a relay address of ')[1] + autoRelayAddr = output2.trim().split('Advertising with a relay address of ')[1].trim() proc2Ready.resolve() } }) @@ -90,5 +91,3 @@ async function test () { } }) } - -module.exports = test \ No newline at end of file diff --git a/examples/chat/src/dialer.js b/examples/chat/src/dialer.js index 2815448bd8..e9d59262e5 100644 --- a/examples/chat/src/dialer.js +++ b/examples/chat/src/dialer.js @@ -1,15 +1,16 @@ -'use strict' /* eslint-disable no-console */ -const PeerId = require('peer-id') -const { Multiaddr } = require('multiaddr') -const createLibp2p = require('./libp2p') -const { stdinToStream, streamToConsole } = require('./stream') +import { Multiaddr } from '@multiformats/multiaddr' +import { createLibp2p } from './libp2p.js' +import { stdinToStream, streamToConsole } from './stream.js' +import { createFromJSON } from '@libp2p/peer-id-factory' +import peerIdDialerJson from './peer-id-dialer.js' +import peerIdListenerJson from './peer-id-listener.js' async function run () { const [idDialer, idListener] = await Promise.all([ - PeerId.createFromJSON(require('./peer-id-dialer')), - PeerId.createFromJSON(require('./peer-id-listener')) + createFromJSON(peerIdDialerJson), + createFromJSON(peerIdListenerJson) ]) // Create a new libp2p node on localhost with a randomly chosen port @@ -25,12 +26,12 @@ async function run () { // Output this node's address console.log('Dialer ready, listening on:') - nodeDialer.multiaddrs.forEach((ma) => { - console.log(ma.toString() + '/p2p/' + idDialer.toB58String()) + nodeDialer.getMultiaddrs().forEach((ma) => { + console.log(ma.toString()) }) // Dial to the remote peer (the "listener") - const listenerMa = new Multiaddr(`/ip4/127.0.0.1/tcp/10333/p2p/${idListener.toB58String()}`) + const listenerMa = new Multiaddr(`/ip4/127.0.0.1/tcp/10333/p2p/${idListener.toString()}`) const { stream } = await nodeDialer.dialProtocol(listenerMa, '/chat/1.0.0') console.log('Dialer dialed to listener on protocol: /chat/1.0.0') diff --git a/examples/chat/src/libp2p.js b/examples/chat/src/libp2p.js index fbce60e93e..610dae0735 100644 --- a/examples/chat/src/libp2p.js +++ b/examples/chat/src/libp2p.js @@ -1,22 +1,23 @@ -'use strict' +import { TCP } from '@libp2p/tcp' +import { WebSockets } from '@libp2p/websockets' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import defaultsDeep from '@nodeutils/defaults-deep' +import { createLibp2p as create } from 'libp2p' -const TCP = require('libp2p-tcp') -const WS = require('libp2p-websockets') -const mplex = require('libp2p-mplex') -const { NOISE } = require('@chainsafe/libp2p-noise') -const defaultsDeep = require('@nodeutils/defaults-deep') -const libp2p = require('../../..') - -async function createLibp2p(_options) { +export async function createLibp2p(_options) { const defaults = { - modules: { - transport: [TCP, WS], - streamMuxer: [mplex], - connEncryption: [NOISE], - }, + transports: [ + new TCP(), + new WebSockets() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + new Noise() + ] } - return libp2p.create(defaultsDeep(_options, defaults)) + return create(defaultsDeep(_options, defaults)) } - -module.exports = createLibp2p diff --git a/examples/chat/src/listener.js b/examples/chat/src/listener.js index 9dc928cadd..dbaf0c2ad6 100644 --- a/examples/chat/src/listener.js +++ b/examples/chat/src/listener.js @@ -1,13 +1,13 @@ -'use strict' /* eslint-disable no-console */ -const PeerId = require('peer-id') -const createLibp2p = require('./libp2p.js') -const { stdinToStream, streamToConsole } = require('./stream') +import { createLibp2p } from './libp2p.js' +import { stdinToStream, streamToConsole } from './stream.js' +import { createFromJSON } from '@libp2p/peer-id-factory' +import peerIdListenerJson from './peer-id-listener.js' async function run () { // Create a new libp2p node with the given multi-address - const idListener = await PeerId.createFromJSON(require('./peer-id-listener')) + const idListener = await createFromJSON(peerIdListenerJson) const nodeListener = await createLibp2p({ peerId: idListener, addresses: { @@ -16,8 +16,9 @@ async function run () { }) // Log a message when a remote peer connects to us - nodeListener.connectionManager.on('peer:connect', (connection) => { - console.log('connected to: ', connection.remotePeer.toB58String()) + nodeListener.connectionManager.addEventListener('peer:connect', (evt) => { + const connection = evt.detail + console.log('connected to: ', connection.remotePeer.toString()) }) // Handle messages for the protocol @@ -33,8 +34,8 @@ async function run () { // Output listen addresses to the console console.log('Listener ready, listening on:') - nodeListener.multiaddrs.forEach((ma) => { - console.log(ma.toString() + '/p2p/' + idListener.toB58String()) + nodeListener.getMultiaddrs().forEach((ma) => { + console.log(ma.toString()) }) } diff --git a/examples/chat/src/peer-id-dialer.json b/examples/chat/src/peer-id-dialer.js similarity index 99% rename from examples/chat/src/peer-id-dialer.json rename to examples/chat/src/peer-id-dialer.js index 5716d74baf..4c5f00dedb 100644 --- a/examples/chat/src/peer-id-dialer.json +++ b/examples/chat/src/peer-id-dialer.js @@ -1,4 +1,4 @@ -{ +export default { "id": "Qma3GsJmB47xYuyahPZPSadh1avvxfyYQwk8R3UnFrQ6aP", "privKey": "CAASpwkwggSjAgEAAoIBAQCaNSDOjPz6T8HZsf7LDpxiQRiN2OjeyIHUS05p8QWOr3EFUCFsC31R4moihE5HN+FxNalUyyFZU//yjf1pdnlMJqrVByJSMa+y2y4x2FucpoCAO97Tx+iWzwlZ2UXEUXM1Y81mhPbeWXy+wP2xElTgIER0Tsn/thoA0SD2u9wJuVvM7dB7cBcHYmqV6JH+KWCedRTum6O1BssqP/4Lbm2+rkrbZ4+oVRoU2DRLoFhKqwqLtylrbuj4XOI3XykMXV5+uQXz1JzubNOB9lsc6K+eRC+w8hhhDuFMgzkZ4qomCnx3uhO67KaICd8yqqBa6PJ/+fBM5Xk4hjyR40bwcf41AgMBAAECggEAZnrCJ6IYiLyyRdr9SbKXCNDb4YByGYPEi/HT1aHgIJfFE1PSMjxcdytxfyjP4JJpVtPjiT9JFVU2ddoYu5qJN6tGwjVwgJEWg1UXmPaAw1T/drjS94kVsAs82qICtFmwp52Apg3dBZ0Qwq/8qE1XbG7lLyohIbfCBiL0tiPYMfkcsN9gnFT/kFCX0LVs2pa9fHCRMY9rqCc4/rWJa1w8sMuQ23y4lDaxKF9OZVvOHFQkbBDrkquWHE4r55fchCz/rJklkPJUNENuncBRu0/2X+p4IKFD1DnttXNwb8j4LPiSlLro1T0hiUr5gO2QmdYwXFF63Q3mjQy0+5I4eNbjjQKBgQDZvZy3gUKS/nQNkYfq9za80uLbIj/cWbO+ZZjXCsj0fNIcQFJcKMBoA7DjJvu2S/lf86/41YHkPdmrLAEQAkJ+5BBNOycjYK9minTEjIMMmZDTXXugZ62wnU6F46uLkgEChTqEP57Y6xwwV+JaEDFEsW5N1eE9lEVX9nGIr4phMwKBgQC1TazLuEt1WBx/iUT83ita7obXqoKNzwsS/MWfY2innzYZKDOqeSYZzLtt9uTtp4X4uLyPbYs0qFYhXLsUYMoGHNN8+NdjoyxCjQRJRBkMtaNR0lc5lVDWl3bTuJovjFCgAr9uqJrmI5OHcCIk/cDpdWb3nWaMihVlePmiTcTy9wKBgQCU0u7c1jKkudqks4XM6a+2HAYGdUBk4cLjLhnrUWnNAcuyl5wzdX8dGPi8KZb+IKuQE8WBNJ2VXVj7kBYh1QmSJVunDflQSvNYCOaKuOeRoxzD+y9Wkca74qkbBmPn/6FFEb7PSZTO+tPHjyodGNgz9XpJJRjQuBk1aDJtlF3m1QKBgE5SAr5ym65SZOU3UGUIOKRsfDW4Q/OsqDUImvpywCgBICaX9lHDShFFHwau7FA52ScL7vDquoMB4UtCOtLfyQYA9995w9oYCCurrVlVIJkb8jSLcADBHw3EmqF1kq3NqJqm9TmBfoDCh52vdCCUufxgKh33kfBOSlXuf7B8dgMbAoGAZ3r0/mBQX6S+s5+xCETMTSNv7TQzxgtURIpVs+ZVr2cMhWhiv+n0Omab9X9Z50se8cWl5lkvx8vn3D/XHHIPrMF6qk7RAXtvReb+PeitNvm0odqjFv0J2qki6fDs0HKwq4kojAXI1Md8Th0eobNjsy21fEEJT7uKMJdovI/SErI=", "pubKey": "CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCaNSDOjPz6T8HZsf7LDpxiQRiN2OjeyIHUS05p8QWOr3EFUCFsC31R4moihE5HN+FxNalUyyFZU//yjf1pdnlMJqrVByJSMa+y2y4x2FucpoCAO97Tx+iWzwlZ2UXEUXM1Y81mhPbeWXy+wP2xElTgIER0Tsn/thoA0SD2u9wJuVvM7dB7cBcHYmqV6JH+KWCedRTum6O1BssqP/4Lbm2+rkrbZ4+oVRoU2DRLoFhKqwqLtylrbuj4XOI3XykMXV5+uQXz1JzubNOB9lsc6K+eRC+w8hhhDuFMgzkZ4qomCnx3uhO67KaICd8yqqBa6PJ/+fBM5Xk4hjyR40bwcf41AgMBAAE=" diff --git a/examples/echo/src/id-l.json b/examples/chat/src/peer-id-listener.js similarity index 99% rename from examples/echo/src/id-l.json rename to examples/chat/src/peer-id-listener.js index 7acb97c8c1..722f95ebf9 100644 --- a/examples/echo/src/id-l.json +++ b/examples/chat/src/peer-id-listener.js @@ -1,4 +1,4 @@ -{ +export default { "id": "QmcrQZ6RJdpYuGvZqD5QEHAv6qX4BrQLJLQPQUrTrzdcgm", "privKey": "CAASqAkwggSkAgEAAoIBAQDLZZcGcbe4urMBVlcHgN0fpBymY+xcr14ewvamG70QZODJ1h9sljlExZ7byLiqRB3SjGbfpZ1FweznwNxWtWpjHkQjTVXeoM4EEgDSNO/Cg7KNlU0EJvgPJXeEPycAZX9qASbVJ6EECQ40VR/7+SuSqsdL1hrmG1phpIju+D64gLyWpw9WEALfzMpH5I/KvdYDW3N4g6zOD2mZNp5y1gHeXINHWzMF596O72/6cxwyiXV1eJ000k1NVnUyrPjXtqWdVLRk5IU1LFpoQoXZU5X1hKj1a2qt/lZfH5eOrF/ramHcwhrYYw1txf8JHXWO/bbNnyemTHAvutZpTNrsWATfAgMBAAECggEAQj0obPnVyjxLFZFnsFLgMHDCv9Fk5V5bOYtmxfvcm50us6ye+T8HEYWGUa9RrGmYiLweuJD34gLgwyzE1RwptHPj3tdNsr4NubefOtXwixlWqdNIjKSgPlaGULQ8YF2tm/kaC2rnfifwz0w1qVqhPReO5fypL+0ShyANVD3WN0Fo2ugzrniCXHUpR2sHXSg6K+2+qWdveyjNWog34b7CgpV73Ln96BWae6ElU8PR5AWdMnRaA9ucA+/HWWJIWB3Fb4+6uwlxhu2L50Ckq1gwYZCtGw63q5L4CglmXMfIKnQAuEzazq9T4YxEkp+XDnVZAOgnQGUBYpetlgMmkkh9qQKBgQDvsEs0ThzFLgnhtC2Jy//ZOrOvIAKAZZf/mS08AqWH3L0/Rjm8ZYbLsRcoWU78sl8UFFwAQhMRDBP9G+RPojWVahBL/B7emdKKnFR1NfwKjFdDVaoX5uNvZEKSl9UubbC4WZJ65u/cd5jEnj+w3ir9G8n+P1gp/0yBz02nZXFgSwKBgQDZPQr4HBxZL7Kx7D49ormIlB7CCn2i7mT11Cppn5ifUTrp7DbFJ2t9e8UNk6tgvbENgCKXvXWsmflSo9gmMxeEOD40AgAkO8Pn2R4OYhrwd89dECiKM34HrVNBzGoB5+YsAno6zGvOzLKbNwMG++2iuNXqXTk4uV9GcI8OnU5ZPQKBgCZUGrKSiyc85XeiSGXwqUkjifhHNh8yH8xPwlwGUFIZimnD4RevZI7OEtXw8iCWpX2gg9XGuyXOuKORAkF5vvfVriV4e7c9Ad4Igbj8mQFWz92EpV6NHXGCpuKqRPzXrZrNOA9PPqwSs+s9IxI1dMpk1zhBCOguWx2m+NP79NVhAoGBAI6WSoTfrpu7ewbdkVzTWgQTdLzYNe6jmxDf2ZbKclrf7lNr/+cYIK2Ud5qZunsdBwFdgVcnu/02czeS42TvVBgs8mcgiQc/Uy7yi4/VROlhOnJTEMjlU2umkGc3zLzDgYiRd7jwRDLQmMrYKNyEr02HFKFn3w8kXSzW5I8rISnhAoGBANhchHVtJd3VMYvxNcQb909FiwTnT9kl9pkjhwivx+f8/K8pDfYCjYSBYCfPTM5Pskv5dXzOdnNuCj6Y2H/9m2SsObukBwF0z5Qijgu1DsxvADVIKZ4rzrGb4uSEmM6200qjJ/9U98fVM7rvOraakrhcf9gRwuspguJQnSO9cLj6", "pubKey": "CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLZZcGcbe4urMBVlcHgN0fpBymY+xcr14ewvamG70QZODJ1h9sljlExZ7byLiqRB3SjGbfpZ1FweznwNxWtWpjHkQjTVXeoM4EEgDSNO/Cg7KNlU0EJvgPJXeEPycAZX9qASbVJ6EECQ40VR/7+SuSqsdL1hrmG1phpIju+D64gLyWpw9WEALfzMpH5I/KvdYDW3N4g6zOD2mZNp5y1gHeXINHWzMF596O72/6cxwyiXV1eJ000k1NVnUyrPjXtqWdVLRk5IU1LFpoQoXZU5X1hKj1a2qt/lZfH5eOrF/ramHcwhrYYw1txf8JHXWO/bbNnyemTHAvutZpTNrsWATfAgMBAAE=" diff --git a/examples/chat/src/stream.js b/examples/chat/src/stream.js index 7e883db03c..391ac9868d 100644 --- a/examples/chat/src/stream.js +++ b/examples/chat/src/stream.js @@ -1,15 +1,19 @@ -'use strict' /* eslint-disable no-console */ -const pipe = require('it-pipe') -const lp = require('it-length-prefixed') +import { pipe } from 'it-pipe' +import * as lp from 'it-length-prefixed' +import map from 'it-map' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' -function stdinToStream(stream) { +export function stdinToStream(stream) { // Read utf-8 from stdin process.stdin.setEncoding('utf8') pipe( // Read from stdin (the source) process.stdin, + // Turn strings into buffers + (source) => map(source, (string) => uint8ArrayFromString(string)), // Encode with length prefix (so receiving side knows how much data is coming) lp.encode(), // Write to the stream (the sink) @@ -17,12 +21,14 @@ function stdinToStream(stream) { ) } -function streamToConsole(stream) { +export function streamToConsole(stream) { pipe( // Read from the stream (the source) stream.source, // Decode length-prefixed data lp.decode(), + // Turn buffers into strings + (source) => map(source, (buf) => uint8ArrayToString(buf)), // Sink function async function (source) { // For each chunk of data @@ -33,8 +39,3 @@ function streamToConsole(stream) { } ) } - -module.exports = { - stdinToStream, - streamToConsole -} diff --git a/examples/chat/test.js b/examples/chat/test.js index 0d9b40d489..dd75cb91b7 100644 --- a/examples/chat/test.js +++ b/examples/chat/test.js @@ -1,9 +1,10 @@ -'use strict' +import path from 'path' +import execa from 'execa' +import pDefer from 'p-defer' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { fileURLToPath } from 'url' -const path = require('path') -const execa = require('execa') -const pDefer = require('p-defer') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) function startProcess(name) { return execa('node', [path.join(__dirname, name)], { @@ -12,7 +13,7 @@ function startProcess(name) { }) } -async function test () { +export async function test () { const message = 'test message' let listenerOutput = '' let dialerOutput = '' @@ -73,5 +74,3 @@ async function test () { } }) } - -module.exports = test diff --git a/examples/connection-encryption/1.js b/examples/connection-encryption/1.js index fccb75377f..eafb4ff7e7 100644 --- a/examples/connection-encryption/1.js +++ b/examples/connection-encryption/1.js @@ -1,22 +1,19 @@ -'use strict' - -const Libp2p = require('../..') -const TCP = require('libp2p-tcp') -const Mplex = require('libp2p-mplex') -const { NOISE } = require('@chainsafe/libp2p-noise') - -const pipe = require('it-pipe') +import { createLibp2p } from '../../dist/src/index.js' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { pipe } from 'it-pipe' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' const createNode = async () => { - const node = await Libp2p.create({ + const node = await createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - modules: { - transport: [TCP], - streamMuxer: [Mplex], - connEncryption: [NOISE] - } + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()] }) await node.start() @@ -30,14 +27,14 @@ const createNode = async () => { createNode() ]) - await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs()) node2.handle('/a-protocol', ({ stream }) => { pipe( stream, async function (source) { for await (const msg of source) { - console.log(msg.toString()) + console.log(uint8ArrayToString(msg)) } } ) @@ -46,7 +43,7 @@ const createNode = async () => { const { stream } = await node1.dialProtocol(node2.peerId, '/a-protocol') await pipe( - ['This information is sent out encrypted to the other peer'], + [uint8ArrayFromString('This information is sent out encrypted to the other peer')], stream ) })(); diff --git a/examples/connection-encryption/README.md b/examples/connection-encryption/README.md index ac824bf93c..ccc8cee11e 100644 --- a/examples/connection-encryption/README.md +++ b/examples/connection-encryption/README.md @@ -13,17 +13,17 @@ We will build this example on top of example for [Protocol and Stream Multiplexi To add them to your libp2p configuration, all you have to do is: ```JavaScript -const Libp2p = require('libp2p') -const { NOISE } = require('@chainsafe/libp2p-noise') - -const createNode = () => { - return Libp2p.create({ - modules: { - transport: [ TCP ], - streamMuxer: [ Mplex ], - // Attach noise as the crypto channel to use - connEncryption: [ NOISE ] - } +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' + +const createNode = async () => { + return await createLibp2p({ + transports: [ new TCP() ], + streamMuxers: [ new Mplex() ], + // Attach noise as the crypto channel to use + conectionEncrypters: [ new Noise() ] }) } ``` diff --git a/examples/connection-encryption/test.js b/examples/connection-encryption/test.js index b6ea10ad5d..8111be9c1e 100644 --- a/examples/connection-encryption/test.js +++ b/examples/connection-encryption/test.js @@ -1,14 +1,14 @@ -'use strict' -const path = require('path') -const { waitForOutput } = require('../utils') +import path from 'path' +import { waitForOutput } from '../utils.js' +import { fileURLToPath } from 'url' -async function test () { +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +export async function test () { process.stdout.write('1.js\n') await waitForOutput('This information is sent out encrypted to the other peer', 'node', [path.join(__dirname, '1.js')], { cwd: __dirname }) } - -module.exports = test diff --git a/examples/delegated-routing/package.json b/examples/delegated-routing/package.json index 6893edc3c4..acdbb03590 100644 --- a/examples/delegated-routing/package.json +++ b/examples/delegated-routing/package.json @@ -3,19 +3,18 @@ "version": "0.1.0", "private": true, "dependencies": { - "@chainsafe/libp2p-noise": "^5.0.2", - "ipfs-core": "^0.13.0", + "@chainsafe/libp2p-noise": "^6.0.1", + "ipfs-core": "^0.14.1", "libp2p": "../../", - "libp2p-delegated-content-routing": "^0.11.0", - "libp2p-delegated-peer-routing": "^0.11.1", - "libp2p-kad-dht": "^0.28.6", - "libp2p-mplex": "^0.10.4", - "libp2p-webrtc-star": "^0.25.0", - "libp2p-websocket-star": "^0.10.2", - "libp2p-websockets": "^0.16.2", - "react": "^16.8.6", - "react-dom": "^16.8.6", - "react-scripts": "2.1.8" + "@libp2p/delegated-content-routing": "^1.0.1", + "@libp2p/delegated-peer-routing": "^1.0.1", + "@libp2p/kad-dht": "^1.0.1", + "@libp2p/mplex": "^1.0.2", + "@libp2p/webrtc-star": "^1.0.6", + "@libp2p/websockets": "^1.0.3", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-scripts": "5.0.0" }, "scripts": { "start": "react-scripts start" diff --git a/examples/delegated-routing/src/libp2p-bundle.js b/examples/delegated-routing/src/libp2p-bundle.js index 0ff6786eff..515c2d1c3c 100644 --- a/examples/delegated-routing/src/libp2p-bundle.js +++ b/examples/delegated-routing/src/libp2p-bundle.js @@ -1,26 +1,23 @@ // eslint-disable-next-line 'use strict' -const Libp2p = require('libp2p') -const Websockets = require('libp2p-websockets') -const WebSocketStar = require('libp2p-websocket-star') -const WebRTCStar = require('libp2p-webrtc-star') -const MPLEX = require('libp2p-mplex') -const { NOISE } = require('@chainsafe/libp2p-noise') -const KadDHT = require('libp2p-kad-dht') -const DelegatedPeerRouter = require('libp2p-delegated-peer-routing') -const DelegatedContentRouter = require('libp2p-delegated-content-routing') +import { createLibp2p } from 'libp2p' +import { WebSockets } from '@libp2p/websockets' +import { WebRTCStar } from '@libp2p/webrtc-star' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { DelegatedPeerRouting } from '@libp2p/delegated-peer-routing' +import { DelegatedContentRouting } from '@libp2p/delegated-content-routing' export default function Libp2pBundle ({peerInfo, peerBook}) { - const wrtcstar = new WebRTCStar({id: peerInfo.id}) - const wsstar = new WebSocketStar({id: peerInfo.id}) + const wrtcstar = new WebRTCStar() const delegatedApiOptions = { host: '0.0.0.0', protocol: 'http', port: '8080' } - return new Libp2p({ + return createLibp2p({ peerInfo, peerBook, // Lets limit the connection managers peers and have it check peer health less frequently @@ -28,48 +25,29 @@ export default function Libp2pBundle ({peerInfo, peerBook}) { maxPeers: 10, pollInterval: 5000 }, - modules: { - contentRouting: [ - new DelegatedContentRouter(peerInfo.id, delegatedApiOptions) - ], - peerRouting: [ - new DelegatedPeerRouter(delegatedApiOptions) - ], - peerDiscovery: [ - wrtcstar.discovery, - wsstar.discovery - ], - transport: [ - wrtcstar, - wsstar, - Websockets - ], - streamMuxer: [ - MPLEX - ], - connEncryption: [ - NOISE - ], - dht: KadDHT + contentRouting: [ + new DelegatedPeerRouting(peerInfo.id, delegatedApiOptions) + ], + peerRouting: [ + new DelegatedContentRouting(delegatedApiOptions) + ], + transports: [ + wrtcstar, + new WebSockets() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + new Noise() + ], + connectionManager: { + autoDial: false }, - config: { - peerDiscovery: { - autoDial: false, - webrtcStar: { - enabled: false - }, - websocketStar: { - enabled: false - } - }, - dht: { + relay: { + enabled: true, + hop: { enabled: false - }, - relay: { - enabled: true, - hop: { - enabled: false - } } } }) diff --git a/examples/discovery-mechanisms/1.js b/examples/discovery-mechanisms/1.js index 8ac3cc489c..6b099bbf2c 100644 --- a/examples/discovery-mechanisms/1.js +++ b/examples/discovery-mechanisms/1.js @@ -1,43 +1,37 @@ /* eslint-disable no-console */ -'use strict' -const Libp2p = require('../../') -const TCP = require('libp2p-tcp') -const Mplex = require('libp2p-mplex') -const { NOISE } = require('@chainsafe/libp2p-noise') -const Bootstrap = require('libp2p-bootstrap') - -const bootstrapers = require('./bootstrapers') +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { Bootstrap } from '@libp2p/bootstrap' +import bootstrapers from './bootstrappers.js' ;(async () => { - const node = await Libp2p.create({ + const node = await createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - modules: { - transport: [TCP], - streamMuxer: [Mplex], - connEncryption: [NOISE], - peerDiscovery: [Bootstrap] - }, - config: { - peerDiscovery: { - bootstrap: { - interval: 60e3, - enabled: true, - list: bootstrapers - } - } - } + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()], + peerDiscovery: [ + new Bootstrap({ + interval: 60e3, + list: bootstrapers + }) + ] }) - node.connectionManager.on('peer:connect', (connection) => { - console.log('Connection established to:', connection.remotePeer.toB58String()) // Emitted when a peer has been found + node.connectionManager.addEventListener('peer:connect', (evt) => { + const connection = evt.detail + console.log('Connection established to:', connection.remotePeer.toString()) // Emitted when a peer has been found }) - node.on('peer:discovery', (peerId) => { + node.addEventListener('peer:discovery', (evt) => { + const peer = evt.detail // No need to dial, autoDial is on - console.log('Discovered:', peerId.toB58String()) + console.log('Discovered:', peer.id.toString()) }) await node.start() diff --git a/examples/discovery-mechanisms/2.js b/examples/discovery-mechanisms/2.js index bd5f8ddc2b..bad5a13f50 100644 --- a/examples/discovery-mechanisms/2.js +++ b/examples/discovery-mechanisms/2.js @@ -1,31 +1,30 @@ /* eslint-disable no-console */ -'use strict' -const Libp2p = require('../../') -const TCP = require('libp2p-tcp') -const Mplex = require('libp2p-mplex') -const { NOISE } = require('@chainsafe/libp2p-noise') -const MulticastDNS = require('libp2p-mdns') +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { MulticastDNS } from '@libp2p/mdns' const createNode = async () => { - const node = await Libp2p.create({ + const node = await createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - modules: { - transport: [TCP], - streamMuxer: [Mplex], - connEncryption: [NOISE], - peerDiscovery: [MulticastDNS] - }, - config: { - peerDiscovery: { - [MulticastDNS.tag]: { - interval: 20e3, - enabled: true - } - } - } + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + new Noise() + ], + peerDiscovery: [ + new MulticastDNS({ + interval: 20e3 + }) + ] }) return node @@ -37,8 +36,8 @@ const createNode = async () => { createNode() ]) - node1.on('peer:discovery', (peerId) => console.log('Discovered:', peerId.toB58String())) - node2.on('peer:discovery', (peerId) => console.log('Discovered:', peerId.toB58String())) + node1.addEventListener('peer:discovery', (evt) => console.log('Discovered:', evt.detail.id.toString())) + node2.addEventListener('peer:discovery', (evt) => console.log('Discovered:', evt.detail.id.toString())) await Promise.all([ node1.start(), diff --git a/examples/discovery-mechanisms/3.js b/examples/discovery-mechanisms/3.js index 645025d4d5..5cf6522bf8 100644 --- a/examples/discovery-mechanisms/3.js +++ b/examples/discovery-mechanisms/3.js @@ -1,66 +1,78 @@ /* eslint-disable no-console */ -'use strict' -const Libp2p = require('../../') -const TCP = require('libp2p-tcp') -const Mplex = require('libp2p-mplex') -const { NOISE } = require('@chainsafe/libp2p-noise') -const Gossipsub = require('@achingbrain/libp2p-gossipsub') -const Bootstrap = require('libp2p-bootstrap') -const PubsubPeerDiscovery = require('libp2p-pubsub-peer-discovery') +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { Gossipsub } from '@achingbrain/libp2p-gossipsub' +import { Bootstrap } from '@libp2p/bootstrap' +import { PubSubPeerDiscovery } from '@libp2p/pubsub-peer-discovery' -const createRelayServer = require('libp2p-relay-server') - -const createNode = async (bootstrapers) => { - const node = await Libp2p.create({ +const createNode = async (bootstrappers) => { + const node = await createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - modules: { - transport: [TCP], - streamMuxer: [Mplex], - connEncryption: [NOISE], - pubsub: Gossipsub, - peerDiscovery: [Bootstrap, PubsubPeerDiscovery] - }, - config: { - peerDiscovery: { - [PubsubPeerDiscovery.tag]: { - interval: 1000, - enabled: true - }, - [Bootstrap.tag]: { - enabled: true, - list: bootstrapers - } - } - } + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()], + pubsub: new Gossipsub(), + peerDiscovery: [ + new Bootstrap({ + list: bootstrappers + }), + new PubSubPeerDiscovery({ + interval: 1000 + }) + ] }) return node } ;(async () => { - const relay = await createRelayServer({ - listenAddresses: ['/ip4/0.0.0.0/tcp/0'] + const relay = await createLibp2p({ + addresses: { + listen: [ + '/ip4/0.0.0.0/tcp/0' + ] + }, + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()], + pubsub: new Gossipsub(), + peerDiscovery: [ + new PubSubPeerDiscovery({ + interval: 1000 + }) + ], + relay: { + 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 + } + } }) - console.log(`libp2p relay starting with id: ${relay.peerId.toB58String()}`) + console.log(`libp2p relay starting with id: ${relay.peerId.toString()}`) await relay.start() - const relayMultiaddrs = relay.multiaddrs.map((m) => `${m.toString()}/p2p/${relay.peerId.toB58String()}`) + + const relayMultiaddrs = relay.getMultiaddrs().map((m) => m.toString()) const [node1, node2] = await Promise.all([ createNode(relayMultiaddrs), createNode(relayMultiaddrs) ]) - node1.on('peer:discovery', (peerId) => { - console.log(`Peer ${node1.peerId.toB58String()} discovered: ${peerId.toB58String()}`) + node1.addEventListener('peer:discovery', (evt) => { + const peer = evt.detail + console.log(`Peer ${node1.peerId.toString()} discovered: ${peer.id.toString()}`) }) - node2.on('peer:discovery', (peerId) => { - console.log(`Peer ${node2.peerId.toB58String()} discovered: ${peerId.toB58String()}`) + node2.addEventListener('peer:discovery',(evt) => { + const peer = evt.detail + console.log(`Peer ${node2.peerId.toString()} discovered: ${peer.id.toString()}`) }) - ;[node1, node2].forEach((node, index) => console.log(`Node ${index} starting with id: ${node.peerId.toB58String()}`)) + ;[node1, node2].forEach((node, index) => console.log(`Node ${index} starting with id: ${node.peerId.toString()}`)) await Promise.all([ node1.start(), node2.start() diff --git a/examples/discovery-mechanisms/README.md b/examples/discovery-mechanisms/README.md index 6d5c647e55..b7d245184d 100644 --- a/examples/discovery-mechanisms/README.md +++ b/examples/discovery-mechanisms/README.md @@ -13,25 +13,25 @@ For this demo, we will connect to IPFS default bootstrapper nodes and so, we wil First, we create our libp2p node. ```JavaScript -const Libp2p = require('libp2p') -const Bootstrap = require('libp2p-bootstrap') - -const node = await Libp2p.create({ - modules: { - transport: [ TCP ], - streamMuxer: [ Mplex ], - connEncryption: [ NOISE ], - peerDiscovery: [ Bootstrap ] - }, - config: { - peerDiscovery: { - bootstrap: { - interval: 60e3, - enabled: true, - list: bootstrapers - } - } - } +import { createLibp2p } from 'libp2p' +import { Bootstrap } from '@libp2p/bootstrap' + +const node = await createLibp2p({ + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + new Noise() + ], + peerDiscovery: [ + new Bootstrap({ + interval: 60e3, + list: bootstrapers + }) + ] }) ``` @@ -51,26 +51,26 @@ const bootstrapers = [ Now, once we create and start the node, we can listen for events such as `peer:discovery` and `peer:connect`, these events tell us when we found a peer, independently of the discovery mechanism used and when we actually dialed to that peer. ```JavaScript -const node = await Libp2p.create({ +const node = await createLibp2p({ peerId, addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - modules: { - transport: [ TCP ], - streamMuxer: [ Mplex ], - connEncryption: [ NOISE ], - peerDiscovery: [ Bootstrap ] - }, - config: { - peerDiscovery: { - bootstrap: { - interval: 60e3, - enabled: true, - list: bootstrapers - } - } - } + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + new Noise() + ], + peerDiscovery: [ + new Bootstrap({ + interval: 60e3, + list: bootstrapers + }) + ] }) node.connectionManager.on('peer:connect', (connection) => { @@ -110,28 +110,28 @@ For this example, we need `libp2p-mdns`, go ahead and `npm install` it. You can Update your libp2p configuration to include MulticastDNS. ```JavaScript -const Libp2p = require('libp2p') -const MulticastDNS = require('libp2p-mdns') +import { createLibp2p } from 'libp2p' +import { MulticastDNS } from '@libp2p/mdns' const createNode = () => { return Libp2p.create({ addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - modules: { - transport: [ TCP ], - streamMuxer: [ Mplex ], - connEncryption: [ NOISE ], - peerDiscovery: [ MulticastDNS ] - }, - config: { - peerDiscovery: { - mdns: { - interval: 20e3, - enabled: true - } - } - } + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + new Noise() + ], + peerDiscovery: [ + new MulticastDNS({ + interval: 20e3 + }) + ] }) } ``` @@ -170,39 +170,37 @@ In the context of this example, we will create and run the `libp2p-relay-server` You can create your libp2p nodes as follows: ```js -const Libp2p = require('libp2p') -const TCP = require('libp2p-tcp') -const Mplex = require('libp2p-mplex') -const { NOISE } = require('@chainsafe/libp2p-noise') -const Gossipsub = require('libp2p-gossipsub') -const Bootstrap = require('libp2p-bootstrap') -const PubsubPeerDiscovery = require('libp2p-pubsub-peer-discovery') +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { Gossipsub } from 'libp2p-gossipsub' +import { Bootstrap } from '@libp2p/bootstrap' +const PubsubPeerDiscovery from 'libp2p-pubsub-peer-discovery') const createNode = async (bootstrapers) => { - const node = await Libp2p.create({ + const node = await createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - modules: { - transport: [TCP], - streamMuxer: [Mplex], - connEncryption: [NOISE], - pubsub: Gossipsub, - peerDiscovery: [Bootstrap, PubsubPeerDiscovery] - }, - config: { - peerDiscovery: { - [PubsubPeerDiscovery.tag]: { - interval: 1000, - enabled: true - }, - [Bootstrap.tag]: { - enabled: true, - list: bootstrapers - } - } - } - }) + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + new Noise() + ], + peerDiscovery: [ + new Bootstrap({ + interval: 60e3, + list: bootstrapers + }), + new PubsubPeerDiscovery({ + interval: 1000 + }) + ]) return node } @@ -212,7 +210,9 @@ We will use the `libp2p-relay-server` as bootstrap nodes for the libp2p nodes, s ```js const relay = await createRelayServer({ - listenAddresses: ['/ip4/0.0.0.0/tcp/0'] + addresses: { + listen: ['/ip4/0.0.0.0/tcp/0'] + } }) console.log(`libp2p relay starting with id: ${relay.peerId.toB58String()}`) await relay.start() diff --git a/examples/discovery-mechanisms/bootstrapers.js b/examples/discovery-mechanisms/bootstrappers.js similarity index 90% rename from examples/discovery-mechanisms/bootstrapers.js rename to examples/discovery-mechanisms/bootstrappers.js index 8ebedb1f93..7f88820eb3 100644 --- a/examples/discovery-mechanisms/bootstrapers.js +++ b/examples/discovery-mechanisms/bootstrappers.js @@ -1,7 +1,5 @@ -'use strict' - // Find this list at: https://github.com/ipfs/js-ipfs/blob/master/packages/ipfs-core-config/src/config.js -const bootstrapers = [ +export default [ '/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ', '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', '/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb', @@ -9,5 +7,3 @@ const bootstrapers = [ '/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa', '/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt', ] - -module.exports = bootstrapers diff --git a/examples/discovery-mechanisms/test-1.js b/examples/discovery-mechanisms/test-1.js index 73e90ad54c..554f2c2e04 100644 --- a/examples/discovery-mechanisms/test-1.js +++ b/examples/discovery-mechanisms/test-1.js @@ -1,14 +1,13 @@ -'use strict' +import path from 'path' +import { waitForOutput } from '../utils.js' +import { fileURLToPath } from 'url' -const path = require('path') -const { waitForOutput } = require('../utils') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) -async function test () { +export async function test () { process.stdout.write('1.js\n') await waitForOutput('Connection established to:', 'node', [path.join(__dirname, '1.js')], { cwd: __dirname }) } - -module.exports = test diff --git a/examples/discovery-mechanisms/test-2.js b/examples/discovery-mechanisms/test-2.js index 86183c81d5..b40da4a332 100644 --- a/examples/discovery-mechanisms/test-2.js +++ b/examples/discovery-mechanisms/test-2.js @@ -1,14 +1,13 @@ -'use strict' +import path from 'path' +import execa from 'execa' +import pWaitFor from 'p-wait-for' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { fileURLToPath } from 'url' -const path = require('path') -const execa = require('execa') -const pWaitFor = require('p-wait-for') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) -const discoveredCopy = 'Discovered:' - -async function test() { - const discoveredNodes = [] +export async function test () { + let discoveredNodes = 0 process.stdout.write('2.js\n') @@ -19,17 +18,16 @@ async function test() { proc.all.on('data', async (data) => { process.stdout.write(data) - const line = uint8ArrayToString(data) + const str = uint8ArrayToString(data) - if (line.includes(discoveredCopy)) { - const id = line.trim().split(discoveredCopy)[1] - discoveredNodes.push(id) - } + str.split('\n').forEach(line => { + if (line.includes('Discovered:')) { + discoveredNodes++ + } + }) }) - await pWaitFor(() => discoveredNodes.length === 2) + await pWaitFor(() => discoveredNodes > 1) proc.kill() } - -module.exports = test diff --git a/examples/discovery-mechanisms/test-3.js b/examples/discovery-mechanisms/test-3.js index f73744cd7e..375ef6f8ab 100644 --- a/examples/discovery-mechanisms/test-3.js +++ b/examples/discovery-mechanisms/test-3.js @@ -1,14 +1,13 @@ -'use strict' +import path from 'path' +import execa from 'execa' +import pWaitFor from 'p-wait-for' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { fileURLToPath } from 'url' -const path = require('path') -const execa = require('execa') -const pWaitFor = require('p-wait-for') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) -const discoveredCopy = 'discovered:' - -async function test() { - let discoverCount = 0 +export async function test () { + let discoveredNodes = 0 process.stdout.write('3.js\n') @@ -19,17 +18,16 @@ async function test() { proc.all.on('data', async (data) => { process.stdout.write(data) - const line = uint8ArrayToString(data) + const str = uint8ArrayToString(data) - // Discovered or Connected - if (line.includes(discoveredCopy)) { - discoverCount++ - } + str.split('\n').forEach(line => { + if (line.includes('discovered:')) { + discoveredNodes++ + } + }) }) - await pWaitFor(() => discoverCount === 4) + await pWaitFor(() => discoveredNodes > 3) proc.kill() } - -module.exports = test diff --git a/examples/discovery-mechanisms/test.js b/examples/discovery-mechanisms/test.js index d9faeb2e9e..79f41f9457 100644 --- a/examples/discovery-mechanisms/test.js +++ b/examples/discovery-mechanisms/test.js @@ -1,13 +1,9 @@ -'use strict' +import { test as test1 } from './test-1.js' +import { test as test2 } from './test-2.js' +import { test as test3 } from './test-3.js' -const test1 = require('./test-1') -const test2 = require('./test-2') -const test3 = require('./test-3') - -async function test () { +export async function test () { await test1() await test2() await test3() } - -module.exports = test diff --git a/examples/echo/src/dialer.js b/examples/echo/src/dialer.js index 5938760783..74a6508798 100644 --- a/examples/echo/src/dialer.js +++ b/examples/echo/src/dialer.js @@ -1,18 +1,21 @@ -'use strict' /* eslint-disable no-console */ /* * Dialer Node */ -const PeerId = require('peer-id') -const createLibp2p = require('./libp2p') -const pipe = require('it-pipe') +import { createLibp2p } from './libp2p.js' +import { pipe } from 'it-pipe' +import idd from './id-d.js' +import idl from './id-l.js' +import { createFromJSON } from '@libp2p/peer-id-factory' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' async function run() { const [dialerId, listenerId] = await Promise.all([ - PeerId.createFromJSON(require('./id-d')), - PeerId.createFromJSON(require('./id-l')) + createFromJSON(idd), + createFromJSON(idl) ]) // Dialer @@ -24,14 +27,13 @@ async function run() { }) // Add peer to Dial (the listener) into the PeerStore - const listenerMultiaddr = '/ip4/127.0.0.1/tcp/10333/p2p/' + listenerId.toB58String() + const listenerMultiaddr = '/ip4/127.0.0.1/tcp/10333/p2p/' + listenerId.toString() // Start the dialer libp2p node await dialerNode.start() console.log('Dialer ready, listening on:') - dialerNode.multiaddrs.forEach((ma) => console.log(ma.toString() + - '/p2p/' + dialerId.toB58String())) + dialerNode.getMultiaddrs().forEach((ma) => console.log(ma.toString())) // Dial the listener node console.log('Dialing to peer:', listenerMultiaddr) @@ -41,7 +43,7 @@ async function run() { pipe( // Source data - ['hey'], + [uint8ArrayFromString('hey')], // Write to the stream, and pass its output to the next function stream, // Sink function @@ -49,7 +51,7 @@ async function run() { // For each chunk of data for await (const data of source) { // Output the data - console.log('received echo:', data.toString()) + console.log('received echo:', uint8ArrayToString(data)) } } ) diff --git a/examples/echo/src/id-d.json b/examples/echo/src/id-d.js similarity index 99% rename from examples/echo/src/id-d.json rename to examples/echo/src/id-d.js index 5716d74baf..4c5f00dedb 100644 --- a/examples/echo/src/id-d.json +++ b/examples/echo/src/id-d.js @@ -1,4 +1,4 @@ -{ +export default { "id": "Qma3GsJmB47xYuyahPZPSadh1avvxfyYQwk8R3UnFrQ6aP", "privKey": "CAASpwkwggSjAgEAAoIBAQCaNSDOjPz6T8HZsf7LDpxiQRiN2OjeyIHUS05p8QWOr3EFUCFsC31R4moihE5HN+FxNalUyyFZU//yjf1pdnlMJqrVByJSMa+y2y4x2FucpoCAO97Tx+iWzwlZ2UXEUXM1Y81mhPbeWXy+wP2xElTgIER0Tsn/thoA0SD2u9wJuVvM7dB7cBcHYmqV6JH+KWCedRTum6O1BssqP/4Lbm2+rkrbZ4+oVRoU2DRLoFhKqwqLtylrbuj4XOI3XykMXV5+uQXz1JzubNOB9lsc6K+eRC+w8hhhDuFMgzkZ4qomCnx3uhO67KaICd8yqqBa6PJ/+fBM5Xk4hjyR40bwcf41AgMBAAECggEAZnrCJ6IYiLyyRdr9SbKXCNDb4YByGYPEi/HT1aHgIJfFE1PSMjxcdytxfyjP4JJpVtPjiT9JFVU2ddoYu5qJN6tGwjVwgJEWg1UXmPaAw1T/drjS94kVsAs82qICtFmwp52Apg3dBZ0Qwq/8qE1XbG7lLyohIbfCBiL0tiPYMfkcsN9gnFT/kFCX0LVs2pa9fHCRMY9rqCc4/rWJa1w8sMuQ23y4lDaxKF9OZVvOHFQkbBDrkquWHE4r55fchCz/rJklkPJUNENuncBRu0/2X+p4IKFD1DnttXNwb8j4LPiSlLro1T0hiUr5gO2QmdYwXFF63Q3mjQy0+5I4eNbjjQKBgQDZvZy3gUKS/nQNkYfq9za80uLbIj/cWbO+ZZjXCsj0fNIcQFJcKMBoA7DjJvu2S/lf86/41YHkPdmrLAEQAkJ+5BBNOycjYK9minTEjIMMmZDTXXugZ62wnU6F46uLkgEChTqEP57Y6xwwV+JaEDFEsW5N1eE9lEVX9nGIr4phMwKBgQC1TazLuEt1WBx/iUT83ita7obXqoKNzwsS/MWfY2innzYZKDOqeSYZzLtt9uTtp4X4uLyPbYs0qFYhXLsUYMoGHNN8+NdjoyxCjQRJRBkMtaNR0lc5lVDWl3bTuJovjFCgAr9uqJrmI5OHcCIk/cDpdWb3nWaMihVlePmiTcTy9wKBgQCU0u7c1jKkudqks4XM6a+2HAYGdUBk4cLjLhnrUWnNAcuyl5wzdX8dGPi8KZb+IKuQE8WBNJ2VXVj7kBYh1QmSJVunDflQSvNYCOaKuOeRoxzD+y9Wkca74qkbBmPn/6FFEb7PSZTO+tPHjyodGNgz9XpJJRjQuBk1aDJtlF3m1QKBgE5SAr5ym65SZOU3UGUIOKRsfDW4Q/OsqDUImvpywCgBICaX9lHDShFFHwau7FA52ScL7vDquoMB4UtCOtLfyQYA9995w9oYCCurrVlVIJkb8jSLcADBHw3EmqF1kq3NqJqm9TmBfoDCh52vdCCUufxgKh33kfBOSlXuf7B8dgMbAoGAZ3r0/mBQX6S+s5+xCETMTSNv7TQzxgtURIpVs+ZVr2cMhWhiv+n0Omab9X9Z50se8cWl5lkvx8vn3D/XHHIPrMF6qk7RAXtvReb+PeitNvm0odqjFv0J2qki6fDs0HKwq4kojAXI1Md8Th0eobNjsy21fEEJT7uKMJdovI/SErI=", "pubKey": "CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCaNSDOjPz6T8HZsf7LDpxiQRiN2OjeyIHUS05p8QWOr3EFUCFsC31R4moihE5HN+FxNalUyyFZU//yjf1pdnlMJqrVByJSMa+y2y4x2FucpoCAO97Tx+iWzwlZ2UXEUXM1Y81mhPbeWXy+wP2xElTgIER0Tsn/thoA0SD2u9wJuVvM7dB7cBcHYmqV6JH+KWCedRTum6O1BssqP/4Lbm2+rkrbZ4+oVRoU2DRLoFhKqwqLtylrbuj4XOI3XykMXV5+uQXz1JzubNOB9lsc6K+eRC+w8hhhDuFMgzkZ4qomCnx3uhO67KaICd8yqqBa6PJ/+fBM5Xk4hjyR40bwcf41AgMBAAE=" diff --git a/examples/chat/src/peer-id-listener.json b/examples/echo/src/id-l.js similarity index 99% rename from examples/chat/src/peer-id-listener.json rename to examples/echo/src/id-l.js index 7acb97c8c1..722f95ebf9 100644 --- a/examples/chat/src/peer-id-listener.json +++ b/examples/echo/src/id-l.js @@ -1,4 +1,4 @@ -{ +export default { "id": "QmcrQZ6RJdpYuGvZqD5QEHAv6qX4BrQLJLQPQUrTrzdcgm", "privKey": "CAASqAkwggSkAgEAAoIBAQDLZZcGcbe4urMBVlcHgN0fpBymY+xcr14ewvamG70QZODJ1h9sljlExZ7byLiqRB3SjGbfpZ1FweznwNxWtWpjHkQjTVXeoM4EEgDSNO/Cg7KNlU0EJvgPJXeEPycAZX9qASbVJ6EECQ40VR/7+SuSqsdL1hrmG1phpIju+D64gLyWpw9WEALfzMpH5I/KvdYDW3N4g6zOD2mZNp5y1gHeXINHWzMF596O72/6cxwyiXV1eJ000k1NVnUyrPjXtqWdVLRk5IU1LFpoQoXZU5X1hKj1a2qt/lZfH5eOrF/ramHcwhrYYw1txf8JHXWO/bbNnyemTHAvutZpTNrsWATfAgMBAAECggEAQj0obPnVyjxLFZFnsFLgMHDCv9Fk5V5bOYtmxfvcm50us6ye+T8HEYWGUa9RrGmYiLweuJD34gLgwyzE1RwptHPj3tdNsr4NubefOtXwixlWqdNIjKSgPlaGULQ8YF2tm/kaC2rnfifwz0w1qVqhPReO5fypL+0ShyANVD3WN0Fo2ugzrniCXHUpR2sHXSg6K+2+qWdveyjNWog34b7CgpV73Ln96BWae6ElU8PR5AWdMnRaA9ucA+/HWWJIWB3Fb4+6uwlxhu2L50Ckq1gwYZCtGw63q5L4CglmXMfIKnQAuEzazq9T4YxEkp+XDnVZAOgnQGUBYpetlgMmkkh9qQKBgQDvsEs0ThzFLgnhtC2Jy//ZOrOvIAKAZZf/mS08AqWH3L0/Rjm8ZYbLsRcoWU78sl8UFFwAQhMRDBP9G+RPojWVahBL/B7emdKKnFR1NfwKjFdDVaoX5uNvZEKSl9UubbC4WZJ65u/cd5jEnj+w3ir9G8n+P1gp/0yBz02nZXFgSwKBgQDZPQr4HBxZL7Kx7D49ormIlB7CCn2i7mT11Cppn5ifUTrp7DbFJ2t9e8UNk6tgvbENgCKXvXWsmflSo9gmMxeEOD40AgAkO8Pn2R4OYhrwd89dECiKM34HrVNBzGoB5+YsAno6zGvOzLKbNwMG++2iuNXqXTk4uV9GcI8OnU5ZPQKBgCZUGrKSiyc85XeiSGXwqUkjifhHNh8yH8xPwlwGUFIZimnD4RevZI7OEtXw8iCWpX2gg9XGuyXOuKORAkF5vvfVriV4e7c9Ad4Igbj8mQFWz92EpV6NHXGCpuKqRPzXrZrNOA9PPqwSs+s9IxI1dMpk1zhBCOguWx2m+NP79NVhAoGBAI6WSoTfrpu7ewbdkVzTWgQTdLzYNe6jmxDf2ZbKclrf7lNr/+cYIK2Ud5qZunsdBwFdgVcnu/02czeS42TvVBgs8mcgiQc/Uy7yi4/VROlhOnJTEMjlU2umkGc3zLzDgYiRd7jwRDLQmMrYKNyEr02HFKFn3w8kXSzW5I8rISnhAoGBANhchHVtJd3VMYvxNcQb909FiwTnT9kl9pkjhwivx+f8/K8pDfYCjYSBYCfPTM5Pskv5dXzOdnNuCj6Y2H/9m2SsObukBwF0z5Qijgu1DsxvADVIKZ4rzrGb4uSEmM6200qjJ/9U98fVM7rvOraakrhcf9gRwuspguJQnSO9cLj6", "pubKey": "CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLZZcGcbe4urMBVlcHgN0fpBymY+xcr14ewvamG70QZODJ1h9sljlExZ7byLiqRB3SjGbfpZ1FweznwNxWtWpjHkQjTVXeoM4EEgDSNO/Cg7KNlU0EJvgPJXeEPycAZX9qASbVJ6EECQ40VR/7+SuSqsdL1hrmG1phpIju+D64gLyWpw9WEALfzMpH5I/KvdYDW3N4g6zOD2mZNp5y1gHeXINHWzMF596O72/6cxwyiXV1eJ000k1NVnUyrPjXtqWdVLRk5IU1LFpoQoXZU5X1hKj1a2qt/lZfH5eOrF/ramHcwhrYYw1txf8JHXWO/bbNnyemTHAvutZpTNrsWATfAgMBAAE=" diff --git a/examples/echo/src/libp2p.js b/examples/echo/src/libp2p.js index 6bc1d7293e..b875da7238 100644 --- a/examples/echo/src/libp2p.js +++ b/examples/echo/src/libp2p.js @@ -1,23 +1,23 @@ -'use strict' +import { TCP } from '@libp2p/tcp' +import { WebSockets } from '@libp2p/websockets' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import defaultsDeep from '@nodeutils/defaults-deep' +import { createLibp2p as createNode } from 'libp2p' -const TCP = require('libp2p-tcp') -const WS = require('libp2p-websockets') -const mplex = require('libp2p-mplex') -const { NOISE } = require('@chainsafe/libp2p-noise') - -const defaultsDeep = require('@nodeutils/defaults-deep') -const libp2p = require('../../..') - -async function createLibp2p(_options) { +export async function createLibp2p(_options) { const defaults = { - modules: { - transport: [TCP, WS], - streamMuxer: [mplex], - connEncryption: [NOISE], - }, + transports: [ + new TCP(), + new WebSockets() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + new Noise() + ] } - return libp2p.create(defaultsDeep(_options, defaults)) + return createNode(defaultsDeep(_options, defaults)) } - -module.exports = createLibp2p diff --git a/examples/echo/src/listener.js b/examples/echo/src/listener.js index 1f814fc3e0..242c04aea8 100644 --- a/examples/echo/src/listener.js +++ b/examples/echo/src/listener.js @@ -1,16 +1,16 @@ -'use strict' /* eslint-disable no-console */ /* * Listener Node */ -const PeerId = require('peer-id') -const createLibp2p = require('./libp2p') -const pipe = require('it-pipe') +import { createLibp2p } from './libp2p.js' +import { pipe } from 'it-pipe' +import { createFromJSON } from '@libp2p/peer-id-factory' +import idl from './id-l.js' async function run() { - const listenerId = await PeerId.createFromJSON(require('./id-l')) + const listenerId = await createFromJSON(idl) // Listener libp2p node const listenerNode = await createLibp2p({ @@ -21,8 +21,9 @@ async function run() { }) // Log a message when we receive a connection - listenerNode.connectionManager.on('peer:connect', (connection) => { - console.log('received dial to me from:', connection.remotePeer.toB58String()) + listenerNode.connectionManager.addEventListener('peer:connect', (evt) => { + const connection = evt.detail + console.log('received dial to me from:', connection.remotePeer.toString()) }) // Handle incoming connections for the protocol by piping from the stream @@ -33,8 +34,8 @@ async function run() { await listenerNode.start() console.log('Listener ready, listening on:') - listenerNode.multiaddrs.forEach((ma) => { - console.log(ma.toString() + '/p2p/' + listenerId.toB58String()) + listenerNode.getMultiaddrs().forEach((ma) => { + console.log(ma.toString()) }) } diff --git a/examples/echo/test.js b/examples/echo/test.js index 579d609131..927b8e986c 100644 --- a/examples/echo/test.js +++ b/examples/echo/test.js @@ -1,9 +1,10 @@ -'use strict' +import path from 'path' +import execa from 'execa' +import pDefer from 'p-defer' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { fileURLToPath } from 'url' -const path = require('path') -const execa = require('execa') -const pDefer = require('p-defer') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) function startProcess(name) { return execa('node', [path.join(__dirname, name)], { @@ -12,7 +13,7 @@ function startProcess(name) { }) } -async function test () { +export async function test () { const listenerReady = pDefer() const messageReceived = pDefer() @@ -57,5 +58,3 @@ async function test () { } }) } - -module.exports = test diff --git a/examples/libp2p-in-the-browser/.babelrc b/examples/libp2p-in-the-browser/.babelrc deleted file mode 100644 index 2145517d82..0000000000 --- a/examples/libp2p-in-the-browser/.babelrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "plugins": ["syntax-async-functions","transform-regenerator"] -} \ No newline at end of file diff --git a/examples/libp2p-in-the-browser/index.js b/examples/libp2p-in-the-browser/index.js index 397d1201a7..92213d9afe 100644 --- a/examples/libp2p-in-the-browser/index.js +++ b/examples/libp2p-in-the-browser/index.js @@ -1,14 +1,15 @@ -import 'babel-polyfill' -import Libp2p from 'libp2p' -import Websockets from 'libp2p-websockets' -import WebRTCStar from 'libp2p-webrtc-star' -import { NOISE } from '@chainsafe/libp2p-noise' -import Mplex from 'libp2p-mplex' -import Bootstrap from 'libp2p-bootstrap' +import { createLibp2p } from 'libp2p' +import { WebSockets } from '@libp2p/websockets' +import { WebRTCStar } from '@libp2p/webrtc-star' +import { Noise } from '@chainsafe/libp2p-noise' +import { Mplex } from '@libp2p/mplex' +import { Bootstrap } from '@libp2p/bootstrap' document.addEventListener('DOMContentLoaded', async () => { + const webRtcStar = new WebRTCStar() + // Create our libp2p node - const libp2p = await Libp2p.create({ + const libp2p = await createLibp2p({ addresses: { // Add the signaling server address, along with our PeerId to our multiaddrs list // libp2p will automatically attempt to dial to the signaling server so that it can @@ -18,28 +19,24 @@ document.addEventListener('DOMContentLoaded', async () => { '/dns4/wrtc-star2.sjc.dwebops.pub/tcp/443/wss/p2p-webrtc-star' ] }, - modules: { - transport: [Websockets, WebRTCStar], - connEncryption: [NOISE], - streamMuxer: [Mplex], - peerDiscovery: [Bootstrap] - }, - config: { - peerDiscovery: { - // 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: [ - '/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' - ] - } - } - } + transports: [ + new WebSockets(), + webRtcStar + ], + connectionEncryption: [new Noise()], + streamMuxers: [new Mplex()], + peerDiscovery: [ + webRtcStar.discovery, + new Bootstrap({ + list: [ + '/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' + ] + }) + ] }) // UI elements @@ -54,23 +51,26 @@ document.addEventListener('DOMContentLoaded', async () => { } // Listen for new peers - libp2p.on('peer:discovery', (peerId) => { - log(`Found peer ${peerId.toB58String()}`) + libp2p.addEventListener('peer:discovery', (evt) => { + const peer = evt.detail + log(`Found peer ${peer.id.toString()}`) }) // Listen for new connections to peers - libp2p.connectionManager.on('peer:connect', (connection) => { - log(`Connected to ${connection.remotePeer.toB58String()}`) + libp2p.connectionManager.addEventListener('peer:connect', (evt) => { + const connection = evt.detail + log(`Connected to ${connection.remotePeer.toString()}`) }) // Listen for peers disconnecting - libp2p.connectionManager.on('peer:disconnect', (connection) => { - log(`Disconnected from ${connection.remotePeer.toB58String()}`) + libp2p.connectionManager.addEventListener('peer:disconnect', (evt) => { + const connection = evt.detail + log(`Disconnected from ${connection.remotePeer.toString()}`) }) await libp2p.start() status.innerText = 'libp2p started!' - log(`libp2p id is ${libp2p.peerId.toB58String()}`) + log(`libp2p id is ${libp2p.peerId.toString()}`) // Export libp2p to the window so you can play with the API window.libp2p = libp2p diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json index f515deb44a..6f330dc260 100644 --- a/examples/libp2p-in-the-browser/package.json +++ b/examples/libp2p-in-the-browser/package.json @@ -2,31 +2,24 @@ "name": "libp2p-in-browser", "version": "1.0.0", "description": "A libp2p node running in the browser", + "type": "module", "browserslist": [ "last 2 Chrome versions" ], "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "build": "parcel build index.html", - "start": "parcel index.html" + "start": "vite" }, - "keywords": [], - "author": "", "license": "ISC", "dependencies": { - "@chainsafe/libp2p-noise": "^5.0.2", - "libp2p": "../../", - "libp2p-bootstrap": "^0.14.0", - "libp2p-mplex": "^0.10.4", - "libp2p-webrtc-star": "^0.25.0", - "libp2p-websockets": "^0.16.1" + "@chainsafe/libp2p-noise": "^6.0.1", + "@libp2p/bootstrap": "^1.0.1", + "@libp2p/mplex": "^1.0.2", + "@libp2p/webrtc-star": "^1.0.6", + "@libp2p/websockets": "^1.0.3", + "libp2p": "../../" }, "devDependencies": { - "@babel/cli": "^7.13.10", - "@babel/core": "^7.13.0", - "babel-plugin-syntax-async-functions": "^6.13.0", - "babel-plugin-transform-regenerator": "^6.26.0", - "babel-polyfill": "^6.26.0", - "parcel": "^2.0.1" + "vite": "^2.8.6" } } diff --git a/examples/libp2p-in-the-browser/test.js b/examples/libp2p-in-the-browser/test.js index 97e65224bd..0b3a16a6fe 100644 --- a/examples/libp2p-in-the-browser/test.js +++ b/examples/libp2p-in-the-browser/test.js @@ -1,11 +1,14 @@ -'use strict' +import execa from 'execa' +import { chromium } from 'playwright' +import path from 'path' +import { fileURLToPath } from 'url' -const execa = require('execa') -const { chromium } = require('playwright'); +const __dirname = path.dirname(fileURLToPath(import.meta.url)) -async function run() { - let url = '' - const proc = execa('parcel', ['./index.html'], { +export async function test () { + let url = 'http://localhost:3000' + + const proc = execa('vite', [], { preferLocal: true, localDir: __dirname, cwd: __dirname, @@ -16,11 +19,7 @@ async function run() { /**@type {string} */ const out = chunk.toString() - if (out.includes('Server running at')) { - url = out.split('Server running at ')[1] - } - - if (out.includes('Built in')) { + if (out.includes('ready in')) { try { const browser = await chromium.launch(); const page = await browser.newPage(); @@ -36,9 +35,8 @@ async function run() { '#output', { timeout: 5000 } ) - await browser.close(); - - } catch (/** @type {any} */ err) { + await browser.close() + } catch (err) { console.error(err) process.exit(1) } finally { @@ -46,7 +44,4 @@ async function run() { } } }) - } - -module.exports = run diff --git a/examples/package.json b/examples/package.json index bee67556b3..596714c06a 100644 --- a/examples/package.json +++ b/examples/package.json @@ -2,18 +2,18 @@ "name": "libp2p-examples", "version": "1.0.0", "description": "Examples of how to use libp2p", + "type": "module", "scripts": { "test": "node ./test.js", "test:all": "node ./test-all.js" }, "license": "MIT", "dependencies": { - "@achingbrain/libp2p-gossipsub": "^0.12.2", + "@achingbrain/libp2p-gossipsub": "^0.13.5", + "@libp2p/pubsub-peer-discovery": "^5.0.1", "execa": "^2.1.0", "fs-extra": "^8.1.0", - "libp2p": "../src", - "libp2p-pubsub-peer-discovery": "^4.0.0", - "libp2p-relay-server": "^0.3.0", + "libp2p": "../", "p-defer": "^3.0.0", "uint8arrays": "^3.0.0", "which": "^2.0.1" diff --git a/examples/peer-and-content-routing/1.js b/examples/peer-and-content-routing/1.js index 16b2dfbcfd..12ecfd4be8 100644 --- a/examples/peer-and-content-routing/1.js +++ b/examples/peer-and-content-routing/1.js @@ -1,30 +1,21 @@ /* eslint-disable no-console */ -'use strict' -const Libp2p = require('../../') -const TCP = require('libp2p-tcp') -const Mplex = require('libp2p-mplex') -const { NOISE } = require('@chainsafe/libp2p-noise') -const KadDHT = require('libp2p-kad-dht') - -const delay = require('delay') +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { KadDHT } from '@libp2p/kad-dht' +import delay from 'delay' const createNode = async () => { - const node = await Libp2p.create({ + const node = await createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - modules: { - transport: [TCP], - streamMuxer: [Mplex], - connEncryption: [NOISE], - dht: KadDHT - }, - config: { - dht: { - enabled: true - } - } + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()], + dht: new KadDHT() }) await node.start() @@ -38,8 +29,8 @@ const createNode = async () => { createNode() ]) - await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) - await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs()) + await node2.peerStore.addressBook.set(node3.peerId, node3.getMultiaddrs()) await Promise.all([ node1.dial(node2.peerId), @@ -52,5 +43,5 @@ const createNode = async () => { const peer = await node1.peerRouting.findPeer(node3.peerId) console.log('Found it, multiaddrs are:') - peer.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${peer.id.toB58String()}`)) + peer.multiaddrs.forEach((ma) => console.log(ma.toString())) })(); diff --git a/examples/peer-and-content-routing/2.js b/examples/peer-and-content-routing/2.js index bcad5977e7..5df2346613 100644 --- a/examples/peer-and-content-routing/2.js +++ b/examples/peer-and-content-routing/2.js @@ -1,32 +1,23 @@ /* eslint-disable no-console */ -'use strict' -const Libp2p = require('../../') -const TCP = require('libp2p-tcp') -const Mplex = require('libp2p-mplex') -const { NOISE } = require('@chainsafe/libp2p-noise') -const { CID } = require('multiformats/cid') -const KadDHT = require('libp2p-kad-dht') - -const all = require('it-all') -const delay = require('delay') +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { CID } from 'multiformats/cid' +import { KadDHT } from '@libp2p/kad-dht' +import all from 'it-all' +import delay from 'delay' const createNode = async () => { - const node = await Libp2p.create({ + const node = await createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - modules: { - transport: [TCP], - streamMuxer: [Mplex], - connEncryption: [NOISE], - dht: KadDHT - }, - config: { - dht: { - enabled: true - } - } + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()], + dht: new KadDHT() }) await node.start() @@ -40,8 +31,8 @@ const createNode = async () => { createNode() ]) - await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) - await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs()) + await node2.peerStore.addressBook.set(node3.peerId, node3.getMultiaddrs()) await Promise.all([ node1.dial(node2.peerId), @@ -54,12 +45,12 @@ const createNode = async () => { const cid = CID.parse('QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL') await node1.contentRouting.provide(cid) - console.log('Node %s is providing %s', node1.peerId.toB58String(), cid.toString()) + console.log('Node %s is providing %s', node1.peerId.toString(), cid.toString()) // wait for propagation await delay(300) const providers = await all(node3.contentRouting.findProviders(cid, { timeout: 3000 })) - console.log('Found provider:', providers[0].id.toB58String()) + console.log('Found provider:', providers[0].id.toString()) })(); diff --git a/examples/peer-and-content-routing/README.md b/examples/peer-and-content-routing/README.md index 373f10d7a2..ad523414ff 100644 --- a/examples/peer-and-content-routing/README.md +++ b/examples/peer-and-content-routing/README.md @@ -13,26 +13,24 @@ This example builds on top of the [Protocol and Stream Muxing](../protocol-and-s First, let's update our config to support Peer Routing and Content Routing. ```JavaScript -const Libp2p = require('libp2p') -const KadDHT = require('libp2p-kad-dht') +import { createLibp2p } from 'libp2p' +import { KadDHT } from '@libp2p/kad-dht' -const node = await Libp2p.create({ +const node = await createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - modules: { - transport: [ TCP ], - streamMuxer: [ Mplex ], - connEncryption: [ NOISE ], - // we add the DHT module that will enable Peer and Content Routing - dht: KadDHT - }, - config: { - dht: { - // dht must be enabled - enabled: true - } - } + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connEncryption: [ + new Noise() + ], + // we add the DHT module that will enable Peer and Content Routing + dht: KadDHT }) ``` diff --git a/examples/peer-and-content-routing/test-1.js b/examples/peer-and-content-routing/test-1.js index 43d6c1eb7e..8a234f4d28 100644 --- a/examples/peer-and-content-routing/test-1.js +++ b/examples/peer-and-content-routing/test-1.js @@ -1,14 +1,13 @@ -'use strict' +import path from 'path' +import { waitForOutput } from '../utils.js' +import { fileURLToPath } from 'url' -const path = require('path') -const { waitForOutput } = require('../utils') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) -async function test () { +export async function test () { process.stdout.write('1.js\n') await waitForOutput('Found it, multiaddrs are:', 'node', [path.join(__dirname, '1.js')], { cwd: __dirname }) } - -module.exports = test diff --git a/examples/peer-and-content-routing/test-2.js b/examples/peer-and-content-routing/test-2.js index 76c492de66..68b41f6572 100644 --- a/examples/peer-and-content-routing/test-2.js +++ b/examples/peer-and-content-routing/test-2.js @@ -1,14 +1,13 @@ -'use strict' +import path from 'path' +import { waitForOutput } from '../utils.js' +import { fileURLToPath } from 'url' -const path = require('path') -const { waitForOutput } = require('../utils') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) -async function test () { +export async function test () { process.stdout.write('2.js\n') await waitForOutput('Found provider:', 'node', [path.join(__dirname, '2.js')], { cwd: __dirname }) } - -module.exports = test diff --git a/examples/peer-and-content-routing/test.js b/examples/peer-and-content-routing/test.js index 1ccbda6d0b..622a7dfce2 100644 --- a/examples/peer-and-content-routing/test.js +++ b/examples/peer-and-content-routing/test.js @@ -1,11 +1,7 @@ -'use strict' +import { test as test1 } from './test-1.js' +import { test as test2 } from './test-2.js' -const test1 = require('./test-1') -const test2 = require('./test-2') - -async function test() { +export async function test() { await test1() await test2() } - -module.exports = test diff --git a/examples/pnet/index.js b/examples/pnet/index.js index 090b9e0711..1da827cf14 100644 --- a/examples/pnet/index.js +++ b/examples/pnet/index.js @@ -1,10 +1,10 @@ /* eslint no-console: ["off"] */ -'use strict' -const { generate } = require('libp2p/src/pnet') -const privateLibp2pNode = require('./libp2p-node') - -const pipe = require('it-pipe') +import { generate } from 'libp2p/pnet/generate' +import { privateLibp2pNode } from './libp2p-node.js' +import { pipe } from 'it-pipe' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' // Create a Uint8Array and write the swarm key to it const swarmKey = new Uint8Array(95) @@ -29,7 +29,7 @@ generate(otherSwarmKey) console.log('nodes started...') // Add node 2 data to node1's PeerStore - await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs()) await node1.dial(node2.peerId) node2.handle('/private', ({ stream }) => { @@ -37,7 +37,7 @@ generate(otherSwarmKey) stream, async function (source) { for await (const msg of source) { - console.log(msg.toString()) + console.log(uint8ArrayToString(msg)) } } ) @@ -46,7 +46,7 @@ generate(otherSwarmKey) const { stream } = await node1.dialProtocol(node2.peerId, '/private') await pipe( - ['This message is sent on a private network'], + [uint8ArrayFromString('This message is sent on a private network')], stream ) })() diff --git a/examples/pnet/libp2p-node.js b/examples/pnet/libp2p-node.js index 8c01fde938..662edb056d 100644 --- a/examples/pnet/libp2p-node.js +++ b/examples/pnet/libp2p-node.js @@ -1,38 +1,31 @@ -'use strict' - -const Libp2p = require('libp2p') -const TCP = require('libp2p-tcp') -const MPLEX = require('libp2p-mplex') -const { NOISE } = require('@chainsafe/libp2p-noise') -const Protector = require('libp2p/src/pnet') +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { PreSharedKeyConnectionProtector } from 'libp2p/pnet' /** * privateLibp2pNode returns a libp2p node function that will use the swarm * key with the given `swarmKey` to create the Protector - * - * @param {Uint8Array} swarmKey - * @returns {Promise} Returns a libp2pNode function for use in IPFS creation */ -const privateLibp2pNode = async (swarmKey) => { - const node = await Libp2p.create({ +export async function privateLibp2pNode (swarmKey) { + const node = await createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - modules: { - transport: [TCP], // We're only using the TCP transport for this example - streamMuxer: [MPLEX], // We're only using mplex muxing - // Let's make sure to use identifying crypto in our pnet since the protector doesn't - // care about node identity, and only the presence of private keys - connEncryption: [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 - peerDiscovery: [], - connProtector: new Protector(swarmKey) - } + transports: [new TCP()], // We're only using the TCP transport for this example + streamMuxers: [new 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 + connectionEncryption: [new 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 + peerDiscovery: [], + connectionProtector: new PreSharedKeyConnectionProtector({ + psk: swarmKey + }) }) return node } - -module.exports = privateLibp2pNode diff --git a/examples/pnet/test.js b/examples/pnet/test.js index 56519927aa..33696e95ef 100644 --- a/examples/pnet/test.js +++ b/examples/pnet/test.js @@ -1,13 +1,12 @@ -'use strict' +import path from 'path' +import { waitForOutput } from '../utils.js' +import { fileURLToPath } from 'url' -const path = require('path') -const { waitForOutput } = require('../utils') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) -async function test () { +export async function test () { await waitForOutput('This message is sent on a private network', 'node', [path.join(__dirname, 'index.js')], { cwd: __dirname }) } -module.exports = test - diff --git a/examples/pnet/utils.js b/examples/pnet/utils.js index 4f03dc8669..16d89de702 100644 --- a/examples/pnet/utils.js +++ b/examples/pnet/utils.js @@ -1,6 +1,6 @@ 'use strict' -const fs = require('fs') -const path = require('path') +const fs from 'fs') +import path from 'path' /** * mkdirp recursively creates needed folders for the given dir path diff --git a/examples/protocol-and-stream-muxing/1.js b/examples/protocol-and-stream-muxing/1.js index 3f71e0f5a0..364bffdbc7 100644 --- a/examples/protocol-and-stream-muxing/1.js +++ b/examples/protocol-and-stream-muxing/1.js @@ -1,22 +1,19 @@ -'use strict' - -const Libp2p = require('../../') -const TCP = require('libp2p-tcp') -const MPLEX = require('libp2p-mplex') -const { NOISE } = require('@chainsafe/libp2p-noise') - -const pipe = require('it-pipe') +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { pipe } from 'it-pipe' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' const createNode = async () => { - const node = await Libp2p.create({ + const node = await createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - modules: { - transport: [TCP], - streamMuxer: [MPLEX], - connEncryption: [NOISE] - } + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()] }) await node.start() @@ -31,7 +28,7 @@ const createNode = async () => { ]) // Add node's 2 data to the PeerStore - await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs()) // exact matching node2.handle('/your-protocol', ({ stream }) => { @@ -39,7 +36,7 @@ const createNode = async () => { stream, async function (source) { for await (const msg of source) { - console.log(msg.toString()) + console.log(uint8ArrayToString(msg)) } } ) @@ -56,7 +53,7 @@ const createNode = async () => { stream, async function (source) { for await (const msg of source) { - console.log(msg.toString()) + console.log(uint8ArrayToString(msg)) } } ) @@ -65,7 +62,7 @@ const createNode = async () => { const { stream } = await node1.dialProtocol(node2.peerId, ['/your-protocol']) await pipe( - ['my own protocol, wow!'], + [uint8ArrayFromString('my own protocol, wow!')], stream ) diff --git a/examples/protocol-and-stream-muxing/2.js b/examples/protocol-and-stream-muxing/2.js index a7fdb175e2..44b0b1ebb3 100644 --- a/examples/protocol-and-stream-muxing/2.js +++ b/examples/protocol-and-stream-muxing/2.js @@ -1,22 +1,19 @@ -'use strict' - -const Libp2p = require('../../') -const TCP = require('libp2p-tcp') -const MPLEX = require('libp2p-mplex') -const { NOISE } = require('@chainsafe/libp2p-noise') - -const pipe = require('it-pipe') +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { pipe } from 'it-pipe' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' const createNode = async () => { - const node = await Libp2p.create({ + const node = await createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - modules: { - transport: [TCP], - streamMuxer: [MPLEX], - connEncryption: [NOISE] - } + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()] }) await node.start() @@ -31,14 +28,14 @@ const createNode = async () => { ]) // Add node's 2 data to the PeerStore - await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs()) node2.handle(['/a', '/b'], ({ protocol, stream }) => { pipe( stream, async function (source) { for await (const msg of source) { - console.log(`from: ${protocol}, msg: ${msg.toString()}`) + console.log(`from: ${protocol}, msg: ${uint8ArrayToString(msg)}`) } } ) @@ -46,19 +43,19 @@ const createNode = async () => { const { stream: stream1 } = await node1.dialProtocol(node2.peerId, ['/a']) await pipe( - ['protocol (a)'], + [uint8ArrayFromString('protocol (a)')], stream1 ) const { stream: stream2 } = await node1.dialProtocol(node2.peerId, ['/b']) await pipe( - ['protocol (b)'], + [uint8ArrayFromString('protocol (b)')], stream2 ) const { stream: stream3 } = await node1.dialProtocol(node2.peerId, ['/b']) await pipe( - ['another stream on protocol (b)'], + [uint8ArrayFromString('another stream on protocol (b)')], stream3 ) })(); diff --git a/examples/protocol-and-stream-muxing/3.js b/examples/protocol-and-stream-muxing/3.js index c9a9148352..a368743509 100644 --- a/examples/protocol-and-stream-muxing/3.js +++ b/examples/protocol-and-stream-muxing/3.js @@ -1,23 +1,21 @@ /* eslint-disable no-console */ -'use strict' -const Libp2p = require('../../') -const TCP = require('libp2p-tcp') -const MPLEX = require('libp2p-mplex') -const { NOISE } = require('@chainsafe/libp2p-noise') - -const pipe = require('it-pipe') +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { pipe } from 'it-pipe' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' const createNode = async () => { - const node = await Libp2p.create({ + const node = await createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - modules: { - transport: [TCP], - streamMuxer: [MPLEX], - connEncryption: [NOISE] - } + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()] }) await node.start() @@ -32,14 +30,14 @@ const createNode = async () => { ]) // Add node's 2 data to the PeerStore - await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs()) node1.handle('/node-1', ({ stream }) => { pipe( stream, async function (source) { for await (const msg of source) { - console.log(msg.toString()) + console.log(uint8ArrayToString(msg)) } } ) @@ -50,7 +48,7 @@ const createNode = async () => { stream, async function (source) { for await (const msg of source) { - console.log(msg.toString()) + console.log(uint8ArrayToString(msg)) } } ) @@ -58,13 +56,13 @@ const createNode = async () => { const { stream: stream1 } = await node1.dialProtocol(node2.peerId, ['/node-2']) await pipe( - ['from 1 to 2'], + [uint8ArrayFromString('from 1 to 2')], stream1 ) const { stream: stream2 } = await node2.dialProtocol(node1.peerId, ['/node-1']) await pipe( - ['from 2 to 1'], + [uint8ArrayFromString('from 2 to 1')], stream2 ) })(); diff --git a/examples/protocol-and-stream-muxing/README.md b/examples/protocol-and-stream-muxing/README.md index a4df1a8b57..dae3104801 100644 --- a/examples/protocol-and-stream-muxing/README.md +++ b/examples/protocol-and-stream-muxing/README.md @@ -11,9 +11,9 @@ Let's see _protocol multiplexing_ in action! You will need the following modules After creating the nodes, we need to tell libp2p which protocols to handle. ```JavaScript -const pipe = require('it-pipe') -const { map } = require('streaming-iterables') -const { toBuffer } = require('it-buffer') +import { pipe } from 'it-pipe' +const { map } from 'streaming-iterables') +const { toBuffer } from 'it-buffer') // ... const node1 = nodes[0] @@ -102,17 +102,19 @@ Stream multiplexing is an old concept, in fact it happens in many of the layers Currently, we have [libp2p-mplex](https://github.com/libp2p/js-libp2p-mplex) and pluging it in is as easy as adding a transport. Let's revisit our libp2p configuration. ```JavaScript -const Libp2p = require('libp2p') -const TCP = require('libp2p-tcp') -const MPLEX = require('libp2p-mplex') +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' //... const createNode = () => { return Libp2p.create({ - modules: { - transport: [ TCP ], - streamMuxer: [ Mplex ] - } + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ] }) } ``` diff --git a/examples/protocol-and-stream-muxing/test-1.js b/examples/protocol-and-stream-muxing/test-1.js index 91f409d5a9..d11e42e4f8 100644 --- a/examples/protocol-and-stream-muxing/test-1.js +++ b/examples/protocol-and-stream-muxing/test-1.js @@ -1,14 +1,13 @@ -'use strict' +import path from 'path' +import { waitForOutput } from '../utils.js' +import { fileURLToPath } from 'url' -const path = require('path') -const { waitForOutput } = require('../utils') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) -async function test () { +export async function test () { process.stdout.write('1.js\n') await waitForOutput('my own protocol, wow!', 'node', [path.join(__dirname, '1.js')], { cwd: __dirname }) } - -module.exports = test diff --git a/examples/protocol-and-stream-muxing/test-2.js b/examples/protocol-and-stream-muxing/test-2.js index 26b4b12ea1..cf6dc60665 100644 --- a/examples/protocol-and-stream-muxing/test-2.js +++ b/examples/protocol-and-stream-muxing/test-2.js @@ -1,14 +1,13 @@ -'use strict' +import path from 'path' +import { waitForOutput } from '../utils.js' +import { fileURLToPath } from 'url' -const path = require('path') -const { waitForOutput } = require('../utils') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) -async function test () { +export async function test () { process.stdout.write('2.js\n') await waitForOutput('another stream on protocol (b)', 'node', [path.join(__dirname, '2.js')], { cwd: __dirname }) } - -module.exports = test diff --git a/examples/protocol-and-stream-muxing/test-3.js b/examples/protocol-and-stream-muxing/test-3.js index 8724237cf2..bcd4aa1547 100644 --- a/examples/protocol-and-stream-muxing/test-3.js +++ b/examples/protocol-and-stream-muxing/test-3.js @@ -1,14 +1,13 @@ -'use strict' +import path from 'path' +import { waitForOutput } from '../utils.js' +import { fileURLToPath } from 'url' -const path = require('path') -const { waitForOutput } = require('../utils') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) -async function test () { +export async function test () { process.stdout.write('3.js\n') await waitForOutput('from 2 to 1', 'node', [path.join(__dirname, '3.js')], { cwd: __dirname }) } - -module.exports = test diff --git a/examples/protocol-and-stream-muxing/test.js b/examples/protocol-and-stream-muxing/test.js index 72fa27ee7c..8f209bdddb 100644 --- a/examples/protocol-and-stream-muxing/test.js +++ b/examples/protocol-and-stream-muxing/test.js @@ -1,13 +1,9 @@ -'use strict' +import { test as test1 } from './test-1.js' +import { test as test2 } from './test-2.js' +import { test as test3 } from './test-3.js' -const test1 = require('./test-1') -const test2 = require('./test-2') -const test3 = require('./test-3') - -async function test() { +export async function test() { await test1() await test2() await test3() } - -module.exports = test diff --git a/examples/pubsub/1.js b/examples/pubsub/1.js index 20aa497014..53d7739e9e 100644 --- a/examples/pubsub/1.js +++ b/examples/pubsub/1.js @@ -1,25 +1,23 @@ /* eslint-disable no-console */ -'use strict' -const Libp2p = require('../../') -const TCP = require('libp2p-tcp') -const Mplex = require('libp2p-mplex') -const { NOISE } = require('@chainsafe/libp2p-noise') -const Gossipsub = require('@achingbrain/libp2p-gossipsub') -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { Gossipsub } from '@achingbrain/libp2p-gossipsub' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { CustomEvent } from '@libp2p/interfaces' const createNode = async () => { - const node = await Libp2p.create({ + const node = await createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - modules: { - transport: [TCP], - streamMuxer: [Mplex], - connEncryption: [NOISE], - pubsub: Gossipsub - } + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()], + pubsub: new Gossipsub() }) await node.start() @@ -35,22 +33,20 @@ const createNode = async () => { ]) // Add node's 2 data to the PeerStore - await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs()) await node1.dial(node2.peerId) - node1.pubsub.on(topic, (msg) => { - console.log(`node1 received: ${uint8ArrayToString(msg.data)}`) + node1.pubsub.addEventListener(topic, (evt) => { + console.log(`node1 received: ${uint8ArrayToString(evt.detail.data)}`) }) - node1.pubsub.subscribe(topic) // Will not receive own published messages by default - node2.pubsub.on(topic, (msg) => { - console.log(`node2 received: ${uint8ArrayToString(msg.data)}`) + node2.pubsub.addEventListener(topic, (evt) => { + console.log(`node2 received: ${uint8ArrayToString(evt.detail.data)}`) }) - node2.pubsub.subscribe(topic) // node2 publishes "news" every second setInterval(() => { - node2.pubsub.publish(topic, uint8ArrayFromString('Bird bird bird, bird is the word!')) + node2.pubsub.dispatchEvent(new CustomEvent(topic, { detail: uint8ArrayFromString('Bird bird bird, bird is the word!') })) }, 1000) })() diff --git a/examples/pubsub/README.md b/examples/pubsub/README.md index 654dd76d26..9dec1f973c 100644 --- a/examples/pubsub/README.md +++ b/examples/pubsub/README.md @@ -21,28 +21,32 @@ Using PubSub is super simple, you only need to provide the implementation of you First, let's update our libp2p configuration with a pubsub implementation. ```JavaScript -const Libp2p = require('libp2p') -const Gossipsub = require('libp2p-gossipsub') +import { createLibp2p } from 'libp2p' +import { Gossipsub } from 'libp2p-gossipsub' -const node = await Libp2p.create({ +const node = await createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - modules: { - transport: [ TCP ], - streamMuxer: [ Mplex ], - connEncryption: [ NOISE ], - // we add the Pubsub module we want - pubsub: Gossipsub - } + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + new Noise() + ], + // we add the Pubsub module we want + pubsub: new Gossipsub() }) ``` Once that is done, we only need to create a few libp2p nodes, connect them and everything is ready to start using pubsub. ```JavaScript -const { fromString } = require('uint8arrays/from-string') -const { toString } = require('uint8arrays/to-string') +const { fromString } from 'uint8arrays/from-string') +const { toString } from 'uint8arrays/to-string') const topic = 'news' const node1 = nodes[0] diff --git a/examples/pubsub/message-filtering/1.js b/examples/pubsub/message-filtering/1.js index 81a7830d4d..32665c1287 100644 --- a/examples/pubsub/message-filtering/1.js +++ b/examples/pubsub/message-filtering/1.js @@ -1,25 +1,23 @@ /* eslint-disable no-console */ -'use strict' -const Libp2p = require('../../../') -const TCP = require('libp2p-tcp') -const Mplex = require('libp2p-mplex') -const { NOISE } = require('@chainsafe/libp2p-noise') -const Gossipsub = require('@achingbrain/libp2p-gossipsub') -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { Gossipsub } from '@achingbrain/libp2p-gossipsub' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { CustomEvent } from '@libp2p/interfaces' const createNode = async () => { - const node = await Libp2p.create({ + const node = await createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - modules: { - transport: [TCP], - streamMuxer: [Mplex], - connEncryption: [NOISE], - pubsub: Gossipsub - } + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()], + pubsub: new Gossipsub() }) await node.start() @@ -36,28 +34,26 @@ const createNode = async () => { ]) // node1 conect to node2 and node2 conect to node3 - await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs()) await node1.dial(node2.peerId) - await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) + await node2.peerStore.addressBook.set(node3.peerId, node3.getMultiaddrs()) await node2.dial(node3.peerId) //subscribe - node1.pubsub.on(topic, (msg) => { + node1.pubsub.addEventListener(topic, (evt) => { // Will not receive own published messages by default - console.log(`node1 received: ${uint8ArrayToString(msg.data)}`) + console.log(`node1 received: ${uint8ArrayToString(evt.detail.data)}`) }) await node1.pubsub.subscribe(topic) - node2.pubsub.on(topic, (msg) => { - console.log(`node2 received: ${uint8ArrayToString(msg.data)}`) + node2.pubsub.addEventListener(topic, (evt) => { + console.log(`node2 received: ${uint8ArrayToString(evt.detail.data)}`) }) - await node2.pubsub.subscribe(topic) - node3.pubsub.on(topic, (msg) => { - console.log(`node3 received: ${uint8ArrayToString(msg.data)}`) + node3.pubsub.addEventListener(topic, (evt) => { + console.log(`node3 received: ${uint8ArrayToString(evt.detail.data)}`) }) - await node3.pubsub.subscribe(topic) const validateFruit = (msgTopic, msg) => { const fruit = uint8ArrayToString(msg.data) @@ -79,7 +75,7 @@ const createNode = async () => { // car is not a fruit ! setInterval(() => { console.log('############## fruit ' + myFruits[count] + ' ##############') - node1.pubsub.publish(topic, uint8ArrayFromString(myFruits[count])) + node1.pubsub.dispatchEvent(new CustomEvent(topic, { detail: uint8ArrayFromString(myFruits[count]) })) count++ if (count == myFruits.length) { count = 0 diff --git a/examples/pubsub/message-filtering/README.md b/examples/pubsub/message-filtering/README.md index eb554afac1..9eecb87313 100644 --- a/examples/pubsub/message-filtering/README.md +++ b/examples/pubsub/message-filtering/README.md @@ -7,19 +7,23 @@ To prevent undesired data from being propagated on the network, we can apply a f First, let's update our libp2p configuration with a pubsub implementation. ```JavaScript -const Libp2p = require('libp2p') -const Gossipsub = require('libp2p-gossipsub') +import { createLibp2p } from 'libp2p' +import { Gossipsub } from 'libp2p-gossipsub' -const node = await Libp2p.create({ +const node = await createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - modules: { - transport: [ TCP ], - streamMuxer: [ Mplex ], - connEncryption: [ NOISE ], - pubsub: Gossipsub - } + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + new Noise() + ], + pubsub: new Gossipsub() }) ``` diff --git a/examples/pubsub/message-filtering/test.js b/examples/pubsub/message-filtering/test.js index fddcc5c840..4cb9fbaa15 100644 --- a/examples/pubsub/message-filtering/test.js +++ b/examples/pubsub/message-filtering/test.js @@ -1,9 +1,10 @@ -'use strict' +import path from 'path' +import execa from 'execa' +import pDefer from 'p-defer' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { fileURLToPath } from 'url' -const path = require('path') -const execa = require('execa') -const pDefer = require('p-defer') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) const stdout = [ { @@ -24,7 +25,7 @@ const stdout = [ }, ] -async function test () { +export async function test () { const defer = pDefer() let topicCount = 0 let topicMessageCount = 0 @@ -63,5 +64,3 @@ async function test () { await defer.promise proc.kill() } - -module.exports = test diff --git a/examples/pubsub/test-1.js b/examples/pubsub/test-1.js index ea968897e5..c818a573d9 100644 --- a/examples/pubsub/test-1.js +++ b/examples/pubsub/test-1.js @@ -1,11 +1,12 @@ -'use strict' +import path from 'path' +import execa from 'execa' +import pDefer from 'p-defer' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { fileURLToPath } from 'url' -const path = require('path') -const execa = require('execa') -const pDefer = require('p-defer') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) -async function test () { +export async function test () { const defer = pDefer() process.stdout.write('1.js\n') @@ -26,5 +27,3 @@ async function test () { await defer.promise proc.kill() } - -module.exports = test diff --git a/examples/pubsub/test.js b/examples/pubsub/test.js index 987c351a4d..2848f4b0b5 100644 --- a/examples/pubsub/test.js +++ b/examples/pubsub/test.js @@ -1,11 +1,7 @@ -'use strict' +import { test as test1 } from './test-1.js' +import { test as testMessageFiltering } from './message-filtering/test.js' -const test1 = require('./test-1') -const testMessageFiltering = require('./message-filtering/test') - -async function test() { +export async function test() { await test1() await testMessageFiltering() } - -module.exports = test diff --git a/examples/test-all.js b/examples/test-all.js index 3ee99e45fa..bce01ac71c 100644 --- a/examples/test-all.js +++ b/examples/test-all.js @@ -1,4 +1,3 @@ -'use strict' process.on('unhandedRejection', (err) => { console.error(err) @@ -6,11 +5,14 @@ process.on('unhandedRejection', (err) => { process.exit(1) }) -const path = require('path') -const fs = require('fs') -const { +import path from 'path' +import fs from 'fs' +import { waitForOutput -} = require('./utils') +} from './utils.js' +import { fileURLToPath } from 'url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) async function testAll () { for (const dir of fs.readdirSync(__dirname)) { @@ -24,7 +26,7 @@ async function testAll () { continue } - await waitForOutput('npm info ok', 'npm', ['test', '--', dir], { + await waitForOutput('npm info ok', 'npm', ['--loglevel', 'info', 'run', 'test', '--', dir], { cwd: __dirname }) } diff --git a/examples/test.js b/examples/test.js index 69f2be8bca..5de9963449 100644 --- a/examples/test.js +++ b/examples/test.js @@ -1,11 +1,12 @@ -'use strict' - process.env.NODE_ENV = 'test' process.env.CI = true // needed for some "clever" build tools -const fs = require('fs-extra') -const path = require('path') -const execa = require('execa') +import fs from 'fs-extra' +import path from 'path' +import execa from 'execa' +import { fileURLToPath } from 'url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) const dir = path.join(__dirname, process.argv[2]) testExample(dir) @@ -53,7 +54,7 @@ async function build (dir) { return } - const pkg = require(pkgJson) + const pkg = JSON.parse(fs.readFileSync(pkgJson)) let build if (pkg.scripts.bundle) { @@ -88,7 +89,7 @@ async function runTest (dir) { return } - const test = require(testFile) + const { test } = await import(testFile) await test() } diff --git a/examples/transports/1.js b/examples/transports/1.js index 81d8a2bc0d..739acaa81c 100644 --- a/examples/transports/1.js +++ b/examples/transports/1.js @@ -1,21 +1,24 @@ /* eslint-disable no-console */ -'use strict' -const Libp2p = require('../..') -const TCP = require('libp2p-tcp') -const { NOISE } = require('@chainsafe/libp2p-noise') +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Noise } from '@chainsafe/libp2p-noise' const createNode = async () => { - const node = await Libp2p.create({ + const node = await createLibp2p({ 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'] + listen: [ + '/ip4/0.0.0.0/tcp/0' + ] }, - modules: { - transport: [TCP], - connEncryption: [NOISE] - } + transports: [ + new TCP() + ], + connectionEncryption: [ + new Noise() + ] }) await node.start() @@ -27,5 +30,5 @@ const createNode = async () => { console.log('node has started (true/false):', node.isStarted()) console.log('listening on:') - node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`)) + node.getMultiaddrs().forEach((ma) => console.log(ma.toString())) })(); diff --git a/examples/transports/2.js b/examples/transports/2.js index 2962a062a1..d157da1191 100644 --- a/examples/transports/2.js +++ b/examples/transports/2.js @@ -1,26 +1,24 @@ /* eslint-disable no-console */ -'use strict' -const Libp2p = require('../..') -const TCP = require('libp2p-tcp') -const { NOISE } = require('@chainsafe/libp2p-noise') -const MPLEX = require('libp2p-mplex') - -const pipe = require('it-pipe') -const concat = require('it-concat') +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Noise } from '@chainsafe/libp2p-noise' +import { Mplex } from '@libp2p/mplex' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { pipe } from 'it-pipe' +import toBuffer from 'it-to-buffer' const createNode = async () => { - const node = await Libp2p.create({ + const node = await createLibp2p({ 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] - } + transports: [new TCP()], + connectionEncryption: [new Noise()], + streamMuxers: [new Mplex()] }) await node.start() @@ -29,7 +27,7 @@ const createNode = async () => { function printAddrs (node, number) { console.log('node %s is listening on:', number) - node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`)) + node.getMultiaddrs().forEach((ma) => console.log(ma.toString())) } ;(async () => { @@ -44,16 +42,16 @@ function printAddrs (node, number) { node2.handle('/print', async ({ stream }) => { const result = await pipe( stream, - concat + toBuffer ) - console.log(result.toString()) + console.log(uint8ArrayToString(result)) }) - await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs()) const { stream } = await node1.dialProtocol(node2.peerId, '/print') await pipe( - ['Hello', ' ', 'p2p', ' ', 'world', '!'], + ['Hello', ' ', 'p2p', ' ', 'world', '!'].map(str => uint8ArrayFromString(str)), stream ) })(); diff --git a/examples/transports/3.js b/examples/transports/3.js index d9a83697c3..b1a233f867 100644 --- a/examples/transports/3.js +++ b/examples/transports/3.js @@ -1,28 +1,26 @@ /* eslint-disable no-console */ -'use strict' -const Libp2p = require('../..') -const TCP = require('libp2p-tcp') -const WebSockets = require('libp2p-websockets') -const { NOISE } = require('@chainsafe/libp2p-noise') -const MPLEX = require('libp2p-mplex') - -const pipe = require('it-pipe') +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { WebSockets } from '@libp2p/websockets' +import { Noise } from '@chainsafe/libp2p-noise' +import { Mplex } from '@libp2p/mplex' +import { pipe } from 'it-pipe' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' const createNode = async (transports, addresses = []) => { if (!Array.isArray(addresses)) { addresses = [addresses] } - const node = await Libp2p.create({ + const node = await createLibp2p({ addresses: { listen: addresses }, - modules: { - transport: transports, - connEncryption: [NOISE], - streamMuxer: [MPLEX] - } + transports: transports, + connectionEncryption: [new Noise()], + streamMuxers: [new Mplex()] }) await node.start() @@ -31,7 +29,7 @@ const createNode = async (transports, addresses = []) => { function printAddrs(node, number) { console.log('node %s is listening on:', number) - node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`)) + node.getMultiaddrs().forEach((ma) => console.log(ma.toString())) } function print ({ stream }) { @@ -39,7 +37,7 @@ function print ({ stream }) { stream, async function (source) { for await (const msg of source) { - console.log(msg.toString()) + console.log(uint8ArrayToString(msg)) } } ) @@ -47,9 +45,9 @@ function print ({ stream }) { ;(async () => { const [node1, node2, node3] = await Promise.all([ - createNode([TCP], '/ip4/0.0.0.0/tcp/0'), - createNode([TCP, WebSockets], ['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws']), - createNode([WebSockets], '/ip4/127.0.0.1/tcp/20000/ws') + createNode([new TCP()], '/ip4/0.0.0.0/tcp/0'), + createNode([new TCP(), new WebSockets()], ['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws']), + createNode([new WebSockets()], '/ip4/127.0.0.1/tcp/20000/ws') ]) printAddrs(node1, '1') @@ -60,28 +58,28 @@ function print ({ stream }) { node2.handle('/print', print) node3.handle('/print', print) - await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) - await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) - await node3.peerStore.addressBook.set(node1.peerId, node1.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs()) + await node2.peerStore.addressBook.set(node3.peerId, node3.getMultiaddrs()) + await node3.peerStore.addressBook.set(node1.peerId, node1.getMultiaddrs()) // node 1 (TCP) dials to node 2 (TCP+WebSockets) const { stream } = await node1.dialProtocol(node2.peerId, '/print') await pipe( - ['node 1 dialed to node 2 successfully'], + [uint8ArrayFromString('node 1 dialed to node 2 successfully')], stream ) // node 2 (TCP+WebSockets) dials to node 2 (WebSockets) const { stream: stream2 } = await node2.dialProtocol(node3.peerId, '/print') await pipe( - ['node 2 dialed to node 3 successfully'], + [uint8ArrayFromString('node 2 dialed to node 3 successfully')], stream2 ) // node 3 (listening WebSockets) can dial node 1 (TCP) try { await node3.dialProtocol(node1.peerId, '/print') - } catch (/** @type {any} */ err) { + } catch (err) { console.log('node 3 failed to dial to node 1 with:', err.message) } })(); diff --git a/examples/transports/4.js b/examples/transports/4.js index b46d147373..389217aaf4 100644 --- a/examples/transports/4.js +++ b/examples/transports/4.js @@ -1,17 +1,15 @@ /* eslint-disable no-console */ -'use strict' -const Libp2p = require('../..') -const TCP = require('libp2p-tcp') -const WebSockets = require('libp2p-websockets') -const { NOISE } = require('@chainsafe/libp2p-noise') -const MPLEX = require('libp2p-mplex') - -const fs = require('fs'); -const https = require('https'); -const pipe = require('it-pipe') - -const transportKey = WebSockets.prototype[Symbol.toStringTag]; +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { WebSockets } from '@libp2p/websockets' +import { Noise } from '@chainsafe/libp2p-noise' +import { Mplex } from '@libp2p/mplex' +import fs from 'fs' +import https from 'https' +import { pipe } from 'it-pipe' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' const httpServer = https.createServer({ cert: fs.readFileSync('./test_certs/cert.pem'), @@ -23,26 +21,25 @@ const createNode = async (addresses = []) => { addresses = [addresses] } - const node = await Libp2p.create({ + const node = await createLibp2p({ addresses: { listen: addresses }, - modules: { - transport: [WebSockets], - connEncryption: [NOISE], - streamMuxer: [MPLEX] - }, - config: { - peerDiscovery: { - // Disable autoDial as it would fail because we are using a self-signed cert. - // `dialProtocol` does not fail because we pass `rejectUnauthorized: false`. - autoDial: false - }, - transport: { - [transportKey]: { - listenerOptions: { server: httpServer }, - }, - }, + transports: [ + new TCP(), + new WebSockets({ + server: httpServer, + websocket: { + rejectUnauthorized: false + } + }) + ], + connectionEncryption: [new Noise()], + streamMuxers: [new Mplex()], + connectionManager: { + // Disable autoDial as it would fail because we are using a self-signed cert. + // `dialProtocol` does not fail because we pass `rejectUnauthorized: false`. + autoDial: false } }) @@ -52,7 +49,7 @@ const createNode = async (addresses = []) => { function printAddrs(node, number) { console.log('node %s is listening on:', number) - node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`)) + node.getMultiaddrs().forEach((ma) => console.log(ma.toString())) } function print ({ stream }) { @@ -60,7 +57,7 @@ function print ({ stream }) { stream, async function (source) { for await (const msg of source) { - console.log(msg.toString()) + console.log(uint8ArrayToString(msg)) } } ) @@ -78,12 +75,12 @@ function print ({ stream }) { node1.handle('/print', print) node2.handle('/print', print) - const targetAddr = `${node1.multiaddrs[0]}/p2p/${node1.peerId.toB58String()}`; + const targetAddr = node1.getMultiaddrs()[0]; // node 2 (Secure WebSockets) dials to node 1 (Secure Websockets) - const { stream } = await node2.dialProtocol(targetAddr, '/print', { websocket: { rejectUnauthorized: false } }) + const { stream } = await node2.dialProtocol(targetAddr, '/print') await pipe( - ['node 2 dialed to node 1 successfully'], + [uint8ArrayFromString('node 2 dialed to node 1 successfully')], stream ) })(); diff --git a/examples/transports/README.md b/examples/transports/README.md index 03d46012e9..1d3f5d4fd8 100644 --- a/examples/transports/README.md +++ b/examples/transports/README.md @@ -21,23 +21,23 @@ Then, in your favorite text editor create a file with the `.js` extension. I've First thing is to create our own libp2p node! Insert: ```JavaScript -'use strict' - -const Libp2p = require('libp2p') -const TCP = require('libp2p-tcp') -const { NOISE } = require('@chainsafe/libp2p-noise') +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Noise } from '@chainsafe/libp2p-noise' const createNode = async () => { - const node = await Libp2p.create({ + const node = await createLibp2p({ 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 ] - } + transports: [ + new TCP() + ], + connectionEncryption: [ + new Noise() + ] }) await node.start() @@ -80,31 +80,29 @@ Now that we have our `createNode` function, let's create two nodes and make them For this step, we will need some more dependencies. ```bash -> npm install it-pipe it-concat libp2p-mplex +> npm install it-pipe it-to-buffer @libp2p/mplex ``` And we also need to import the modules on our .js file: ```js -const pipe = require('it-pipe') -const concat = require('it-concat') -const MPLEX = require('libp2p-mplex') +import { pipe } from 'it-pipe' +import toBuffer from 'it-to-buffer' +import { Mplex } from '@libp2p/mplex' ``` 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({ + const node = await createLibp2p({ 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 - } + transports: [new TCP()], + connectionEncryption: [new Noise()], + streamMuxers: [new Mplex()] // <--- Add this line }) await node.start() @@ -135,7 +133,7 @@ Then add, node2.handle('/print', async ({ stream }) => { const result = await pipe( stream, - concat + toBuffer ) console.log(result.toString()) }) @@ -186,15 +184,13 @@ const createNode = async (transports, addresses = []) => { addresses = [addresses] } - const node = await Libp2p.create({ + const node = await createLibp2p({ addresses: { listen: addresses }, - modules: { - transport: transports, - connEncryption: [NOISE], - streamMuxer: [MPLEX] - } + transport: transports, + connectionEncryption: [new Noise()], + streamMuxers: [new Mplex()] }) await node.start() @@ -207,8 +203,8 @@ As a rule, a libp2p node will only be capable of using a transport if: a) it has Let's update our flow to create nodes and see how they behave when dialing to each other: ```JavaScript -const WebSockets = require('libp2p-websockets') -const TCP = require('libp2p-tcp') +import { WebSockets } from '@libp2p/websockets' +import { TCP } from '@libp2p/tcp' const [node1, node2, node3] = await Promise.all([ createNode([TCP], '/ip4/0.0.0.0/tcp/0'), diff --git a/examples/transports/test-1.js b/examples/transports/test-1.js index bcdaf57b23..81c1721d94 100644 --- a/examples/transports/test-1.js +++ b/examples/transports/test-1.js @@ -1,14 +1,13 @@ -'use strict' +import path from 'path' +import { waitForOutput } from '../utils.js' +import { fileURLToPath } from 'url' -const path = require('path') -const { waitForOutput } = require('../utils') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) -async function test () { +export async function test () { process.stdout.write('1.js\n') await waitForOutput('/p2p/', 'node', [path.join(__dirname, '1.js')], { cwd: __dirname }) } - -module.exports = test diff --git a/examples/transports/test-2.js b/examples/transports/test-2.js index b383ac9e98..4c9714781b 100644 --- a/examples/transports/test-2.js +++ b/examples/transports/test-2.js @@ -1,14 +1,13 @@ -'use strict' +import path from 'path' +import { waitForOutput } from '../utils.js' +import { fileURLToPath } from 'url' -const path = require('path') -const { waitForOutput } = require('../utils') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) -async function test () { +export async function test () { process.stdout.write('2.js\n') await waitForOutput('Hello p2p world!', 'node', [path.join(__dirname, '2.js')], { cwd: __dirname }) } - -module.exports = test diff --git a/examples/transports/test-3.js b/examples/transports/test-3.js index 642ab045b4..acc1e27def 100644 --- a/examples/transports/test-3.js +++ b/examples/transports/test-3.js @@ -1,14 +1,13 @@ -'use strict' +import path from 'path' +import { waitForOutput } from '../utils.js' +import { fileURLToPath } from 'url' -const path = require('path') -const { waitForOutput } = require('../utils') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) -async function test () { +export async function test () { process.stdout.write('3.js\n') await waitForOutput('node 3 failed to dial to node 1 with:', 'node', [path.join(__dirname, '3.js')], { cwd: __dirname }) } - -module.exports = test diff --git a/examples/transports/test-4.js b/examples/transports/test-4.js index 5186ff533e..7a5953b0da 100644 --- a/examples/transports/test-4.js +++ b/examples/transports/test-4.js @@ -1,14 +1,13 @@ -'use strict' +import path from 'path' +import { waitForOutput } from '../utils.js' +import { fileURLToPath } from 'url' -const path = require('path') -const { waitForOutput } = require('../utils') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) -async function test () { +export async function test () { process.stdout.write('4.js\n') await waitForOutput('node 2 dialed to node 1 successfully', 'node', [path.join(__dirname, '4.js')], { cwd: __dirname }) } - -module.exports = test diff --git a/examples/transports/test.js b/examples/transports/test.js index 8ef5d0b56c..1b0f8f949a 100644 --- a/examples/transports/test.js +++ b/examples/transports/test.js @@ -1,15 +1,11 @@ -'use strict' +import { test as test1 } from './test-1.js' +import { test as test2 } from './test-2.js' +import { test as test3 } from './test-3.js' +import { test as test4 } from './test-4.js' -const test1 = require('./test-1') -const test2 = require('./test-2') -const test3 = require('./test-3') -const test4 = require('./test-4') - -async function test() { +export async function test() { await test1() await test2() await test3() await test4() } - -module.exports = test diff --git a/examples/utils.js b/examples/utils.js index 1f6e571c97..914d91e9bb 100644 --- a/examples/utils.js +++ b/examples/utils.js @@ -1,15 +1,13 @@ -'use strict' - -const execa = require('execa') -const fs = require('fs-extra') -const which = require('which') +import execa from 'execa' +import fs from 'fs-extra' +import which from 'which' async function isExecutable (command) { try { await fs.access(command, fs.constants.X_OK) return true - } catch (/** @type {any} */ err) { + } catch (err) { if (err.code === 'ENOENT') { return isExecutable(await which(command)) } @@ -22,7 +20,7 @@ async function isExecutable (command) { } } -async function waitForOutput (expectedOutput, command, args = [], opts = {}) { +export async function waitForOutput (expectedOutput, command, args = [], opts = {}) { if (!await isExecutable(command)) { args.unshift(command) command = 'node' @@ -49,13 +47,9 @@ async function waitForOutput (expectedOutput, command, args = [], opts = {}) { try { await proc - } catch (/** @type {any} */ err) { + } catch (err) { if (!err.killed) { throw err } } } - -module.exports = { - waitForOutput -} diff --git a/examples/webrtc-direct/README.md b/examples/webrtc-direct/README.md index 3eb406a66b..529316a8a5 100644 --- a/examples/webrtc-direct/README.md +++ b/examples/webrtc-direct/README.md @@ -9,7 +9,7 @@ When in the root folder of this example, type `node listener.js` in terminal. Yo incoming connections. Below is just an example of such address. In your case the suffix hash (`peerId`) will be different. ```bash -$ node listener.js +$ node listener.js Listening on: /ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct/p2p/QmUKQCzEUhhhobcNSrXU5uzxTqbvF1BjMCGNGZzZU14Kgd ``` @@ -18,16 +18,15 @@ Listening on: Confirm that the above address is the same as the field `list` in `public/dialer.js`: ```js peerDiscovery: { - [Bootstrap.tag]: { - enabled: true, + new Bootstrap({ // paste the address into `list` list: ['/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct/p2p/QmUKQCzEUhhhobcNSrXU5uzxTqbvF1BjMCGNGZzZU14Kgd'] - } + }) } ``` ## 2. Run a browser libp2p dialer -When in the root folder of this example, type `npm run dev` in terminal. You should see an address where you can browse +When in the root folder of this example, type `npm start` in terminal. You should see an address where you can browse the running client. Open this address in your browser. In console logs you should see logs about successful connection with the node client. In the output of node client you should see a log message about successful connection as well. diff --git a/examples/webrtc-direct/dialer.js b/examples/webrtc-direct/dialer.js index b08720c255..d2357dfa8c 100644 --- a/examples/webrtc-direct/dialer.js +++ b/examples/webrtc-direct/dialer.js @@ -1,28 +1,21 @@ -import 'babel-polyfill' -const Libp2p = require('libp2p') -const WebRTCDirect = require('libp2p-webrtc-direct') -const Mplex = require('libp2p-mplex') -const { NOISE } = require('@chainsafe/libp2p-noise') -const Bootstrap = require('libp2p-bootstrap') +import { createLibp2p } from 'libp2p' +import { WebRTCDirect } from '@achingbrain/webrtc-direct' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { Bootstrap } from '@libp2p/bootstrap' document.addEventListener('DOMContentLoaded', async () => { // use the same peer id as in `listener.js` to avoid copy-pasting of listener's peer id into `peerDiscovery` const hardcodedPeerId = '12D3KooWCuo3MdXfMgaqpLC5Houi1TRoFqgK9aoxok4NK5udMu8m' - const libp2p = await Libp2p.create({ - modules: { - transport: [WebRTCDirect], - streamMuxer: [Mplex], - connEncryption: [NOISE], - peerDiscovery: [Bootstrap] - }, - config: { - peerDiscovery: { - [Bootstrap.tag]: { - enabled: true, - list: [`/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct/p2p/${hardcodedPeerId}`] - } - } - } + const libp2p = await createLibp2p({ + transports: [new WebRTCDirect()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()], + peerDiscovery: [ + new Bootstrap({ + list: [`/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct/p2p/${hardcodedPeerId}`] + }) + ] }) const status = document.getElementById('status') @@ -36,22 +29,21 @@ document.addEventListener('DOMContentLoaded', async () => { } // Listen for new peers - libp2p.on('peer:discovery', (peerId) => { - log(`Found peer ${peerId.toB58String()}`) + libp2p.addEventListener('peer:discovery', (evt) => { + log(`Found peer ${evt.detail.id.toString()}`) }) // Listen for new connections to peers - libp2p.connectionManager.on('peer:connect', (connection) => { - log(`Connected to ${connection.remotePeer.toB58String()}`) + libp2p.connectionManager.addEventListener('peer:connect', (evt) => { + log(`Connected to ${evt.detail.remotePeer.toString()}`) }) // Listen for peers disconnecting - libp2p.connectionManager.on('peer:disconnect', (connection) => { - log(`Disconnected from ${connection.remotePeer.toB58String()}`) + libp2p.connectionManager.addEventListener('peer:disconnect', (evt) => { + log(`Disconnected from ${evt.detail.remotePeer.toString()}`) }) await libp2p.start() status.innerText = 'libp2p started!' - log(`libp2p id is ${libp2p.peerId.toB58String()}`) - + log(`libp2p id is ${libp2p.peerId.toString()}`) }) diff --git a/examples/webrtc-direct/listener.js b/examples/webrtc-direct/listener.js index b0b9cb4354..e27cb66b61 100644 --- a/examples/webrtc-direct/listener.js +++ b/examples/webrtc-direct/listener.js @@ -1,43 +1,34 @@ -const Libp2p = require('libp2p') -const Bootstrap = require('libp2p-bootstrap') -const WebRTCDirect = require('libp2p-webrtc-direct') -const Mplex = require('libp2p-mplex') -const { NOISE } = require('@chainsafe/libp2p-noise') -const PeerId = require('peer-id') +import { createLibp2p } from 'libp2p' +import { WebRTCDirect } from '@achingbrain/webrtc-direct' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import { createFromJSON } from '@libp2p/peer-id-factory' +import wrtc from 'wrtc' ;(async () => { // hardcoded peer id to avoid copy-pasting of listener's peer id into the dialer's bootstrap list // generated with cmd `peer-id --type=ed25519` - const hardcodedPeerId = await PeerId.createFromJSON({ + const hardcodedPeerId = await createFromJSON({ "id": "12D3KooWCuo3MdXfMgaqpLC5Houi1TRoFqgK9aoxok4NK5udMu8m", "privKey": "CAESQAG6Ld7ev6nnD0FKPs033/j0eQpjWilhxnzJ2CCTqT0+LfcWoI2Vr+zdc1vwk7XAVdyoCa2nwUR3RJebPWsF1/I=", "pubKey": "CAESIC33FqCNla/s3XNb8JO1wFXcqAmtp8FEd0SXmz1rBdfy" }) - const node = await Libp2p.create({ + const node = await createLibp2p({ peerId: hardcodedPeerId, addresses: { listen: ['/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct'] }, - modules: { - transport: [WebRTCDirect], - streamMuxer: [Mplex], - connEncryption: [NOISE] - }, - config: { - peerDiscovery: { - [Bootstrap.tag]: { - enabled: false, - } - } - } + transports: [new WebRTCDirect({ wrtc })], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()] }) - node.connectionManager.on('peer:connect', (connection) => { - console.info(`Connected to ${connection.remotePeer.toB58String()}!`) + node.connectionManager.addEventListener('peer:connect', (evt) => { + console.info(`Connected to ${evt.detail.remotePeer.toString()}!`) }) await node.start() console.log('Listening on:') - node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`)) + node.getMultiaddrs().forEach((ma) => console.log(ma.toString())) })() diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index 6eb9b4b0db..950112b1a7 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -2,32 +2,25 @@ "name": "webrtc-direct", "version": "0.0.1", "private": true, - "description": "", + "type": "module", + "browserslist": [ + "last 2 Chrome versions" + ], "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "build": "parcel build index.html", - "start": "parcel index.html" + "start": "vite" }, "license": "ISC", - "devDependencies": { - "@babel/cli": "^7.13.10", - "@babel/core": "^7.13.10", - "@mapbox/node-pre-gyp": "^1.0.8", - "babel-plugin-syntax-async-functions": "^6.13.0", - "babel-plugin-transform-regenerator": "^6.26.0", - "babel-polyfill": "^6.26.0", - "parcel": "^2.0.1", - "util": "^0.12.3" - }, "dependencies": { - "@chainsafe/libp2p-noise": "^5.0.2", + "@achingbrain/webrtc-direct": "^0.7.2", + "@chainsafe/libp2p-noise": "^6.0.1", + "@libp2p/bootstrap": "^1.0.1", + "@libp2p/mplex": "^1.0.2", "libp2p": "../../", - "libp2p-bootstrap": "^0.14.0", - "libp2p-mplex": "^0.10.4", - "libp2p-webrtc-direct": "^0.7.0", - "peer-id": "^0.16.0" + "wrtc": "^0.4.7" }, - "browser": { - "ipfs": "ipfs/dist/index.min.js" + "devDependencies": { + "@mapbox/node-pre-gyp": "^1.0.8", + "vite": "^2.8.6" } } diff --git a/examples/webrtc-direct/test.js b/examples/webrtc-direct/test.js index d6603f36dd..a658e84185 100644 --- a/examples/webrtc-direct/test.js +++ b/examples/webrtc-direct/test.js @@ -1,10 +1,11 @@ -'use strict' +import path from 'path' +import execa from 'execa' +import pDefer from 'p-defer' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { chromium } from 'playwright' +import { fileURLToPath } from 'url' -const path = require('path') -const execa = require('execa') -const pDefer = require('p-defer') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') -const { chromium } = require('playwright'); +const __dirname = path.dirname(fileURLToPath(import.meta.url)) function startNode (name, args = []) { return execa('node', [path.join(__dirname, name), ...args], { @@ -13,8 +14,8 @@ function startNode (name, args = []) { }) } -function startBrowser (name, args = []) { - return execa('parcel', [path.join(__dirname, name), ...args], { +function startBrowser () { + return execa('vite', [], { preferLocal: true, localDir: __dirname, cwd: __dirname, @@ -22,7 +23,7 @@ function startBrowser (name, args = []) { }) } -async function test () { +export async function test () { // Step 1, listener process const listenerProcReady = pDefer() let listenerOutput = '' @@ -42,20 +43,14 @@ async function test () { // Step 2, dialer process process.stdout.write('dialer.js\n') - let dialerUrl = '' - const dialerProc = startBrowser('index.html') + let dialerUrl = 'http://localhost:3000' + const dialerProc = startBrowser() dialerProc.all.on('data', async (chunk) => { /**@type {string} */ const out = chunk.toString() - if (out.includes('Server running at')) { - dialerUrl = out.split('Server running at ')[1] - } - - - if (out.includes('Built in ')) { - + if (out.includes('ready in')) { try { const browser = await chromium.launch(); const page = await browser.newPage(); @@ -71,25 +66,14 @@ async function test () { '#output', { timeout: 10000 } ) - await browser.close(); - } catch (/** @type {any} */ err) { + await browser.close() + } catch (err) { console.error(err) process.exit(1) } finally { dialerProc.cancel() - listenerProc.kill() + listenerProc.cancel() } } }) - - await Promise.all([ - listenerProc, - dialerProc, - ]).catch((err) => { - if (err.signal !== 'SIGTERM') { - throw err - } - }) } - -module.exports = test diff --git a/package.json b/package.json index 54fdb30acc..166aa40297 100644 --- a/package.json +++ b/package.json @@ -2,126 +2,146 @@ "name": "libp2p", "version": "0.36.2", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", - "leadMaintainer": "Jacob Heun ", - "main": "src/index.js", - "types": "dist/src/index.d.ts", - "typesVersions": { - "*": { - "src/*": [ - "dist/src/*", - "dist/src/*/index" - ] - } - }, - "files": [ - "dist", - "src" - ], - "scripts": { - "lint": "aegir lint", - "build": "aegir build", - "build:proto": "npm run build:proto:circuit && npm run build:proto:fetch && npm run build:proto:identify && npm run build:proto:plaintext && npm run build:proto:address-book && npm run build:proto:proto-book && npm run build:proto:peer && npm run build:proto:peer-record && npm run build:proto:envelope", - "build:proto:circuit": "pbjs -t static-module -w commonjs -r libp2p-circuit --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/circuit/protocol/index.js ./src/circuit/protocol/index.proto", - "build:proto:fetch": "pbjs -t static-module -w commonjs -r libp2p-fetch --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/fetch/proto.js ./src/fetch/proto.proto", - "build:proto:identify": "pbjs -t static-module -w commonjs -r libp2p-identify --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/identify/message.js ./src/identify/message.proto", - "build:proto:plaintext": "pbjs -t static-module -w commonjs -r libp2p-plaintext --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/insecure/proto.js ./src/insecure/proto.proto", - "build:proto:peer": "pbjs -t static-module -w commonjs -r libp2p-peer --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/peer-store/pb/peer.js ./src/peer-store/pb/peer.proto", - "build:proto:peer-record": "pbjs -t static-module -w commonjs -r libp2p-peer-record --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/record/peer-record/peer-record.js ./src/record/peer-record/peer-record.proto", - "build:proto:envelope": "pbjs -t static-module -w commonjs -r libp2p-envelope --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/record/envelope/envelope.js ./src/record/envelope/envelope.proto", - "build:proto-types": "npm run build:proto-types:circuit && npm run build:proto-types:fetch && npm run build:proto-types:identify && npm run build:proto-types:plaintext && npm run build:proto-types:address-book && npm run build:proto-types:proto-book && npm run build:proto-types:peer && npm run build:proto-types:peer-record && npm run build:proto-types:envelope", - "build:proto-types:circuit": "pbts -o src/circuit/protocol/index.d.ts src/circuit/protocol/index.js", - "build:proto-types:fetch": "pbts -o src/fetch/proto.d.ts src/fetch/proto.js", - "build:proto-types:identify": "pbts -o src/identify/message.d.ts src/identify/message.js", - "build:proto-types:plaintext": "pbts -o src/insecure/proto.d.ts src/insecure/proto.js", - "build:proto-types:peer": "pbts -o src/peer-store/pb/peer.d.ts src/peer-store/pb/peer.js", - "build:proto-types:peer-record": "pbts -o src/record/peer-record/peer-record.d.ts src/record/peer-record/peer-record.js", - "build:proto-types:envelope": "pbts -o src/record/envelope/envelope.d.ts src/record/envelope/envelope.js", - "test": "aegir test", - "test:ts": "aegir build --no-bundle && npm run test --prefix test/ts-use", - "test:node": "aegir test -t node -f \"./test/**/*.{node,spec}.js\"", - "test:browser": "aegir test -t browser", - "test:examples": "cd examples && npm run test:all", - "test:interop": "LIBP2P_JS=$PWD npx aegir test -t node -f ./node_modules/libp2p-interop/test/*", - "prepare": "npm run build", - "coverage": "nyc --reporter=text --reporter=lcov npm run test:node" - }, + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p#readme", "repository": { "type": "git", - "url": "https://github.com/libp2p/js-libp2p.git" + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" }, "keywords": [ + "IPFS", "libp2p", "network", "p2p", "peer", - "peer-to-peer", - "IPFS" + "peer-to-peer" ], - "bugs": { - "url": "https://github.com/libp2p/js-libp2p/issues" - }, - "homepage": "https://libp2p.io", - "license": "MIT", "engines": { - "node": ">=15.0.0" + "node": ">=16.0.0", + "npm": ">=7.0.0" }, - "browser": { - "nat-api": false + "type": "module", + "types": "./dist/src/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ], + "src/*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ] + } + }, + "files": [ + "src", + "dist/src", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "import": "./dist/src/index.js" + }, + "./insecure": { + "import": "./dist/src/insecure/index.js" + }, + "./pnet": { + "import": "./dist/src/pnet/index.js" + }, + "./pnet/generate": { + "import": "./dist/src/pnet/key-generator.js" + } }, "eslintConfig": { "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + }, "ignorePatterns": [ "!.aegir.js", "test/ts-use", "*.d.ts" ] }, + "scripts": { + "lint": "aegir lint", + "build": "tsc", + "postbuild": "mkdirp dist/src/circuit/pb dist/src/fetch/pb dist/src/identify/pb dist/src/insecure/pb && cp src/circuit/pb/*.js src/circuit/pb/*.d.ts dist/src/circuit/pb && cp src/fetch/pb/*.js src/fetch/pb/*.d.ts dist/src/fetch/pb && cp src/identify/pb/*.js src/identify/pb/*.d.ts dist/src/identify/pb && cp src/insecure/pb/*.js src/insecure/pb/*.d.ts dist/src/insecure/pb", + "generate": "run-s generate:proto:* generate:proto-types:*", + "generate:proto:circuit": "pbjs -t static-module -w es6 -r libp2p-circuit --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/circuit/protocol/index.js ./src/circuit/protocol/index.proto", + "generate:proto:fetch": "pbjs -t static-module -w es6 -r libp2p-fetch --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/fetch/proto.js ./src/fetch/proto.proto", + "generate:proto:identify": "pbjs -t static-module -w es6 -r libp2p-identify --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/identify/message.js ./src/identify/message.proto", + "generate:proto:plaintext": "pbjs -t static-module -w es6 -r libp2p-plaintext --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/insecure/proto.js ./src/insecure/proto.proto", + "generate:proto-types:circuit": "pbts -o src/circuit/protocol/index.d.ts src/circuit/protocol/index.js", + "generate:proto-types:fetch": "pbts -o src/fetch/proto.d.ts src/fetch/proto.js", + "generate:proto-types:identify": "pbts -o src/identify/message.d.ts src/identify/message.js", + "generate:proto-types:plaintext": "pbts -o src/insecure/proto.d.ts src/insecure/proto.js", + "pretest": "npm run build", + "test": "aegir test", + "test:node": "npm run test -- -t node -f \"./dist/test/**/*.{node,spec}.js\" --cov", + "test:chrome": "npm run test -- -t browser -f \"./dist/test/**/*.spec.js\" --cov", + "test:chrome-webworker": "npm run test -- -t webworker -f \"./dist/test/**/*.spec.js\"", + "test:firefox": "npm run test -- -t browser -f \"./dist/test/**/*.spec.js\" -- --browser firefox", + "test:firefox-webworker": "npm run test -- -t webworker -f \"./dist/test/**/*.spec.js\" -- --browser firefox", + "test:examples": "cd examples && npm run test:all", + "test:interop": "npm run test -- -t node -f dist/test/interop.js" + }, "dependencies": { - "@vascosantos/moving-average": "^1.1.0", - "abortable-iterator": "^3.0.0", - "aggregate-error": "^3.1.0", + "@achingbrain/nat-port-mapper": "^1.0.0", + "@libp2p/connection": "^1.1.4", + "@libp2p/crypto": "^0.22.9", + "@libp2p/interfaces": "^1.3.17", + "@libp2p/multistream-select": "^1.0.3", + "@libp2p/peer-id": "^1.1.8", + "@libp2p/peer-id-factory": "^1.0.8", + "@libp2p/peer-store": "^1.0.6", + "@libp2p/utils": "^1.0.9", + "@multiformats/mafmt": "^11.0.2", + "@multiformats/multiaddr": "^10.1.8", + "abortable-iterator": "^4.0.2", + "aggregate-error": "^4.0.0", "any-signal": "^3.0.0", "bignumber.js": "^9.0.1", "class-is": "^1.1.0", "datastore-core": "^7.0.0", - "debug": "^4.3.1", - "err-code": "^3.0.0", - "es6-promisify": "^7.0.0", + "debug": "^4.3.3", + "err-code": "^3.0.1", "events": "^3.3.0", "hashlru": "^2.3.0", - "interface-datastore": "^6.0.2", - "it-all": "^1.0.4", - "it-buffer": "^0.1.2", - "it-drain": "^1.0.3", - "it-filter": "^1.0.1", - "it-first": "^1.0.4", + "interface-datastore": "^6.1.0", + "it-all": "^1.0.6", + "it-drain": "^1.0.5", + "it-filter": "^1.0.3", + "it-first": "^1.0.6", "it-foreach": "^0.1.1", - "it-handshake": "^2.0.0", - "it-length-prefixed": "^5.0.2", - "it-map": "^1.0.4", - "it-merge": "^1.0.0", - "it-pipe": "^1.1.0", + "it-handshake": "^3.0.1", + "it-length-prefixed": "^7.0.1", + "it-map": "^1.0.6", + "it-merge": "^1.0.3", + "it-pipe": "^2.0.3", "it-sort": "^1.0.1", - "it-take": "^1.0.0", - "libp2p-crypto": "^0.21.2", - "libp2p-interfaces": "^4.0.0", - "libp2p-utils": "^0.4.0", - "mafmt": "^10.0.0", + "it-stream-types": "^1.0.4", + "it-take": "^1.0.2", + "it-to-buffer": "^2.0.2", "merge-options": "^3.0.4", - "mortice": "^2.0.1", - "multiaddr": "^10.0.0", - "multiformats": "^9.0.0", - "multistream-select": "^3.0.0", + "mortice": "^3.0.0", + "multiformats": "^9.6.3", "mutable-proxy": "^1.0.0", - "nat-api": "^0.3.1", "node-forge": "^1.2.1", - "p-any": "^3.0.0", "p-fifo": "^1.0.0", - "p-retry": "^4.4.0", - "p-settle": "^4.1.1", - "peer-id": "^0.16.0", - "private-ip": "^2.1.0", - "protobufjs": "^6.10.2", + "p-retry": "^5.0.0", + "p-settle": "^5.0.0", + "private-ip": "^2.3.3", + "protobufjs": "^6.11.2", "retimer": "^3.0.0", "sanitize-filename": "^1.6.3", "set-delayed-interval": "^1.0.0", @@ -133,121 +153,49 @@ "xsalsa20": "^1.1.0" }, "devDependencies": { - "@chainsafe/libp2p-noise": "^5.0.0", + "@achingbrain/libp2p-gossipsub": "^0.13.5", + "@chainsafe/libp2p-noise": "^6.0.1", + "@libp2p/bootstrap": "^1.0.2", + "@libp2p/daemon-client": "^0.0.2", + "@libp2p/daemon-server": "^0.0.2", + "@libp2p/delegated-content-routing": "^1.0.2", + "@libp2p/delegated-peer-routing": "^1.0.2", + "@libp2p/floodsub": "^1.0.2", + "@libp2p/interface-compliance-tests": "^1.1.20", + "@libp2p/interop": "^0.0.3", + "@libp2p/kad-dht": "^1.0.3", + "@libp2p/mdns": "^1.0.3", + "@libp2p/mplex": "^1.0.1", + "@libp2p/tcp": "^1.0.6", + "@libp2p/tracked-map": "^1.0.4", + "@libp2p/webrtc-star": "^1.0.3", + "@libp2p/websockets": "^1.0.3", "@nodeutils/defaults-deep": "^1.1.0", - "@types/es6-promisify": "^6.0.0", - "@types/node": "^16.0.1", + "@types/node": "^16.11.26", "@types/node-forge": "^1.0.0", + "@types/p-fifo": "^1.0.0", "@types/varint": "^6.0.0", - "aegir": "^36.0.0", + "@types/xsalsa20": "^1.1.0", + "aegir": "^36.1.3", "buffer": "^6.0.3", + "cborg": "^1.8.1", "delay": "^5.0.0", - "into-stream": "^6.0.0", - "ipfs-http-client": "^54.0.2", - "it-concat": "^2.0.0", - "it-pair": "^1.0.0", - "it-pushable": "^1.4.0", - "libp2p": ".", - "libp2p-bootstrap": "^0.14.0", - "libp2p-delegated-content-routing": "^0.11.0", - "libp2p-delegated-peer-routing": "^0.11.1", - "libp2p-interfaces-compliance-tests": "^4.0.8", - "libp2p-interop": "^0.7.1", - "libp2p-kad-dht": "^0.28.6", - "libp2p-mdns": "^0.18.0", - "libp2p-mplex": "^0.10.4", - "libp2p-tcp": "^0.17.0", - "libp2p-webrtc-star": "^0.25.0", - "libp2p-websockets": "^0.16.0", + "go-libp2p": "^0.0.6", + "into-stream": "^7.0.0", + "ipfs-http-client": "^56.0.1", + "it-pair": "^2.0.2", + "it-pushable": "^2.0.1", "nock": "^13.0.3", - "p-defer": "^3.0.0", - "p-times": "^3.0.0", - "p-wait-for": "^3.2.0", + "npm-run-all": "^4.1.5", + "p-defer": "^4.0.0", + "p-event": "^5.0.1", + "p-times": "^4.0.0", + "p-wait-for": "^4.1.0", "rimraf": "^3.0.2", - "sinon": "^12.0.1", - "util": "^0.12.3" + "sinon": "^13.0.1", + "ts-sinon": "^2.0.2" }, - "contributors": [ - "Vasco Santos ", - "David Dias ", - "Jacob Heun ", - "Alex Potsides ", - "Alan Shaw ", - "Cayman ", - "Pedro Teixeira ", - "Friedel Ziegelmayer ", - "Maciej Krüger ", - "Hugo Dias ", - "dirkmc ", - "Volker Mische ", - "Chris Dostert ", - "zeim839 <50573884+zeim839@users.noreply.github.com>", - "Robert Kiel ", - "Richard Littauer ", - "a1300 ", - "Ryan Bell ", - "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", - "Andrew Nesbitt ", - "Franck Royer ", - "Thomas Eizinger ", - "Vít Habada ", - "Giovanni T. Parra ", - "acolytec3 <17355484+acolytec3@users.noreply.github.com>", - "Alan Smithee ", - "Elven ", - "Samlior ", - "Didrik Nordström ", - "Aditya Bose <13054902+adbose@users.noreply.github.com>", - "TJKoury ", - "TheStarBoys <41286328+TheStarBoys@users.noreply.github.com>", - "Tiago Alves ", - "Tim Daubenschütz ", - "XiaoZhang ", - "Yusef Napora ", - "Zane Starr ", - "ebinks ", - "greenSnot ", - "isan_rivkin ", - "mayerwin ", - "mcclure ", - "patrickwoodhead <91056047+patrickwoodhead@users.noreply.github.com>", - "phillmac ", - "robertkiel ", - "shresthagrawal <34920931+shresthagrawal@users.noreply.github.com>", - "swedneck <40505480+swedneck@users.noreply.github.com>", - "tuyennhv ", - "Sönke Hahn ", - "Aleksei ", - "Bernd Strehl ", - "Chris Bratlien ", - "Cindy Wu ", - "Daijiro Wachi ", - "Diogo Silva ", - "Dmitriy Ryajov ", - "Ethan Lam ", - "Fei Liu ", - "Felipe Martins ", - "Florian-Merle ", - "Francis Gulotta ", - "Guy Sviry <32539816+guysv@users.noreply.github.com>", - "Henrique Dias ", - "Irakli Gozalishvili ", - "Joel Gustafson ", - "John Rees ", - "João Santos ", - "Julien Bouquillon ", - "Kevin Kwok ", - "Kevin Lacker ", - "Lars Gierth ", - "Leask Wong ", - "Marcin Tojek ", - "Marston Connell <34043723+TheMarstonConnell@users.noreply.github.com>", - "Michael Burns <5170+mburns@users.noreply.github.com>", - "Miguel Mota ", - "Nuno Nogueira ", - "Philipp Muens ", - "RasmusErik Voel Jensen ", - "Smite Chow ", - "Soeren " - ] + "browser": { + "nat-api": false + } } diff --git a/scripts/node-globals.js b/scripts/node-globals.js deleted file mode 100644 index cc0a4c9e9c..0000000000 --- a/scripts/node-globals.js +++ /dev/null @@ -1,2 +0,0 @@ -// @ts-nocheck -export const { Buffer } = require('buffer') diff --git a/src/address-manager/index.js b/src/address-manager/index.js deleted file mode 100644 index 25de94b61f..0000000000 --- a/src/address-manager/index.js +++ /dev/null @@ -1,96 +0,0 @@ -'use strict' - -const { EventEmitter } = require('events') -const { Multiaddr } = require('multiaddr') -const PeerId = require('peer-id') - -/** - * @typedef {Object} AddressManagerOptions - * @property {string[]} [listen = []] - list of multiaddrs string representation to listen. - * @property {string[]} [announce = []] - list of multiaddrs string representation to announce. - */ - -/** - * @fires AddressManager#change:addresses Emitted when a addresses change. - */ -class AddressManager extends EventEmitter { - /** - * Responsible for managing the peer addresses. - * Peers can specify their listen and announce addresses. - * The listen addresses will be used by the libp2p transports to listen for new connections, - * while the announce addresses will be used for the peer addresses' to other peers in the network. - * - * @class - * @param {PeerId} peerId - The Peer ID of the node - * @param {object} [options] - * @param {Array} [options.listen = []] - list of multiaddrs string representation to listen. - * @param {Array} [options.announce = []] - list of multiaddrs string representation to announce. - */ - constructor (peerId, { listen = [], announce = [] } = {}) { - super() - - this.peerId = peerId - this.listen = new Set(listen.map(ma => ma.toString())) - this.announce = new Set(announce.map(ma => ma.toString())) - this.observed = new Set() - } - - /** - * Get peer listen multiaddrs. - * - * @returns {Multiaddr[]} - */ - getListenAddrs () { - return Array.from(this.listen).map((a) => new Multiaddr(a)) - } - - /** - * Get peer announcing multiaddrs. - * - * @returns {Multiaddr[]} - */ - getAnnounceAddrs () { - return Array.from(this.announce).map((a) => new Multiaddr(a)) - } - - /** - * Get observed multiaddrs. - * - * @returns {Array} - */ - getObservedAddrs () { - return Array.from(this.observed).map((a) => new Multiaddr(a)) - } - - /** - * Add peer observed addresses - * - * @param {string | Multiaddr} addr - */ - addObservedAddr (addr) { - let ma = new Multiaddr(addr) - const remotePeer = ma.getPeerId() - - // strip our peer id if it has been passed - if (remotePeer) { - const remotePeerId = PeerId.createFromB58String(remotePeer) - - // use same encoding for comparison - if (remotePeerId.equals(this.peerId)) { - ma = ma.decapsulate(new Multiaddr(`/p2p/${this.peerId}`)) - } - } - - const addrString = ma.toString() - - // do not trigger the change:addresses event if we already know about this address - if (this.observed.has(addrString)) { - return - } - - this.observed.add(addrString) - this.emit('change:addresses') - } -} - -module.exports = AddressManager diff --git a/src/address-manager/index.ts b/src/address-manager/index.ts new file mode 100644 index 0000000000..d5ccdd0267 --- /dev/null +++ b/src/address-manager/index.ts @@ -0,0 +1,129 @@ +import { AddressManagerEvents, CustomEvent, EventEmitter } from '@libp2p/interfaces' +import { Multiaddr } from '@multiformats/multiaddr' +import { peerIdFromString } from '@libp2p/peer-id' +import type { Components } from '@libp2p/interfaces/components' + +export interface AddressManagerInit { + announceFilter?: AddressFilter + + /** + * list of multiaddrs string representation to listen + */ + listen?: string[] + + /** + * list of multiaddrs string representation to announce + */ + announce?: string[] + + /** + * list of multiaddrs string representation to never announce + */ + noAnnounce?: string[] +} + +export interface AddressFilter { + (addrs: Multiaddr[]): Multiaddr[] +} + +const defaultAddressFilter = (addrs: Multiaddr[]): Multiaddr[] => addrs + +export class DefaultAddressManager extends EventEmitter { + private readonly components: Components + private readonly listen: Set + private readonly announce: Set + private readonly observed: Set + private readonly announceFilter: AddressFilter + + /** + * Responsible for managing the peer addresses. + * Peers can specify their listen and announce addresses. + * The listen addresses will be used by the libp2p transports to listen for new connections, + * while the announce addresses will be used for the peer addresses' to other peers in the network. + */ + constructor (components: Components, init: AddressManagerInit) { + super() + + const { listen = [], announce = [] } = init + + this.components = components + this.listen = new Set(listen.map(ma => ma.toString())) + this.announce = new Set(announce.map(ma => ma.toString())) + this.observed = new Set() + this.announceFilter = init.announceFilter ?? defaultAddressFilter + } + + /** + * Get peer listen multiaddrs + */ + getListenAddrs (): Multiaddr[] { + return Array.from(this.listen).map((a) => new Multiaddr(a)) + } + + /** + * Get peer announcing multiaddrs + */ + getAnnounceAddrs (): Multiaddr[] { + return Array.from(this.announce).map((a) => new Multiaddr(a)) + } + + /** + * Get observed multiaddrs + */ + getObservedAddrs (): Multiaddr[] { + return Array.from(this.observed).map((a) => new Multiaddr(a)) + } + + /** + * Add peer observed addresses + */ + addObservedAddr (addr: string | Multiaddr): void { + let ma = new Multiaddr(addr) + const remotePeer = ma.getPeerId() + + // strip our peer id if it has been passed + if (remotePeer != null) { + const remotePeerId = peerIdFromString(remotePeer) + + // use same encoding for comparison + if (remotePeerId.equals(this.components.getPeerId())) { + ma = ma.decapsulate(new Multiaddr(`/p2p/${this.components.getPeerId().toString()}`)) + } + } + + const addrString = ma.toString() + + // do not trigger the change:addresses event if we already know about this address + if (this.observed.has(addrString)) { + return + } + + this.observed.add(addrString) + this.dispatchEvent(new CustomEvent('change:addresses')) + } + + getAddresses (): Multiaddr[] { + let addrs = this.getAnnounceAddrs().map(ma => ma.toString()) + + if (addrs.length === 0) { + // no configured announce addrs, add configured listen addresses + addrs = this.components.getTransportManager().getAddrs().map(ma => ma.toString()) + } + + addrs = addrs.concat(this.getObservedAddrs().map(ma => ma.toString())) + + // dedupe multiaddrs + const addrSet = new Set(addrs) + + // Create advertising list + return this.announceFilter(Array.from(addrSet) + .map(str => new Multiaddr(str))) + .map(ma => { + if (ma.getPeerId() === this.components.getPeerId().toString()) { + return ma + } + + return ma.encapsulate(`/p2p/${this.components.getPeerId().toString()}`) + }) + } +} diff --git a/src/circuit/README.md b/src/circuit/README.md index 680a76c204..cbf1dd1dc4 100644 --- a/src/circuit/README.md +++ b/src/circuit/README.md @@ -9,9 +9,9 @@ ## Table of Contents - [js-libp2p-circuit](#js-libp2p-circuit) + - [Table of Contents](#table-of-contents) - [Why?](#why) - [libp2p-circuit and IPFS](#libp2p-circuit-and-ipfs) - - [Table of Contents](#table-of-contents) - [Usage](#usage) - [API](#api) - [Implementation rational](#implementation-rational) @@ -37,22 +37,27 @@ Libp2p circuit configuration can be seen at [Setup with Relay](../../doc/CONFIGU Once you have a circuit relay node running, you can configure other nodes to use it as a relay as follows: ```js -const { Multiaddr } = require('multiaddr') -const Libp2p = require('libp2p') -const TCP = require('libp2p-tcp') -const MPLEX = require('libp2p-mplex') -const { NOISE } = require('libp2p-noise') +import { Multiaddr } from '@multiformats/multiaddr' +import Libp2p from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { NOISE } from '@chainsafe/libp2p-noise' const relayAddr = ... -const node = await Libp2p.create({ +const node = await createLibp2p({ addresses: { listen: [new Multiaddr(`${relayAddr}/p2p-circuit`)] }, - modules: { - transport: [TCP], - streamMuxer: [MPLEX], - connEncryption: [NOISE] + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] }, config: { relay: { // Circuit Relay options (this config is part of libp2p core configurations) diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js deleted file mode 100644 index e173ac039b..0000000000 --- a/src/circuit/auto-relay.js +++ /dev/null @@ -1,302 +0,0 @@ -'use strict' - -const debug = require('debug') -const log = Object.assign(debug('libp2p:auto-relay'), { - error: debug('libp2p:auto-relay:err') -}) - -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') -const { Multiaddr } = require('multiaddr') -const all = require('it-all') - -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') - -/** - * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection - * @typedef {import('../peer-store/types').Address} Address - * @typedef {import('peer-id')} PeerId - */ - -/** - * @typedef {Object} AutoRelayProperties - * @property {import('../')} libp2p - * - * @typedef {Object} AutoRelayOptions - * @property {number} [maxListeners = 1] - maximum number of relays to listen. - * @property {(error: Error, msg?: string) => {}} [onError] - */ - -class AutoRelay { - /** - * Creates an instance of AutoRelay. - * - * @class - * @param {AutoRelayProperties & AutoRelayOptions} props - */ - constructor ({ libp2p, maxListeners = 1, onError }) { - this._libp2p = libp2p - this._peerId = libp2p.peerId - this._peerStore = libp2p.peerStore - this._connectionManager = libp2p.connectionManager - this._transportManager = libp2p.transportManager - this._addressSorter = libp2p.dialer.addressSorter - - this.maxListeners = maxListeners - - /** - * @type {Set} - */ - 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) - - /** - * @param {Error} error - * @param {string} [msg] - */ - this._onError = (error, msg) => { - log.error(msg || error) - onError && onError(error, msg) - } - } - - /** - * 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 {string[]} props.protocols - * @returns {Promise} - */ - 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)) { - await 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) - if (!connection) { - return - } - - // 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) { - await this._peerStore.metadataBook.setValue(peerId, HOP_METADATA_KEY, uint8ArrayFromString(HOP_METADATA_VALUE)) - await this._addListenRelay(connection, id) - } - } catch (/** @type {any} */ err) { - this._onError(err) - } - } - - /** - * Peer disconnects. - * - * @param {Connection} connection - connection to the peer - */ - _onPeerDisconnected (connection) { - const peerId = connection.remotePeer - const id = peerId.toB58String() - - // Not listening on this relay - if (!this._listenRelays.has(id)) { - return - } - - this._removeListenRelay(id).catch(err => { - log.error(err) - }) - } - - /** - * Attempt to listen on the given relay connection. - * - * @private - * @param {Connection} connection - connection to the peer - * @param {string} id - peer identifier string - * @returns {Promise} - */ - async _addListenRelay (connection, id) { - try { - // Check if already listening on enough relays - if (this._listenRelays.size >= this.maxListeners) { - return - } - - // Get peer known addresses and sort them per public addresses first - const remoteAddrs = await this._peerStore.addressBook.getMultiaddrsForPeer( - connection.remotePeer, this._addressSorter - ) - - // Attempt to listen on relay - const result = await Promise.all( - remoteAddrs.map(async addr => { - try { - // Announce multiaddrs will update on listen success by TransportManager event being triggered - await this._transportManager.listen([new Multiaddr(`${addr.toString()}/p2p-circuit`)]) - return true - } catch (/** @type {any} */ err) { - this._onError(err) - } - - return false - }) - ) - - if (result.includes(true)) { - this._listenRelays.add(id) - } - } catch (/** @type {any} */ err) { - this._onError(err) - this._listenRelays.delete(id) - } - } - - /** - * Remove listen relay. - * - * @private - * @param {string} id - peer identifier string. - */ - async _removeListenRelay (id) { - if (this._listenRelays.delete(id)) { - // TODO: this should be responsibility of the connMgr - await 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 {string[]} [peersToIgnore] - */ - 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 = [] - const peers = await all(this._peerStore.getPeers()) - - // Check if we have known hop peers to use and attempt to listen on the already connected - for await (const { id, metadata } of peers) { - const idStr = id.toB58String() - - // Continue to next if listening on this or peer to ignore - if (this._listenRelays.has(idStr)) { - continue - } - - if (peersToIgnore.includes(idStr)) { - continue - } - - const supportsHop = metadata.get(HOP_METADATA_KEY) - - // Continue to next if it does not support Hop - if (!supportsHop || uint8ArrayToString(supportsHop) !== HOP_METADATA_VALUE) { - continue - } - - const connection = this._connectionManager.get(id) - - // If not connected, store for possible later use. - if (!connection) { - knownHopsToDial.push(id) - continue - } - - await this._addListenRelay(connection, idStr) - - // 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) { - await this._tryToListenOnRelay(peerId) - - // 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 - await this._peerStore.addressBook.add(peerId, provider.multiaddrs) - - await this._tryToListenOnRelay(peerId) - - // Check if already listening on enough relays - if (this._listenRelays.size >= this.maxListeners) { - return - } - } - } catch (/** @type {any} */ err) { - this._onError(err) - } - } - - /** - * @param {PeerId} peerId - */ - async _tryToListenOnRelay (peerId) { - try { - const connection = await this._libp2p.dial(peerId) - await this._addListenRelay(connection, peerId.toB58String()) - } catch (/** @type {any} */ err) { - this._onError(err, `could not connect and listen on known hop relay ${peerId.toB58String()}`) - } - } -} - -module.exports = AutoRelay diff --git a/src/circuit/auto-relay.ts b/src/circuit/auto-relay.ts new file mode 100644 index 0000000000..8d84a8e980 --- /dev/null +++ b/src/circuit/auto-relay.ts @@ -0,0 +1,284 @@ +import { logger } from '@libp2p/logger' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { RELAY_CODEC } from './multicodec.js' +import { canHop } from './circuit/hop.js' +import { namespaceToCid } from './utils.js' +import { + CIRCUIT_PROTO_CODE, + HOP_METADATA_KEY, + HOP_METADATA_VALUE, + RELAY_RENDEZVOUS_NS +} from './constants.js' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { AddressSorter, PeerProtocolsChangeData } from '@libp2p/interfaces/peer-store' +import type { Connection } from '@libp2p/interfaces/connection' +import type { Components } from '@libp2p/interfaces/components' +import sort from 'it-sort' +import all from 'it-all' +import { pipe } from 'it-pipe' +import { publicAddressesFirst } from '@libp2p/utils/address-sort' + +const log = logger('libp2p:auto-relay') + +const noop = () => {} + +export interface AutoRelayInit { + addressSorter?: AddressSorter + maxListeners?: number + onError?: (error: Error, msg?: string) => void +} + +export class AutoRelay { + private readonly components: Components + private readonly addressSorter: AddressSorter + private readonly maxListeners: number + private readonly listenRelays: Set + private readonly onError: (error: Error, msg?: string) => void + + constructor (components: Components, init: AutoRelayInit) { + this.components = components + this.addressSorter = init.addressSorter ?? publicAddressesFirst + this.maxListeners = init.maxListeners ?? 1 + this.listenRelays = new Set() + this.onError = init.onError ?? noop + + this._onProtocolChange = this._onProtocolChange.bind(this) + this._onPeerDisconnected = this._onPeerDisconnected.bind(this) + + this.components.getPeerStore().addEventListener('change:protocols', (evt) => { + void this._onProtocolChange(evt).catch(err => { + log.error(err) + }) + }) + this.components.getConnectionManager().addEventListener('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. + */ + async _onProtocolChange (evt: CustomEvent) { + const { + peerId, + protocols + } = evt.detail + const id = peerId.toString() + + // Check if it has the protocol + const hasProtocol = protocols.find(protocol => protocol === RELAY_CODEC) + + // If no protocol, check if we were keeping the peer before as a listenRelay + if (hasProtocol == null) { + if (this.listenRelays.has(id)) { + await this._removeListenRelay(id) + } + + return + } + + if (this.listenRelays.has(id)) { + return + } + + // If protocol, check if can hop, store info in the metadataBook and listen on it + try { + const connection = this.components.getConnectionManager().getConnection(peerId) + + if (connection == null) { + return + } + + // 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) { + await this.components.getPeerStore().metadataBook.setValue(peerId, HOP_METADATA_KEY, uint8ArrayFromString(HOP_METADATA_VALUE)) + await this._addListenRelay(connection, id) + } + } catch (err: any) { + this.onError(err) + } + } + + /** + * Peer disconnects + */ + _onPeerDisconnected (evt: CustomEvent) { + const connection = evt.detail + const peerId = connection.remotePeer + const id = peerId.toString() + + // Not listening on this relay + if (!this.listenRelays.has(id)) { + return + } + + this._removeListenRelay(id).catch(err => { + log.error(err) + }) + } + + /** + * Attempt to listen on the given relay connection + */ + async _addListenRelay (connection: Connection, id: string): Promise { + try { + // Check if already listening on enough relays + if (this.listenRelays.size >= this.maxListeners) { + return + } + + // Get peer known addresses and sort them with public addresses first + const remoteAddrs = await pipe( + await this.components.getPeerStore().addressBook.get(connection.remotePeer), + (source) => sort(source, this.addressSorter), + async (source) => await all(source) + ) + + // Attempt to listen on relay + const result = await Promise.all( + remoteAddrs.map(async addr => { + try { + let multiaddr = addr.multiaddr + + if (multiaddr.getPeerId() == null) { + multiaddr = multiaddr.encapsulate(`/p2p/${connection.remotePeer.toString()}`) + } + + multiaddr = multiaddr.encapsulate('/p2p-circuit') + + // Announce multiaddrs will update on listen success by TransportManager event being triggered + await this.components.getTransportManager().listen([multiaddr]) + return true + } catch (err: any) { + log.error('error listening on circuit address', err) + this.onError(err) + } + + return false + }) + ) + + if (result.includes(true)) { + this.listenRelays.add(id) + } + } catch (err: any) { + this.onError(err) + this.listenRelays.delete(id) + } + } + + /** + * Remove listen relay + */ + async _removeListenRelay (id: string) { + if (this.listenRelays.delete(id)) { + // TODO: this should be responsibility of the connMgr + await 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. + */ + async _listenOnAvailableHopRelays (peersToIgnore: string[] = []) { + // 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 = [] + const peers = await this.components.getPeerStore().all() + + // Check if we have known hop peers to use and attempt to listen on the already connected + for (const { id, metadata } of peers) { + const idStr = id.toString() + + // Continue to next if listening on this or peer to ignore + if (this.listenRelays.has(idStr)) { + continue + } + + if (peersToIgnore.includes(idStr)) { + continue + } + + const supportsHop = metadata.get(HOP_METADATA_KEY) + + // Continue to next if it does not support Hop + if ((supportsHop == null) || uint8ArrayToString(supportsHop) !== HOP_METADATA_VALUE) { + continue + } + + const connection = this.components.getConnectionManager().getConnection(id) + + // If not connected, store for possible later use. + if (connection == null) { + knownHopsToDial.push(id) + continue + } + + await this._addListenRelay(connection, idStr) + + // 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) { + await this._tryToListenOnRelay(peerId) + + // 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.components.getContentRouting().findProviders(cid)) { + if (provider.multiaddrs.length === 0) { + continue + } + + const peerId = provider.id + await this.components.getPeerStore().addressBook.add(peerId, provider.multiaddrs) + + await this._tryToListenOnRelay(peerId) + + // Check if already listening on enough relays + if (this.listenRelays.size >= this.maxListeners) { + return + } + } + } catch (err: any) { + this.onError(err) + } + } + + async _tryToListenOnRelay (peerId: PeerId) { + try { + const connection = await this.components.getDialer().dial(peerId) + await this._addListenRelay(connection, peerId.toString()) + } catch (err: any) { + log.error('Could not use %p as relay', peerId, err) + this.onError(err, `could not connect and listen on known hop relay ${peerId.toString()}`) + } + } +} diff --git a/src/circuit/circuit/hop.js b/src/circuit/circuit/hop.js deleted file mode 100644 index 73d9b1f855..0000000000 --- a/src/circuit/circuit/hop.js +++ /dev/null @@ -1,205 +0,0 @@ -'use strict' - -const debug = require('debug') -const log = Object.assign(debug('libp2p:circuit:hop'), { - error: debug('libp2p:circuit:hop:err') -}) -const errCode = require('err-code') - -const PeerId = require('peer-id') -const { validateAddrs } = require('./utils') -const StreamHandler = require('./stream-handler') -const { CircuitRelay: CircuitPB } = require('../protocol') -const { pipe } = require('it-pipe') -const { codes: Errors } = require('../../errors') - -const { stop } = require('./stop') - -const multicodec = require('./../multicodec') - -/** - * @typedef {import('../protocol').ICircuitRelay} ICircuitRelay - * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection - * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream - * @typedef {import('../transport')} Transport - */ - -/** - * @typedef {Object} HopRequest - * @property {Connection} connection - * @property {ICircuitRelay} request - * @property {StreamHandler} streamHandler - * @property {Transport} circuit - */ - -/** - * @param {HopRequest} options - * @returns {Promise} - */ -async function handleHop ({ - connection, - request, - streamHandler, - circuit -}) { - // Ensure hop is enabled - if (!circuit._options.hop.enabled) { - log('HOP request received but we are not acting as a relay') - return streamHandler.end({ - type: CircuitPB.Type.STATUS, - code: CircuitPB.Status.HOP_CANT_SPEAK_RELAY - }) - } - - // Validate the HOP request has the required input - try { - validateAddrs(request, streamHandler) - } catch (/** @type {any} */ err) { - return log.error('invalid hop request via peer %s', connection.remotePeer.toB58String(), err) - } - - if (!request.dstPeer) { - log('HOP request received but we do not receive a dstPeer') - return - } - - // Get the connection to the destination (stop) peer - const destinationPeer = new PeerId(request.dstPeer.id) - - const destinationConnection = circuit._connectionManager.get(destinationPeer) - if (!destinationConnection && !circuit._options.hop.active) { - log('HOP request received but we are not connected to the destination peer') - return streamHandler.end({ - type: CircuitPB.Type.STATUS, - code: CircuitPB.Status.HOP_NO_CONN_TO_DST - }) - } - - // TODO: Handle being an active relay - if (!destinationConnection) { - return - } - - // Handle the incoming HOP request by performing a STOP request - const stopRequest = { - type: CircuitPB.Type.STOP, - dstPeer: request.dstPeer, - srcPeer: request.srcPeer - } - - let destinationStream - try { - destinationStream = await stop({ - connection: destinationConnection, - request: stopRequest - }) - } catch (/** @type {any} */ err) { - return log.error(err) - } - - log('hop request from %s is valid', connection.remotePeer.toB58String()) - streamHandler.write({ - type: CircuitPB.Type.STATUS, - code: CircuitPB.Status.SUCCESS - }) - const sourceStream = streamHandler.rest() - - // Short circuit the two streams to create the relayed connection - return pipe( - sourceStream, - destinationStream, - sourceStream - ) -} - -/** - * Performs a HOP request to a relay peer, to request a connection to another - * peer. A new, virtual, connection will be created between the two via the relay. - * - * @param {object} options - * @param {Connection} options.connection - Connection to the relay - * @param {ICircuitRelay} options.request - * @returns {Promise} - */ -async function hop ({ - connection, - request -}) { - // Create a new stream to the relay - const { stream } = await connection.newStream([multicodec.relay]) - // Send the HOP request - const streamHandler = new StreamHandler({ stream }) - streamHandler.write(request) - - const response = await streamHandler.read() - - if (!response) { - throw errCode(new Error('HOP request had no response'), Errors.ERR_HOP_REQUEST_FAILED) - } - - if (response.code === CircuitPB.Status.SUCCESS) { - log('hop request was successful') - return streamHandler.rest() - } - - log('hop request failed with code %d, closing stream', response.code) - streamHandler.close() - throw errCode(new Error(`HOP request failed with code ${response.code}`), Errors.ERR_HOP_REQUEST_FAILED) -} - -/** - * 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} - */ -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 || 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 {Transport} options.circuit - * @private - */ -function handleCanHop ({ - connection, - streamHandler, - circuit -}) { - const canHop = circuit._options.hop.enabled - log('can hop (%s) request from %s', canHop, connection.remotePeer.toB58String()) - streamHandler.end({ - type: CircuitPB.Type.STATUS, - code: canHop ? CircuitPB.Status.SUCCESS : CircuitPB.Status.HOP_CANT_SPEAK_RELAY - }) -} - -module.exports = { - handleHop, - hop, - canHop, - handleCanHop -} diff --git a/src/circuit/circuit/hop.ts b/src/circuit/circuit/hop.ts new file mode 100644 index 0000000000..5440813a90 --- /dev/null +++ b/src/circuit/circuit/hop.ts @@ -0,0 +1,211 @@ +import { logger } from '@libp2p/logger' +import errCode from 'err-code' +import { validateAddrs } from './utils.js' +import { StreamHandler } from './stream-handler.js' +import { CircuitRelay as CircuitPB, ICircuitRelay } from '../pb/index.js' +import { pipe } from 'it-pipe' +import { codes as Errors } from '../../errors.js' +import { stop } from './stop.js' +import { RELAY_CODEC } from '../multicodec.js' +import type { Connection } from '@libp2p/interfaces/connection' +import { peerIdFromBytes } from '@libp2p/peer-id' +import type { Duplex } from 'it-stream-types' +import type { Circuit } from '../transport.js' +import type { ConnectionManager } from '@libp2p/interfaces/registrar' + +const log = logger('libp2p:circuit:hop') + +export interface HopRequest { + connection: Connection + request: ICircuitRelay + streamHandler: StreamHandler + circuit: Circuit + connectionManager: ConnectionManager +} + +export async function handleHop (hopRequest: HopRequest) { + const { + connection, + request, + streamHandler, + circuit, + connectionManager + } = hopRequest + + // Ensure hop is enabled + if (!circuit.hopEnabled()) { + log('HOP request received but we are not acting as a relay') + return streamHandler.end({ + type: CircuitPB.Type.STATUS, + code: CircuitPB.Status.HOP_CANT_SPEAK_RELAY + }) + } + + // Validate the HOP request has the required input + try { + validateAddrs(request, streamHandler) + } catch (err: any) { + log.error('invalid hop request via peer %p %o', connection.remotePeer, err) + + return + } + + if (request.dstPeer == null) { + log('HOP request received but we do not receive a dstPeer') + return + } + + // Get the connection to the destination (stop) peer + const destinationPeer = peerIdFromBytes(request.dstPeer.id) + + const destinationConnection = connectionManager.getConnection(destinationPeer) + if (destinationConnection == null && !circuit.hopActive()) { + log('HOP request received but we are not connected to the destination peer') + return streamHandler.end({ + type: CircuitPB.Type.STATUS, + code: CircuitPB.Status.HOP_NO_CONN_TO_DST + }) + } + + // TODO: Handle being an active relay + if (destinationConnection == null) { + log('did not have connection to remote peer') + return streamHandler.end({ + type: CircuitPB.Type.STATUS, + code: CircuitPB.Status.HOP_NO_CONN_TO_DST + }) + } + + // Handle the incoming HOP request by performing a STOP request + const stopRequest = { + type: CircuitPB.Type.STOP, + dstPeer: request.dstPeer, + srcPeer: request.srcPeer + } + + let destinationStream: Duplex + try { + log('performing STOP request') + const result = await stop({ + connection: destinationConnection, + request: stopRequest + }) + + if (result == null) { + throw new Error('Could not stop') + } + + destinationStream = result + } catch (err: any) { + log.error(err) + + return + } + + log('hop request from %p is valid', connection.remotePeer) + streamHandler.write({ + type: CircuitPB.Type.STATUS, + code: CircuitPB.Status.SUCCESS + }) + const sourceStream = streamHandler.rest() + + log('creating related connections') + // Short circuit the two streams to create the relayed connection + return await pipe( + sourceStream, + destinationStream, + sourceStream + ) +} + +export interface HopConfig { + connection: Connection + request: ICircuitRelay +} + +/** + * Performs a HOP request to a relay peer, to request a connection to another + * peer. A new, virtual, connection will be created between the two via the relay. + */ +export async function hop (options: HopConfig): Promise> { + const { + connection, + request + } = options + + // Create a new stream to the relay + const { stream } = await connection.newStream(RELAY_CODEC) + // Send the HOP request + const streamHandler = new StreamHandler({ stream }) + streamHandler.write(request) + + const response = await streamHandler.read() + + if (response == null) { + throw errCode(new Error('HOP request had no response'), Errors.ERR_HOP_REQUEST_FAILED) + } + + if (response.code === CircuitPB.Status.SUCCESS) { + log('hop request was successful') + return streamHandler.rest() + } + + log('hop request failed with code %d, closing stream', response.code) + streamHandler.close() + + throw errCode(new Error(`HOP request failed with code ${response.code}`), Errors.ERR_HOP_REQUEST_FAILED) +} + +export interface CanHopOptions { + connection: Connection +} + +/** + * Performs a CAN_HOP request to a relay peer, in order to understand its capabilities + */ +export async function canHop (options: CanHopOptions) { + const { + connection + } = options + + // Create a new stream to the relay + const { stream } = await connection.newStream(RELAY_CODEC) + + // 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 == null || response.code !== CircuitPB.Status.SUCCESS) { + return false + } + + return true +} + +export interface HandleCanHopOptions { + connection: Connection + streamHandler: StreamHandler + circuit: Circuit +} + +/** + * Creates an unencoded CAN_HOP response based on the Circuits configuration + */ +export function handleCanHop (options: HandleCanHopOptions) { + const { + connection, + streamHandler, + circuit + } = options + const canHop = circuit.hopEnabled() + log('can hop (%s) request from %p', canHop, connection.remotePeer) + streamHandler.end({ + type: CircuitPB.Type.STATUS, + code: canHop ? CircuitPB.Status.SUCCESS : CircuitPB.Status.HOP_CANT_SPEAK_RELAY + }) +} diff --git a/src/circuit/circuit/stop.js b/src/circuit/circuit/stop.js deleted file mode 100644 index 8efd00f892..0000000000 --- a/src/circuit/circuit/stop.js +++ /dev/null @@ -1,81 +0,0 @@ -'use strict' - -const debug = require('debug') -const log = Object.assign(debug('libp2p:circuit:stop'), { - error: debug('libp2p:circuit:stop:err') -}) - -const { CircuitRelay: CircuitPB } = require('../protocol') -const multicodec = require('../multicodec') -const StreamHandler = require('./stream-handler') -const { validateAddrs } = require('./utils') - -/** - * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection - * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream - * @typedef {import('../protocol').ICircuitRelay} ICircuitRelay - */ - -/** - * Handles incoming STOP requests - * - * @private - * @param {Object} options - * @param {Connection} options.connection - * @param {ICircuitRelay} options.request - The CircuitRelay protobuf request (unencoded) - * @param {StreamHandler} options.streamHandler - * @returns {Promise|void} Resolves a duplex iterable - */ -module.exports.handleStop = function handleStop ({ - connection, - request, - streamHandler -}) { - // Validate the STOP request has the required input - try { - validateAddrs(request, streamHandler) - } catch (/** @type {any} */ err) { - return log.error('invalid stop request via peer %s', connection.remotePeer.toB58String(), err) - } - - // The request is valid - log('stop request is valid') - streamHandler.write({ - type: CircuitPB.Type.STATUS, - code: CircuitPB.Status.SUCCESS - }) - return streamHandler.rest() -} - -/** - * Creates a STOP request - * - * @private - * @param {Object} options - * @param {Connection} options.connection - * @param {ICircuitRelay} options.request - The CircuitRelay protobuf request (unencoded) - * @returns {Promise} Resolves a duplex iterable - */ -module.exports.stop = async function stop ({ - connection, - request -}) { - const { stream } = await connection.newStream([multicodec.relay]) - log('starting stop request to %s', connection.remotePeer.toB58String()) - const streamHandler = new StreamHandler({ stream }) - - streamHandler.write(request) - const response = await streamHandler.read() - - if (!response) { - return streamHandler.close() - } - - if (response.code === CircuitPB.Status.SUCCESS) { - log('stop request to %s was successful', connection.remotePeer.toB58String()) - return streamHandler.rest() - } - - log('stop request failed with code %d', response.code) - streamHandler.close() -} diff --git a/src/circuit/circuit/stop.ts b/src/circuit/circuit/stop.ts new file mode 100644 index 0000000000..edef9672f4 --- /dev/null +++ b/src/circuit/circuit/stop.ts @@ -0,0 +1,78 @@ +import { logger } from '@libp2p/logger' +import { CircuitRelay as CircuitPB, ICircuitRelay } from '../pb/index.js' +import { RELAY_CODEC } from '../multicodec.js' +import { StreamHandler } from './stream-handler.js' +import { validateAddrs } from './utils.js' +import type { Connection } from '@libp2p/interfaces/connection' +import type { Duplex } from 'it-stream-types' + +const log = logger('libp2p:circuit:stop') + +export interface HandleStopOptions { + connection: Connection + request: ICircuitRelay + streamHandler: StreamHandler +} + +/** + * Handles incoming STOP requests + */ +export function handleStop (options: HandleStopOptions): Duplex | undefined { + const { + connection, + request, + streamHandler + } = options + + // Validate the STOP request has the required input + try { + validateAddrs(request, streamHandler) + } catch (err: any) { + log.error('invalid stop request via peer %p %o', connection.remotePeer, err) + return + } + + // The request is valid + log('stop request is valid') + streamHandler.write({ + type: CircuitPB.Type.STATUS, + code: CircuitPB.Status.SUCCESS + }) + + return streamHandler.rest() +} + +export interface StopOptions { + connection: Connection + request: ICircuitRelay +} + +/** + * Creates a STOP request + */ +export async function stop (options: StopOptions) { + const { + connection, + request + } = options + + const { stream } = await connection.newStream([RELAY_CODEC]) + log('starting stop request to %p', connection.remotePeer) + const streamHandler = new StreamHandler({ stream }) + + streamHandler.write(request) + const response = await streamHandler.read() + + if (response == null) { + streamHandler.close() + return + } + + if (response.code === CircuitPB.Status.SUCCESS) { + log('stop request to %p was successful', connection.remotePeer) + return streamHandler.rest() + } + + log('stop request failed with code %d', response.code) + streamHandler.close() +} diff --git a/src/circuit/circuit/stream-handler.js b/src/circuit/circuit/stream-handler.js deleted file mode 100644 index bbae7d6825..0000000000 --- a/src/circuit/circuit/stream-handler.js +++ /dev/null @@ -1,94 +0,0 @@ -'use strict' - -const debug = require('debug') -const log = Object.assign(debug('libp2p:circuit:stream-handler'), { - error: debug('libp2p:circuit:stream-handler:err') -}) - -const lp = require('it-length-prefixed') -// @ts-ignore it-handshake does not export types -const handshake = require('it-handshake') -const { CircuitRelay } = require('../protocol') - -/** - * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream - * @typedef {import('../protocol').ICircuitRelay} ICircuitRelay - */ - -class StreamHandler { - /** - * Create a stream handler for connection - * - * @class - * @param {object} options - * @param {MuxedStream} options.stream - A duplex iterable - * @param {number} [options.maxLength = 4096] - max bytes length of message - */ - constructor ({ stream, maxLength = 4096 }) { - this.stream = stream - - this.shake = handshake(this.stream) - // @ts-ignore options are not optional - this.decoder = lp.decode.fromReader(this.shake.reader, { maxDataLength: maxLength }) - } - - /** - * Read and decode message - * - * @async - */ - async read () { - const msg = await this.decoder.next() - if (msg.value) { - const value = CircuitRelay.decode(msg.value.slice()) - log('read message type', value.type) - return value - } - - log('read received no value, closing stream') - // End the stream, we didn't get data - this.close() - } - - /** - * Encode and write array of buffers - * - * @param {ICircuitRelay} msg - An unencoded CircuitRelay protobuf message - * @returns {void} - */ - write (msg) { - log('write message type %s', msg.type) - // @ts-ignore lp.encode expects type type 'Buffer | BufferList', not 'Uint8Array' - this.shake.write(lp.encode.single(CircuitRelay.encode(msg).finish())) - } - - /** - * Return the handshake rest stream and invalidate handler - * - * @returns {*} A duplex iterable - */ - rest () { - this.shake.rest() - return this.shake.stream - } - - /** - * @param {ICircuitRelay} msg - An unencoded CircuitRelay protobuf message - */ - end (msg) { - this.write(msg) - this.close() - } - - /** - * Close the stream - * - * @returns {void} - */ - close () { - log('closing the stream') - this.rest().sink([]) - } -} - -module.exports = StreamHandler diff --git a/src/circuit/circuit/stream-handler.ts b/src/circuit/circuit/stream-handler.ts new file mode 100644 index 0000000000..8b55f36ad6 --- /dev/null +++ b/src/circuit/circuit/stream-handler.ts @@ -0,0 +1,87 @@ +import { logger } from '@libp2p/logger' +import * as lp from 'it-length-prefixed' +import { Handshake, handshake } from 'it-handshake' +import { CircuitRelay, ICircuitRelay } from '../pb/index.js' +import type { Stream } from '@libp2p/interfaces/connection' +import type { Source } from 'it-stream-types' + +const log = logger('libp2p:circuit:stream-handler') + +export interface StreamHandlerOptions { + /** + * A duplex iterable + */ + stream: Stream + + /** + * max bytes length of message + */ + maxLength?: number +} + +export class StreamHandler { + private readonly stream: Stream + private readonly shake: Handshake + private readonly decoder: Source + + constructor (options: StreamHandlerOptions) { + const { stream, maxLength = 4096 } = options + + this.stream = stream + this.shake = handshake(this.stream) + this.decoder = lp.decode.fromReader(this.shake.reader, { maxDataLength: maxLength }) + } + + /** + * Read and decode message + */ + async read () { + // @ts-expect-error FIXME is a source, needs to be a generator + const msg = await this.decoder.next() + + if (msg.value != null) { + const value = CircuitRelay.decode(msg.value.slice()) + log('read message type', value.type) + return value + } + + log('read received no value, closing stream') + // End the stream, we didn't get data + this.close() + } + + /** + * Encode and write array of buffers + */ + write (msg: ICircuitRelay) { + log('write message type %s', msg.type) + // @ts-expect-error lp.encode expects type type 'Buffer | BufferList', not 'Uint8Array' + this.shake.write(lp.encode.single(CircuitRelay.encode(msg).finish())) + } + + /** + * Return the handshake rest stream and invalidate handler + */ + rest () { + this.shake.rest() + return this.shake.stream + } + + /** + * @param {ICircuitRelay} msg - An unencoded CircuitRelay protobuf message + */ + end (msg: ICircuitRelay) { + this.write(msg) + this.close() + } + + /** + * Close the stream + */ + close () { + log('closing the stream') + void this.rest().sink([]).catch(err => { + log.error(err) + }) + } +} diff --git a/src/circuit/circuit/utils.js b/src/circuit/circuit/utils.ts similarity index 50% rename from src/circuit/circuit/utils.js rename to src/circuit/circuit/utils.ts index 624d0ba490..b5fe34f2d0 100644 --- a/src/circuit/circuit/utils.js +++ b/src/circuit/circuit/utils.ts @@ -1,20 +1,11 @@ -'use strict' - -const { Multiaddr } = require('multiaddr') -const { CircuitRelay } = require('../protocol') - -/** - * @typedef {import('./stream-handler')} StreamHandler - * @typedef {import('../protocol').ICircuitRelay} ICircuitRelay - */ +import { Multiaddr } from '@multiformats/multiaddr' +import { CircuitRelay, ICircuitRelay } from '../pb/index.js' +import type { StreamHandler } from './stream-handler.js' /** * Write a response - * - * @param {StreamHandler} streamHandler - * @param {import('../protocol').CircuitRelay.Status} status */ -function writeResponse (streamHandler, status) { +function writeResponse (streamHandler: StreamHandler, status: CircuitRelay.Status) { streamHandler.write({ type: CircuitRelay.Type.STATUS, code: status @@ -23,18 +14,15 @@ function writeResponse (streamHandler, status) { /** * Validate incomming HOP/STOP message - * - * @param {ICircuitRelay} msg - A CircuitRelay unencoded protobuf message - * @param {StreamHandler} streamHandler */ -function validateAddrs (msg, streamHandler) { +export function validateAddrs (msg: ICircuitRelay, streamHandler: StreamHandler) { try { - if (msg.dstPeer && msg.dstPeer.addrs) { + if (msg.dstPeer?.addrs != null) { msg.dstPeer.addrs.forEach((addr) => { return new Multiaddr(addr) }) } - } catch (/** @type {any} */ err) { + } catch (err: any) { writeResponse(streamHandler, msg.type === CircuitRelay.Type.HOP ? CircuitRelay.Status.HOP_DST_MULTIADDR_INVALID : CircuitRelay.Status.STOP_DST_MULTIADDR_INVALID) @@ -42,19 +30,15 @@ function validateAddrs (msg, streamHandler) { } try { - if (msg.srcPeer && msg.srcPeer.addrs) { + if (msg.srcPeer?.addrs != null) { msg.srcPeer.addrs.forEach((addr) => { return new Multiaddr(addr) }) } - } catch (/** @type {any} */ err) { + } catch (err: any) { writeResponse(streamHandler, msg.type === CircuitRelay.Type.HOP ? CircuitRelay.Status.HOP_SRC_MULTIADDR_INVALID : CircuitRelay.Status.STOP_SRC_MULTIADDR_INVALID) throw err } } - -module.exports = { - validateAddrs -} diff --git a/src/circuit/constants.js b/src/circuit/constants.js deleted file mode 100644 index b4de629c63..0000000000 --- a/src/circuit/constants.js +++ /dev/null @@ -1,12 +0,0 @@ -'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 -} diff --git a/src/circuit/constants.ts b/src/circuit/constants.ts new file mode 100644 index 0000000000..8405a0fa74 --- /dev/null +++ b/src/circuit/constants.ts @@ -0,0 +1,31 @@ +const minute = 60 * 1000 + +/** + * Delay before HOP relay service is advertised on the network + */ +export const ADVERTISE_BOOT_DELAY = 15 * minute + +/** + * Delay Between HOP relay service advertisements on the network + */ +export const ADVERTISE_TTL = 30 * minute + +/** + * Multicodec code + */ +export const CIRCUIT_PROTO_CODE = 290 + +/** + * PeerStore metadaBook key for HOP relay service + */ +export const HOP_METADATA_KEY = 'hop_relay' + +/** + * PeerStore metadaBook value for HOP relay service + */ +export const HOP_METADATA_VALUE = 'true' + +/** + * Relay HOP relay service namespace for discovery + */ +export const RELAY_RENDEZVOUS_NS = '/libp2p/relay' diff --git a/src/circuit/index.js b/src/circuit/index.js deleted file mode 100644 index 06d4107a1d..0000000000 --- a/src/circuit/index.js +++ /dev/null @@ -1,102 +0,0 @@ -'use strict' - -const debug = require('debug') -const log = Object.assign(debug('libp2p:relay'), { - error: debug('libp2p:relay:err') -}) -const { codes } = require('./../errors') -const { - setDelayedInterval, - clearDelayedInterval -// @ts-ignore set-delayed-interval does not export types -} = require('set-delayed-interval') - -const AutoRelay = require('./auto-relay') -const { namespaceToCid } = require('./utils') -const { - RELAY_RENDEZVOUS_NS -} = require('./constants') - -/** - * @typedef {import('../')} Libp2p - * - * @typedef {Object} RelayAdvertiseOptions - * @property {number} [bootDelay = ADVERTISE_BOOT_DELAY] - * @property {boolean} [enabled = true] - * @property {number} [ttl = ADVERTISE_TTL] - * - * @typedef {Object} HopOptions - * @property {boolean} [enabled = false] - * @property {boolean} [active = false] - * - * @typedef {Object} AutoRelayOptions - * @property {number} [maxListeners = 2] - maximum number of relays to listen. - * @property {boolean} [enabled = false] - */ - -class Relay { - /** - * Creates an instance of Relay. - * - * @class - * @param {Libp2p} libp2p - */ - constructor (libp2p) { - this._libp2p = libp2p - this._options = { - ...libp2p._config.relay - } - - // Create autoRelay if enabled - this._autoRelay = this._options.autoRelay.enabled && new AutoRelay({ libp2p, ...this._options.autoRelay }) - - this._advertiseService = this._advertiseService.bind(this) - } - - /** - * 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 = setDelayedInterval( - this._advertiseService, this._options.advertise.ttl, this._options.advertise.bootDelay - ) - } - } - - /** - * Stop Relay service. - * - * @returns {void} - */ - stop () { - clearDelayedInterval(this._timeout) - } - - /** - * Advertise hop relay service in the network. - * - * @returns {Promise} - */ - async _advertiseService () { - try { - const cid = await namespaceToCid(RELAY_RENDEZVOUS_NS) - await this._libp2p.contentRouting.provide(cid) - } catch (/** @type {any} */ err) { - if (err.code === codes.ERR_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) - } - } - } -} - -module.exports = Relay diff --git a/src/circuit/index.ts b/src/circuit/index.ts new file mode 100644 index 0000000000..8bd66808b1 --- /dev/null +++ b/src/circuit/index.ts @@ -0,0 +1,120 @@ +import { logger } from '@libp2p/logger' +import { codes } from '../errors.js' +import { + setDelayedInterval, + clearDelayedInterval +// @ts-expect-error set-delayed-interval does not export types +} from 'set-delayed-interval' +import { AutoRelay } from './auto-relay.js' +import { namespaceToCid } from './utils.js' +import { + RELAY_RENDEZVOUS_NS +} from './constants.js' +import type { AddressSorter } from '@libp2p/interfaces/peer-store' +import type { Startable } from '@libp2p/interfaces' +import type { Components } from '@libp2p/interfaces/components' + +const log = logger('libp2p:relay') + +export interface RelayAdvertiseConfig { + bootDelay?: number + enabled?: boolean + ttl?: number +} + +export interface HopConfig { + enabled?: boolean + active?: boolean +} + +export interface AutoRelayConfig { + enabled?: boolean + + /** + * maximum number of relays to listen + */ + maxListeners: number +} + +export interface RelayInit { + addressSorter?: AddressSorter + maxListeners?: number + onError?: (error: Error, msg?: string) => void + hop: HopConfig + advertise: RelayAdvertiseConfig + autoRelay: AutoRelayConfig +} + +export class Relay implements Startable { + private readonly components: Components + private readonly init: RelayInit + // @ts-expect-error this field isn't used anywhere? + private readonly autoRelay?: AutoRelay + private timeout?: any + private started: boolean + + /** + * Creates an instance of Relay + */ + constructor (components: Components, init: RelayInit) { + this.components = components + // Create autoRelay if enabled + this.autoRelay = init.autoRelay?.enabled !== false + ? new AutoRelay(components, { + addressSorter: init.addressSorter, + ...init.autoRelay + }) + : undefined + + this.started = false + this.init = init + this._advertiseService = this._advertiseService.bind(this) + } + + isStarted () { + return this.started + } + + /** + * Start Relay service + */ + async start () { + // Advertise service if HOP enabled + if (this.init.hop.enabled !== false && this.init.advertise.enabled !== false) { + this.timeout = setDelayedInterval( + this._advertiseService, this.init.advertise.ttl, this.init.advertise.bootDelay + ) + } + + this.started = true + } + + /** + * Stop Relay service + */ + async stop () { + if (this.timeout != null) { + clearDelayedInterval(this.timeout) + } + + this.started = false + } + + /** + * Advertise hop relay service in the network. + */ + async _advertiseService () { + try { + const cid = await namespaceToCid(RELAY_RENDEZVOUS_NS) + await this.components.getContentRouting().provide(cid) + } catch (err: any) { + if (err.code === codes.ERR_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 + await this.stop() + } else { + log.error(err) + } + } + } +} diff --git a/src/circuit/listener.js b/src/circuit/listener.ts similarity index 54% rename from src/circuit/listener.js rename to src/circuit/listener.ts index a3f11dc7d4..7de4fbf9e8 100644 --- a/src/circuit/listener.js +++ b/src/circuit/listener.ts @@ -1,33 +1,27 @@ -'use strict' +import { CustomEvent, EventEmitter } from '@libp2p/interfaces' +import type { ConnectionManager } from '@libp2p/interfaces/registrar' +import type { Dialer } from '@libp2p/interfaces/dialer' +import type { Listener } from '@libp2p/interfaces/transport' +import { Multiaddr } from '@multiformats/multiaddr' -const { EventEmitter } = require('events') -const { Multiaddr } = require('multiaddr') - -/** - * @typedef {import('libp2p-interfaces/src/transport/types').Listener} Listener - */ +export interface ListenerOptions { + dialer: Dialer + connectionManager: ConnectionManager +} -/** - * @param {import('../')} libp2p - * @returns {Listener} a transport listener - */ -module.exports = (libp2p) => { +export function createListener (options: ListenerOptions): Listener { const listeningAddrs = new Map() /** * Add swarm handler and listen for incoming connections - * - * @param {Multiaddr} addr - * @returns {Promise} */ - async function listen (addr) { - const addrString = String(addr).split('/p2p-circuit').find(a => a !== '') - - const relayConn = await libp2p.dial(new Multiaddr(addrString)) + async function listen (addr: Multiaddr): Promise { + const addrString = addr.toString().split('/p2p-circuit').find(a => a !== '') + const relayConn = await options.dialer.dial(new Multiaddr(addrString)) const relayedAddr = relayConn.remoteAddr.encapsulate('/p2p-circuit') - listeningAddrs.set(relayConn.remotePeer.toB58String(), relayedAddr) - listener.emit('listening') + listeningAddrs.set(relayConn.remotePeer.toString(), relayedAddr) + listener.dispatchEvent(new CustomEvent('listening')) } /** @@ -54,20 +48,20 @@ module.exports = (libp2p) => { return addrs } - /** @type Listener */ - const listener = Object.assign(new EventEmitter(), { - close: () => Promise.resolve(), + const listener: Listener = Object.assign(new EventEmitter(), { + close: async () => await Promise.resolve(), listen, getAddrs }) // Remove listeningAddrs when a peer disconnects - libp2p.connectionManager.on('peer:disconnect', (connection) => { - const deleted = listeningAddrs.delete(connection.remotePeer.toB58String()) + options.connectionManager.addEventListener('peer:disconnect', (evt) => { + const { detail: connection } = evt + const deleted = listeningAddrs.delete(connection.remotePeer.toString()) if (deleted) { // Announce listen addresses change - listener.emit('close') + listener.dispatchEvent(new CustomEvent('close')) } }) diff --git a/src/circuit/multicodec.js b/src/circuit/multicodec.js deleted file mode 100644 index bcdb978763..0000000000 --- a/src/circuit/multicodec.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict' - -module.exports = { - relay: '/libp2p/circuit/relay/0.1.0' -} diff --git a/src/circuit/multicodec.ts b/src/circuit/multicodec.ts new file mode 100644 index 0000000000..fcd2822165 --- /dev/null +++ b/src/circuit/multicodec.ts @@ -0,0 +1,2 @@ + +export const RELAY_CODEC = '/libp2p/circuit/relay/0.1.0' diff --git a/src/circuit/protocol/index.d.ts b/src/circuit/pb/index.d.ts similarity index 100% rename from src/circuit/protocol/index.d.ts rename to src/circuit/pb/index.d.ts diff --git a/src/circuit/protocol/index.js b/src/circuit/pb/index.js similarity index 97% rename from src/circuit/protocol/index.js rename to src/circuit/pb/index.js index d929debce0..2157d6deb9 100644 --- a/src/circuit/protocol/index.js +++ b/src/circuit/pb/index.js @@ -1,15 +1,13 @@ /*eslint-disable*/ -"use strict"; - -var $protobuf = require("protobufjs/minimal"); +import $protobuf from "protobufjs/minimal.js"; // Common aliases -var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; +const $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; // Exported root namespace -var $root = $protobuf.roots["libp2p-circuit"] || ($protobuf.roots["libp2p-circuit"] = {}); +const $root = $protobuf.roots["libp2p-circuit"] || ($protobuf.roots["libp2p-circuit"] = {}); -$root.CircuitRelay = (function() { +export const CircuitRelay = $root.CircuitRelay = (() => { /** * Properties of a CircuitRelay. @@ -305,7 +303,7 @@ $root.CircuitRelay = (function() { * @property {number} MALFORMED_MESSAGE=400 MALFORMED_MESSAGE value */ CircuitRelay.Status = (function() { - var valuesById = {}, values = Object.create(valuesById); + const valuesById = {}, values = Object.create(valuesById); values[valuesById[100] = "SUCCESS"] = 100; values[valuesById[220] = "HOP_SRC_ADDR_TOO_LONG"] = 220; values[valuesById[221] = "HOP_DST_ADDR_TOO_LONG"] = 221; @@ -335,7 +333,7 @@ $root.CircuitRelay = (function() { * @property {number} CAN_HOP=4 CAN_HOP value */ CircuitRelay.Type = (function() { - var valuesById = {}, values = Object.create(valuesById); + const valuesById = {}, values = Object.create(valuesById); values[valuesById[1] = "HOP"] = 1; values[valuesById[2] = "STOP"] = 2; values[valuesById[3] = "STATUS"] = 3; @@ -527,4 +525,4 @@ $root.CircuitRelay = (function() { return CircuitRelay; })(); -module.exports = $root; +export { $root as default }; diff --git a/src/circuit/protocol/index.proto b/src/circuit/pb/index.proto similarity index 100% rename from src/circuit/protocol/index.proto rename to src/circuit/pb/index.proto diff --git a/src/circuit/transport.js b/src/circuit/transport.js deleted file mode 100644 index 5d0ad50d63..0000000000 --- a/src/circuit/transport.js +++ /dev/null @@ -1,229 +0,0 @@ -'use strict' - -const debug = require('debug') -const log = Object.assign(debug('libp2p:circuit'), { - error: debug('libp2p:circuit:err') -}) - -const errCode = require('err-code') -const mafmt = require('mafmt') -const { Multiaddr } = require('multiaddr') -const PeerId = require('peer-id') -const { CircuitRelay: CircuitPB } = require('./protocol') -const { codes } = require('../errors') - -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') - -const transportSymbol = Symbol.for('@libp2p/js-libp2p-circuit/circuit') - -/** - * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection - * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream - */ - -class Circuit { - /** - * Creates an instance of the Circuit Transport. - * - * @class - * @param {object} options - * @param {import('../')} options.libp2p - * @param {import('../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)) - } - - /** - * @param {Object} props - * @param {Connection} props.connection - * @param {MuxedStream} props.stream - */ - async _onProtocol ({ connection, stream }) { - /** @type {import('./circuit/stream-handler')} */ - 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 - }) - break - } - default: { - log('Request of type %s not supported', request.type) - } - } - - if (virtualConnection) { - // @ts-ignore dst peer will not be undefined - const remoteAddr = new Multiaddr(request.dstPeer.addrs[0]) - // @ts-ignore src peer will not be undefined - const localAddr = new Multiaddr(request.srcPeer.addrs[0]) - const maConn = toConnection({ - stream: virtualConnection, - remoteAddr, - localAddr - }) - const type = request.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 {Promise} - 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 = new Multiaddr(addrs[0]) - const destinationAddr = new Multiaddr(addrs[addrs.length - 1]) - const relayId = relayAddr.getPeerId() - const destinationId = destinationAddr.getPeerId() - - if (!relayId || !destinationId) { - const errMsg = 'Circuit relay dial failed as addresses did not have peer id' - log.error(errMsg) - throw errCode(new Error(errMsg), codes.ERR_RELAYED_DIAL) - } - - const relayPeer = PeerId.createFromB58String(relayId) - const destinationPeer = PeerId.createFromB58String(destinationId) - - 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, - request: { - type: CircuitPB.Type.HOP, - srcPeer: { - id: this.peerId.toBytes(), - addrs: this._libp2p.multiaddrs.map(addr => addr.bytes) - }, - dstPeer: { - id: destinationPeer.toBytes(), - addrs: [new 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 (/** @type {any} */ err) { - log.error('Circuit relay dial failed', err) - disconnectOnFailure && await relayConnection.close() - throw err - } - } - - /** - * Create a listener - * - * @param {any} options - * @param {Function} handler - * @returns {import('libp2p-interfaces/src/transport/types').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) - } - - /** - * Filter check for all Multiaddrs that this transport can dial on - * - * @param {Multiaddr[]} multiaddrs - * @returns {Multiaddr[]} - */ - filter (multiaddrs) { - multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs] - - return multiaddrs.filter((ma) => { - return mafmt.Circuit.matches(ma) - }) - } - - get [Symbol.toStringTag] () { - return 'Circuit' - } - - /** - * Checks if the given value is a Transport instance. - * - * @param {any} other - * @returns {other is Transport} - */ - static isTransport (other) { - return Boolean(other && other[transportSymbol]) - } -} - -module.exports = Circuit diff --git a/src/circuit/transport.ts b/src/circuit/transport.ts new file mode 100644 index 0000000000..9722d576b5 --- /dev/null +++ b/src/circuit/transport.ts @@ -0,0 +1,216 @@ +import { logger } from '@libp2p/logger' +import errCode from 'err-code' +import * as mafmt from '@multiformats/mafmt' +import { Multiaddr } from '@multiformats/multiaddr' +import { CircuitRelay as CircuitPB } from './pb/index.js' +import { codes } from '../errors.js' +import { streamToMaConnection } from '@libp2p/utils/stream-to-ma-conn' +import { RELAY_CODEC } from './multicodec.js' +import { createListener } from './listener.js' +import { handleCanHop, handleHop, hop } from './circuit/hop.js' +import { handleStop } from './circuit/stop.js' +import { StreamHandler } from './circuit/stream-handler.js' +import { symbol } from '@libp2p/interfaces/transport' +import { peerIdFromString } from '@libp2p/peer-id' +import { Components, Initializable } from '@libp2p/interfaces/components' +import type { AbortOptions } from '@libp2p/interfaces' +import type { IncomingStreamData } from '@libp2p/interfaces/registrar' +import type { Listener, Transport, CreateListenerOptions, ConnectionHandler } from '@libp2p/interfaces/transport' +import type { Connection } from '@libp2p/interfaces/connection' + +const log = logger('libp2p:circuit') + +export class Circuit implements Transport, Initializable { + private handler?: ConnectionHandler + private components: Components = new Components() + + init (components: Components): void { + this.components = components + void this.components.getRegistrar().handle(RELAY_CODEC, (data) => { + void this._onProtocol(data).catch(err => { + log.error(err) + }) + }) + .catch(err => { + log.error(err) + }) + } + + hopEnabled () { + return true + } + + hopActive () { + return true + } + + get [symbol] (): true { + return true + } + + get [Symbol.toStringTag] () { + return this.constructor.name + } + + async _onProtocol (data: IncomingStreamData) { + const { connection, stream } = data + const streamHandler = new StreamHandler({ stream }) + const request = await streamHandler.read() + + if (request == null) { + log('request was invalid, could not read from stream') + streamHandler.write({ + type: CircuitPB.Type.STATUS, + code: CircuitPB.Status.MALFORMED_MESSAGE + }) + streamHandler.close() + return + } + + let virtualConnection + + switch (request.type) { + case CircuitPB.Type.CAN_HOP: { + log('received CAN_HOP request from %p', connection.remotePeer) + await handleCanHop({ circuit: this, connection, streamHandler }) + break + } + case CircuitPB.Type.HOP: { + log('received HOP request from %p', connection.remotePeer) + virtualConnection = await handleHop({ + connection, + request, + streamHandler, + circuit: this, + connectionManager: this.components.getConnectionManager() + }) + break + } + case CircuitPB.Type.STOP: { + log('received STOP request from %p', connection.remotePeer) + virtualConnection = await handleStop({ + connection, + request, + streamHandler + }) + break + } + default: { + log('Request of type %s not supported', request.type) + streamHandler.write({ + type: CircuitPB.Type.STATUS, + code: CircuitPB.Status.MALFORMED_MESSAGE + }) + streamHandler.close() + return + } + } + + if (virtualConnection != null) { + // @ts-expect-error dst peer will not be undefined + const remoteAddr = new Multiaddr(request.dstPeer.addrs[0]) + // @ts-expect-error dst peer will not be undefined + const localAddr = new Multiaddr(request.srcPeer.addrs[0]) + const maConn = streamToMaConnection({ + stream: virtualConnection, + remoteAddr, + localAddr + }) + const type = request.type === CircuitPB.Type.HOP ? 'relay' : 'inbound' + log('new %s connection %s', type, maConn.remoteAddr) + + const conn = await this.components.getUpgrader().upgradeInbound(maConn) + log('%s connection %s upgraded', type, maConn.remoteAddr) + + if (this.handler != null) { + this.handler(conn) + } + } + } + + /** + * Dial a peer over a relay + */ + async dial (ma: Multiaddr, options: AbortOptions = {}): Promise { + // Check the multiaddr to see if it contains a relay and a destination peer + const addrs = ma.toString().split('/p2p-circuit') + const relayAddr = new Multiaddr(addrs[0]) + const destinationAddr = new Multiaddr(addrs[addrs.length - 1]) + const relayId = relayAddr.getPeerId() + const destinationId = destinationAddr.getPeerId() + + if (relayId == null || destinationId == null) { + const errMsg = 'Circuit relay dial failed as addresses did not have peer id' + log.error(errMsg) + throw errCode(new Error(errMsg), codes.ERR_RELAYED_DIAL) + } + + const relayPeer = peerIdFromString(relayId) + const destinationPeer = peerIdFromString(destinationId) + + let disconnectOnFailure = false + let relayConnection = this.components.getConnectionManager().getConnection(relayPeer) + if (relayConnection == null) { + relayConnection = await this.components.getDialer().dial(relayAddr, options) + disconnectOnFailure = true + } + + try { + const virtualConnection = await hop({ + connection: relayConnection, + request: { + type: CircuitPB.Type.HOP, + srcPeer: { + id: this.components.getPeerId().toBytes(), + addrs: this.components.getAddressManager().getAddresses().map(addr => addr.bytes) + }, + dstPeer: { + id: destinationPeer.toBytes(), + addrs: [new Multiaddr(destinationAddr).bytes] + } + } + }) + + const localAddr = relayAddr.encapsulate(`/p2p-circuit/p2p/${this.components.getPeerId().toString()}`) + const maConn = streamToMaConnection({ + stream: virtualConnection, + remoteAddr: ma, + localAddr + }) + log('new outbound connection %s', maConn.remoteAddr) + + return await this.components.getUpgrader().upgradeOutbound(maConn) + } catch (err: any) { + log.error('Circuit relay dial failed', err) + disconnectOnFailure && await relayConnection.close() + throw err + } + } + + /** + * Create a listener + */ + createListener (options: CreateListenerOptions): Listener { + // Called on successful HOP and STOP requests + this.handler = options.handler + + return createListener({ + dialer: this.components.getDialer(), + connectionManager: this.components.getConnectionManager() + }) + } + + /** + * Filter check for all Multiaddrs that this transport can dial on + * + * @param {Multiaddr[]} multiaddrs + * @returns {Multiaddr[]} + */ + filter (multiaddrs: Multiaddr[]): Multiaddr[] { + multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs] + + return multiaddrs.filter((ma) => { + return mafmt.Circuit.matches(ma) + }) + } +} diff --git a/src/circuit/utils.js b/src/circuit/utils.js deleted file mode 100644 index 7f681a54a9..0000000000 --- a/src/circuit/utils.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict' - -const { CID } = require('multiformats/cid') -const { sha256 } = require('multiformats/hashes/sha2') - -/** - * Convert a namespace string into a cid. - * - * @param {string} namespace - * @returns {Promise} - */ -module.exports.namespaceToCid = async (namespace) => { - const bytes = new TextEncoder().encode(namespace) - const hash = await sha256.digest(bytes) - - return CID.createV0(hash) -} diff --git a/src/circuit/utils.ts b/src/circuit/utils.ts new file mode 100644 index 0000000000..eb3bcd6fa8 --- /dev/null +++ b/src/circuit/utils.ts @@ -0,0 +1,12 @@ +import { CID } from 'multiformats/cid' +import { sha256 } from 'multiformats/hashes/sha2' + +/** + * Convert a namespace string into a cid + */ +export async function namespaceToCid (namespace: string): Promise { + const bytes = new TextEncoder().encode(namespace) + const hash = await sha256.digest(bytes) + + return CID.createV0(hash) +} diff --git a/src/config.js b/src/config.js deleted file mode 100644 index b2d3f6044e..0000000000 --- a/src/config.js +++ /dev/null @@ -1,114 +0,0 @@ -'use strict' - -const mergeOptions = require('merge-options') -// @ts-ignore no types in multiaddr path -const { dnsaddrResolver } = require('multiaddr/src/resolvers') - -const Constants = require('./constants') -const { AGENT_VERSION } = require('./identify/consts') -const RelayConstants = require('./circuit/constants') - -const { publicAddressesFirst } = require('libp2p-utils/src/address-sort') -const { FaultTolerance } = require('./transport-manager') - -/** - * @typedef {import('multiaddr').Multiaddr} Multiaddr - * @typedef {import('./types').ConnectionGater} ConnectionGater - * @typedef {import('.').Libp2pOptions} Libp2pOptions - * @typedef {import('.').constructorOptions} constructorOptions - */ - -const DefaultConfig = { - addresses: { - listen: [], - announce: [], - noAnnounce: [], - announceFilter: (/** @type {Multiaddr[]} */ multiaddrs) => multiaddrs - }, - connectionManager: { - minConnections: 25 - }, - connectionGater: /** @type {ConnectionGater} */ {}, - transportManager: { - faultTolerance: FaultTolerance.FATAL_ALL - }, - dialer: { - maxParallelDials: Constants.MAX_PARALLEL_DIALS, - maxDialsPerPeer: Constants.MAX_PER_PEER_DIALS, - dialTimeout: Constants.DIAL_TIMEOUT, - resolvers: { - dnsaddr: dnsaddrResolver - }, - addressSorter: publicAddressesFirst - }, - host: { - agentVersion: AGENT_VERSION - }, - metrics: { - enabled: false - }, - peerStore: { - persistence: false, - threshold: 5 - }, - peerRouting: { - refreshManager: { - enabled: true, - interval: 6e5, - bootDelay: 10e3 - } - }, - config: { - protocolPrefix: 'ipfs', - dht: { - enabled: false, - kBucketSize: 20 - }, - nat: { - enabled: true, - ttl: 7200, - keepAlive: true, - gateway: null, - externalIp: null, - pmp: { - enabled: false - } - }, - peerDiscovery: { - autoDial: true - }, - pubsub: { - enabled: true - }, - 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: {} - } -} - -/** - * @param {Libp2pOptions} opts - * @returns {DefaultConfig & Libp2pOptions & constructorOptions} - */ -module.exports.validate = (opts) => { - /** @type {DefaultConfig & Libp2pOptions & constructorOptions} */ - const resultingOptions = mergeOptions(DefaultConfig, opts) - - if (resultingOptions.modules.transport.length < 1) throw new Error("'options.modules.transport' must contain at least 1 transport") - - return resultingOptions -} diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000000..2720c8a9bd --- /dev/null +++ b/src/config.ts @@ -0,0 +1,101 @@ +import mergeOptions from 'merge-options' +import { dnsaddrResolver } from '@multiformats/multiaddr/resolvers' +import * as Constants from './constants.js' +import { AGENT_VERSION } from './identify/consts.js' +import * as RelayConstants from './circuit/constants.js' +import { publicAddressesFirst } from '@libp2p/utils/address-sort' +import { FAULT_TOLERANCE } from './transport-manager.js' +import type { Multiaddr } from '@multiformats/multiaddr' +import type { Libp2pInit } from './index.js' +import { codes, messages } from './errors.js' +import errCode from 'err-code' +import type { RecursivePartial } from '@libp2p/interfaces' + +const DefaultConfig: Partial = { + addresses: { + listen: [], + announce: [], + noAnnounce: [], + announceFilter: (multiaddrs: Multiaddr[]) => multiaddrs + }, + connectionManager: { + maxConnections: 300, + minConnections: 50, + autoDialInterval: 10000, + autoDial: true + }, + connectionGater: {}, + transportManager: { + faultTolerance: FAULT_TOLERANCE.FATAL_ALL + }, + dialer: { + maxParallelDials: Constants.MAX_PARALLEL_DIALS, + maxDialsPerPeer: Constants.MAX_PER_PEER_DIALS, + dialTimeout: Constants.DIAL_TIMEOUT, + resolvers: { + dnsaddr: dnsaddrResolver + }, + addressSorter: publicAddressesFirst + }, + host: { + agentVersion: AGENT_VERSION + }, + metrics: { + enabled: false, + computeThrottleMaxQueueSize: 1000, + computeThrottleTimeout: 2000, + movingAverageIntervals: [ + 60 * 1000, // 1 minute + 5 * 60 * 1000, // 5 minutes + 15 * 60 * 1000 // 15 minutes + ], + maxOldPeersRetention: 50 + }, + peerRouting: { + refreshManager: { + enabled: true, + interval: 6e5, + bootDelay: 10e3 + } + }, + protocolPrefix: 'ipfs', + nat: { + enabled: true, + ttl: 7200, + keepAlive: true + }, + 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 + } + } +} + +export function validateConfig (opts: RecursivePartial): Libp2pInit { + const resultingOptions: Libp2pInit = mergeOptions(DefaultConfig, opts) + + if (resultingOptions.transports == null || resultingOptions.transports.length < 1) { + throw errCode(new Error(messages.ERR_TRANSPORTS_REQUIRED), codes.ERR_TRANSPORTS_REQUIRED) + } + + if (resultingOptions.connectionEncryption == null || resultingOptions.connectionEncryption.length === 0) { + throw errCode(new Error(messages.CONN_ENCRYPTION_REQUIRED), codes.CONN_ENCRYPTION_REQUIRED) + } + + if (resultingOptions.connectionProtector === null && globalThis.process?.env?.LIBP2P_FORCE_PNET != null) { // eslint-disable-line no-undef + throw errCode(new Error(messages.ERR_PROTECTOR_REQUIRED), codes.ERR_PROTECTOR_REQUIRED) + } + + return resultingOptions +} diff --git a/src/connection-manager/auto-dialler.js b/src/connection-manager/auto-dialler.js deleted file mode 100644 index 1468c48ee7..0000000000 --- a/src/connection-manager/auto-dialler.js +++ /dev/null @@ -1,132 +0,0 @@ -'use strict' - -const debug = require('debug') -const mergeOptions = require('merge-options') -// @ts-ignore retimer does not have types -const retimer = require('retimer') -const all = require('it-all') -const { pipe } = require('it-pipe') -const filter = require('it-filter') -const sort = require('it-sort') - -const log = Object.assign(debug('libp2p:connection-manager:auto-dialler'), { - error: debug('libp2p:connection-manager:auto-dialler:err') -}) - -const defaultOptions = { - enabled: true, - minConnections: 0, - autoDialInterval: 10000 -} - -/** - * @typedef {import('../index')} Libp2p - * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection - */ - -/** - * @typedef {Object} AutoDiallerOptions - * @property {boolean} [enabled = true] - Should preemptively guarantee connections are above the low watermark - * @property {number} [minConnections = 0] - The minimum number of connections to avoid pruning - * @property {number} [autoDialInterval = 10000] - How often, in milliseconds, it should preemptively guarantee connections are above the low watermark - */ - -class AutoDialler { - /** - * 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. - * - * @class - * @param {Libp2p} libp2p - * @param {AutoDiallerOptions} options - */ - constructor (libp2p, options = {}) { - this._options = mergeOptions.call({ ignoreUndefined: true }, defaultOptions, options) - this._libp2p = libp2p - this._running = false - this._autoDialTimeout = null - this._autoDial = this._autoDial.bind(this) - - log('options: %j', this._options) - } - - /** - * Starts the auto dialer - */ - async start () { - if (!this._options.enabled) { - log('not enabled') - return - } - - this._running = true - this._autoDial().catch(err => { - log.error('could start autodial', err) - }) - log('started') - } - - /** - * Stops the auto dialler - */ - async stop () { - if (!this._options.enabled) { - log('not enabled') - return - } - - this._running = false - this._autoDialTimeout && this._autoDialTimeout.clear() - log('stopped') - } - - async _autoDial () { - const minConnections = this._options.minConnections - - // Already has enough connections - if (this._libp2p.connections.size >= minConnections) { - this._autoDialTimeout = retimer(this._autoDial, this._options.autoDialInterval) - return - } - - // Sort peers on whether we know protocols of public keys for them - // TODO: assuming the `peerStore.getPeers()` order is stable this will mean - // we keep trying to connect to the same peers? - const peers = await pipe( - this._libp2p.peerStore.getPeers(), - (source) => filter(source, (peer) => !peer.id.equals(this._libp2p.peerId)), - (source) => sort(source, (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 - }), - (source) => all(source) - ) - - for (let i = 0; this._running && i < peers.length && this._libp2p.connections.size < minConnections; i++) { - const peer = peers[i] - - if (!this._libp2p.connectionManager.get(peer.id)) { - log('connecting to a peerStore stored peer %s', peer.id.toB58String()) - try { - await this._libp2p.dialer.connectToPeer(peer.id) - } catch (/** @type {any} */ err) { - log.error('could not connect to peerStore stored peer', err) - } - } - } - - // Connection Manager was stopped - if (!this._running) { - return - } - - this._autoDialTimeout = retimer(this._autoDial, this._options.autoDialInterval) - } -} - -module.exports = AutoDialler diff --git a/src/connection-manager/auto-dialler.ts b/src/connection-manager/auto-dialler.ts new file mode 100644 index 0000000000..d14cbbe51b --- /dev/null +++ b/src/connection-manager/auto-dialler.ts @@ -0,0 +1,154 @@ +import { logger } from '@libp2p/logger' +import mergeOptions from 'merge-options' +// @ts-expect-error retimer does not have types +import retimer from 'retimer' +import all from 'it-all' +import { pipe } from 'it-pipe' +import filter from 'it-filter' +import sort from 'it-sort' +import type { Startable } from '@libp2p/interfaces' +import type { Components } from '@libp2p/interfaces/components' + +const log = logger('libp2p:connection-manager:auto-dialler') + +export interface AutoDiallerInit { + /** + * Should preemptively guarantee connections are above the low watermark + */ + enabled?: boolean + + /** + * The minimum number of connections to avoid pruning + */ + minConnections?: number + + /** + * How often, in milliseconds, it should preemptively guarantee connections are above the low watermark + */ + autoDialInterval?: number +} + +const defaultOptions: Partial = { + enabled: true, + minConnections: 0, + autoDialInterval: 10000 +} + +export class AutoDialler implements Startable { + private readonly components: Components + private readonly options: Required + private running: boolean + private autoDialTimeout?: ReturnType + + /** + * 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. + */ + constructor (components: Components, init: AutoDiallerInit) { + this.components = components + this.options = mergeOptions.call({ ignoreUndefined: true }, defaultOptions, init) + this.running = false + this._autoDial = this._autoDial.bind(this) + + log('options: %j', this.options) + } + + isStarted () { + return this.running + } + + /** + * Starts the auto dialer + */ + async start () { + if (!this.options.enabled) { + log('not enabled') + return + } + + this.running = true + + void this._autoDial().catch(err => { + log.error('could start autodial', err) + }) + + log('started') + } + + /** + * Stops the auto dialler + */ + async stop () { + if (!this.options.enabled) { + log('not enabled') + return + } + + this.running = false + + if (this.autoDialTimeout != null) { + this.autoDialTimeout.clear() + } + + log('stopped') + } + + async _autoDial () { + if (this.autoDialTimeout != null) { + this.autoDialTimeout.clear() + } + + const minConnections = this.options.minConnections + + // Already has enough connections + if (this.components.getConnectionManager().getConnectionList().length >= minConnections) { + this.autoDialTimeout = retimer(this._autoDial, this.options.autoDialInterval) + + return + } + + // Sort peers on whether we know protocols or public keys for them + const allPeers = await this.components.getPeerStore().all() + + const peers = await pipe( + // shuffle the peers + allPeers.sort(() => Math.random() > 0.5 ? 1 : -1), + (source) => filter(source, (peer) => !peer.id.equals(this.components.getPeerId())), + (source) => sort(source, (a, b) => { + if (b.protocols.length > a.protocols.length) { + return 1 + } else if (b.id.publicKey != null && a.id.publicKey == null) { + return 1 + } + return -1 + }), + async (source) => await all(source) + ) + + for (let i = 0; this.running && i < peers.length && this.components.getConnectionManager().getConnectionList().length < minConnections; i++) { + // Connection Manager was stopped during async dial + if (!this.running) { + return + } + + const peer = peers[i] + + if (this.components.getConnectionManager().getConnection(peer.id) == null) { + log('connecting to a peerStore stored peer %p', peer.id) + try { + await this.components.getDialer().dial(peer.id) + } catch (err: any) { + log.error('could not connect to peerStore stored peer', err) + } + } + } + + // Connection Manager was stopped + if (!this.running) { + return + } + + this.autoDialTimeout = retimer(this._autoDial, this.options.autoDialInterval) + } +} diff --git a/src/connection-manager/index.js b/src/connection-manager/index.js deleted file mode 100644 index eeae15597b..0000000000 --- a/src/connection-manager/index.js +++ /dev/null @@ -1,374 +0,0 @@ -'use strict' - -const debug = require('debug') -const log = Object.assign(debug('libp2p:connection-manager'), { - error: debug('libp2p:connection-manager:err') -}) - -const errcode = require('err-code') -const mergeOptions = require('merge-options') -const LatencyMonitor = require('./latency-monitor') -// @ts-ignore retimer does not have types -const retimer = require('retimer') - -const { EventEmitter } = require('events') -const trackedMap = require('../metrics/tracked-map') -const PeerId = require('peer-id') - -const { - codes: { ERR_INVALID_PARAMETERS } -} = require('../errors') - -const defaultOptions = { - maxConnections: Infinity, - minConnections: 0, - maxData: Infinity, - maxSentData: Infinity, - maxReceivedData: Infinity, - maxEventLoopDelay: Infinity, - pollInterval: 2000, - autoDialInterval: 10000, - movingAverageInterval: 60000, - defaultPeerValue: 1 -} - -const METRICS_COMPONENT = 'connection-manager' -const METRICS_PEER_CONNECTIONS = 'peer-connections' -const METRICS_PEER_VALUES = 'peer-values' - -/** - * @typedef {import('../')} Libp2p - * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection - */ - -/** - * @typedef {Object} ConnectionManagerOptions - * @property {number} [maxConnections = Infinity] - The maximum number of connections allowed. - * @property {number} [minConnections = 0] - The minimum number of connections to avoid pruning. - * @property {number} [maxData = Infinity] - The max data (in and out), per average interval to allow. - * @property {number} [maxSentData = Infinity] - The max outgoing data, per average interval to allow. - * @property {number} [maxReceivedData = Infinity] - The max incoming data, per average interval to allow. - * @property {number} [maxEventLoopDelay = Infinity] - The upper limit the event loop can take to run. - * @property {number} [pollInterval = 2000] - How often, in milliseconds, metrics and latency should be checked. - * @property {number} [movingAverageInterval = 60000] - How often, in milliseconds, to compute averages. - * @property {number} [defaultPeerValue = 1] - The value of the peer. - * @property {boolean} [autoDial = true] - Should preemptively guarantee connections are above the low watermark. - * @property {number} [autoDialInterval = 10000] - How often, in milliseconds, it should preemptively guarantee connections are above the low watermark. - */ - -/** - * - * @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 { - /** - * Responsible for managing known connections. - * - * @class - * @param {Libp2p} libp2p - * @param {ConnectionManagerOptions} options - */ - constructor (libp2p, options = {}) { - super() - - this._libp2p = libp2p - this._peerId = libp2p.peerId.toB58String() - - this._options = mergeOptions.call({ ignoreUndefined: true }, defaultOptions, options) - if (this._options.maxConnections < this._options.minConnections) { - throw errcode(new Error('Connection Manager maxConnections must be greater than minConnections'), ERR_INVALID_PARAMETERS) - } - - log('options: %j', this._options) - - /** - * Map of peer identifiers to their peer value for pruning connections. - * - * @type {Map} - */ - this._peerValues = trackedMap({ - component: METRICS_COMPONENT, - metric: METRICS_PEER_VALUES, - metrics: this._libp2p.metrics - }) - - /** - * Map of connections per peer - * - * @type {Map} - */ - this.connections = trackedMap({ - component: METRICS_COMPONENT, - metric: METRICS_PEER_CONNECTIONS, - metrics: this._libp2p.metrics - }) - - this._started = false - this._timer = null - this._checkMetrics = this._checkMetrics.bind(this) - - this._latencyMonitor = new LatencyMonitor({ - latencyCheckIntervalMs: this._options.pollInterval, - dataEmitIntervalMs: this._options.pollInterval - }) - - // This emitter gets listened to a lot - this.setMaxListeners(Infinity) - } - - /** - * Get current number of open connections. - */ - get size () { - return Array.from(this.connections.values()) - .reduce((accumulator, value) => accumulator + value.length, 0) - } - - /** - * Starts the Connection Manager. If Metrics are not enabled on libp2p - * only event loop and connection limits will be monitored. - */ - start () { - if (this._libp2p.metrics) { - this._timer = this._timer || retimer(this._checkMetrics, this._options.pollInterval) - } - - // latency monitor - this._latencyMonitor.start() - this._onLatencyMeasure = this._onLatencyMeasure.bind(this) - this._latencyMonitor.on('data', this._onLatencyMeasure) - - this._started = true - log('started') - } - - /** - * Stops the Connection Manager - * - * @async - */ - async stop () { - this._timer && this._timer.clear() - - this._latencyMonitor.removeListener('data', this._onLatencyMeasure) - this._latencyMonitor.stop() - - this._started = false - await this._close() - log('stopped') - } - - /** - * Cleans up the connections - * - * @async - */ - async _close () { - // Close all connections we're tracking - const tasks = [] - for (const connectionList of this.connections.values()) { - for (const connection of connectionList) { - tasks.push(connection.close()) - } - } - - await Promise.all(tasks) - this.connections.clear() - } - - /** - * 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 - * @returns {void} - */ - setPeerValue (peerId, value) { - if (value < 0 || value > 1) { - throw new Error('value should be a number between 0 and 1') - } - this._peerValues.set(peerId.toB58String(), value) - } - - /** - * Checks the libp2p metrics to determine if any values have exceeded - * the configured maximums. - * - * @private - */ - async _checkMetrics () { - if (this._libp2p.metrics) { - try { - const movingAverages = this._libp2p.metrics.global.movingAverages - // @ts-ignore moving averages object types - const received = movingAverages.dataReceived[this._options.movingAverageInterval].movingAverage() - await this._checkMaxLimit('maxReceivedData', received) - // @ts-ignore moving averages object types - const sent = movingAverages.dataSent[this._options.movingAverageInterval].movingAverage() - await this._checkMaxLimit('maxSentData', sent) - const total = received + sent - await this._checkMaxLimit('maxData', total) - log('metrics update', total) - } finally { - this._timer = retimer(this._checkMetrics, this._options.pollInterval) - } - } - } - - /** - * Tracks the incoming connection and check the connection limit - * - * @param {Connection} connection - */ - async onConnect (connection) { - if (!this._started) { - // This can happen when we are in the process of shutting down the node - await connection.close() - return - } - - const peerId = connection.remotePeer - const peerIdStr = peerId.toB58String() - const storedConn = this.connections.get(peerIdStr) - - this.emit('peer:connect', connection) - - if (storedConn) { - storedConn.push(connection) - } else { - this.connections.set(peerIdStr, [connection]) - } - - await this._libp2p.peerStore.keyBook.set(peerId, peerId.pubKey) - - if (!this._peerValues.has(peerIdStr)) { - this._peerValues.set(peerIdStr, this._options.defaultPeerValue) - } - - await this._checkMaxLimit('maxConnections', this.size) - } - - /** - * Removes the connection from tracking - * - * @param {Connection} connection - * @returns {void} - */ - onDisconnect (connection) { - if (!this._started) { - // This can happen when we are in the process of shutting down the node - return - } - - const peerId = connection.remotePeer.toB58String() - let storedConn = this.connections.get(peerId) - - if (storedConn && storedConn.length > 1) { - storedConn = storedConn.filter((conn) => conn.id !== connection.id) - this.connections.set(peerId, storedConn) - } else if (storedConn) { - this.connections.delete(peerId) - this._peerValues.delete(connection.remotePeer.toB58String()) - this.emit('peer:disconnect', connection) - - this._libp2p.metrics && this._libp2p.metrics.onPeerDisconnected(connection.remotePeer) - } - } - - /** - * Get a connection with a peer. - * - * @param {PeerId} peerId - * @returns {Connection|null} - */ - 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 {Connection[]} - */ - getAll (peerId) { - if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) - } - - const id = peerId.toB58String() - const connections = this.connections.get(id) - - // Return all open connections - if (connections) { - return connections.filter(connection => connection.stat.status === 'open') - } - return [] - } - - /** - * If the event loop is slow, maybe close a connection - * - * @private - * @param {*} summary - The LatencyMonitor summary - */ - _onLatencyMeasure (summary) { - this._checkMaxLimit('maxEventLoopDelay', summary.avgMs) - .catch(err => { - log.error(err) - }) - } - - /** - * 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 - */ - async _checkMaxLimit (name, value) { - const limit = this._options[name] - log('checking limit of %s. current value: %d of %d', name, value, limit) - if (value > limit) { - log('%s: limit exceeded: %s, %d', this._peerId, name, value) - await this._maybeDisconnectOne() - } - } - - /** - * If we have more connections than our maximum, close a connection - * to the lowest valued peer. - * - * @private - */ - async _maybeDisconnectOne () { - if (this._options.minConnections < this.connections.size) { - const peerValues = Array.from(new Map([...this._peerValues.entries()].sort((a, b) => a[1] - b[1]))) - log('%s: sorted peer values: %j', this._peerId, peerValues) - const disconnectPeer = peerValues[0] - if (disconnectPeer) { - const peerId = disconnectPeer[0] - 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().catch(err => { - log.error(err) - }) - // TODO: should not need to invoke this manually - this.onDisconnect(connections[0]) - break - } - } - } - } - } -} - -module.exports = ConnectionManager diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts new file mode 100644 index 0000000000..ad165d87e8 --- /dev/null +++ b/src/connection-manager/index.ts @@ -0,0 +1,422 @@ +import { logger } from '@libp2p/logger' +import errCode from 'err-code' +import mergeOptions from 'merge-options' +import { LatencyMonitor, SummaryObject } from './latency-monitor.js' +// @ts-expect-error retimer does not have types +import retimer from 'retimer' +import { CustomEvent, EventEmitter, Startable } from '@libp2p/interfaces' +import { trackedMap } from '@libp2p/tracked-map' +import { codes } from '../errors.js' +import { isPeerId, PeerId } from '@libp2p/interfaces/peer-id' +// @ts-expect-error setMaxListeners is missing from the node 16 types +import { setMaxListeners } from 'events' +import type { Connection } from '@libp2p/interfaces/connection' +import type { ConnectionManager } from '@libp2p/interfaces/registrar' +import type { Components } from '@libp2p/interfaces/components' +import * as STATUS from '@libp2p/interfaces/connection/status' + +const log = logger('libp2p:connection-manager') + +const defaultOptions: Partial = { + maxConnections: Infinity, + minConnections: 0, + maxData: Infinity, + maxSentData: Infinity, + maxReceivedData: Infinity, + maxEventLoopDelay: Infinity, + pollInterval: 2000, + autoDialInterval: 10000, + movingAverageInterval: 60000, + defaultPeerValue: 1 +} + +const METRICS_COMPONENT = 'connection-manager' +const METRICS_PEER_CONNECTIONS = 'peer-connections' +const METRICS_PEER_VALUES = 'peer-values' + +export interface ConnectionManagerEvents { + 'peer:connect': CustomEvent + 'peer:disconnect': CustomEvent +} + +export interface ConnectionManagerInit { + /** + * The maximum number of connections allowed + */ + maxConnections?: number + + /** + * The minimum number of connections to avoid pruning + */ + minConnections?: number + + /** + * The max data (in and out), per average interval to allow + */ + maxData?: number + + /** + * The max outgoing data, per average interval to allow + */ + maxSentData?: number + + /** + * The max incoming data, per average interval to allow + */ + maxReceivedData?: number + + /** + * The upper limit the event loop can take to run + */ + maxEventLoopDelay?: number + + /** + * How often, in milliseconds, metrics and latency should be checked + */ + pollInterval?: number + + /** + * How often, in milliseconds, to compute averages + */ + movingAverageInterval?: number + + /** + * The value of the peer + */ + defaultPeerValue?: number + + /** + * Should preemptively guarantee connections are above the low watermark + */ + autoDial?: boolean + + /** + * How often, in milliseconds, it should preemptively guarantee connections are above the low watermark + */ + autoDialInterval?: number +} + +/** + * Responsible for managing known connections. + */ +export class DefaultConnectionManager extends EventEmitter implements ConnectionManager, Startable { + private readonly components: Components + private readonly init: Required + private readonly peerValues: Map + private readonly connections: Map + private started: boolean + private timer?: ReturnType + private readonly latencyMonitor: LatencyMonitor + + constructor (components: Components, init: ConnectionManagerInit = {}) { + super() + + this.components = components + this.init = mergeOptions.call({ ignoreUndefined: true }, defaultOptions, init) + + if (this.init.maxConnections < this.init.minConnections) { + throw errCode(new Error('Connection Manager maxConnections must be greater than minConnections'), codes.ERR_INVALID_PARAMETERS) + } + + log('options: %o', this.init) + + /** + * Map of peer identifiers to their peer value for pruning connections. + * + * @type {Map} + */ + this.peerValues = trackedMap({ + component: METRICS_COMPONENT, + metric: METRICS_PEER_VALUES, + metrics: this.components.getMetrics() + }) + + /** + * Map of connections per peer + */ + this.connections = trackedMap({ + component: METRICS_COMPONENT, + metric: METRICS_PEER_CONNECTIONS, + metrics: this.components.getMetrics() + }) + + this.started = false + this._checkMetrics = this._checkMetrics.bind(this) + + this.latencyMonitor = new LatencyMonitor({ + latencyCheckIntervalMs: init.pollInterval, + dataEmitIntervalMs: init.pollInterval + }) + + try { + // This emitter gets listened to a lot + setMaxListeners?.(Infinity, this) + } catch {} + + this.components.getUpgrader().addEventListener('connection', (evt) => { + void this.onConnect(evt).catch(err => { + log.error(err) + }) + }) + this.components.getUpgrader().addEventListener('connectionEnd', this.onDisconnect.bind(this)) + } + + isStarted () { + return this.started + } + + /** + * Starts the Connection Manager. If Metrics are not enabled on libp2p + * only event loop and connection limits will be monitored. + */ + async start () { + if (this.components.getMetrics() != null) { + this.timer = this.timer ?? retimer(this._checkMetrics, this.init.pollInterval) + } + + // latency monitor + this.latencyMonitor.start() + this._onLatencyMeasure = this._onLatencyMeasure.bind(this) + this.latencyMonitor.addEventListener('data', this._onLatencyMeasure) + + this.started = true + log('started') + } + + /** + * Stops the Connection Manager + */ + async stop () { + this.timer?.clear() + + this.latencyMonitor.removeEventListener('data', this._onLatencyMeasure) + this.latencyMonitor.stop() + + this.started = false + await this._close() + log('stopped') + } + + /** + * Cleans up the connections + */ + async _close () { + // Close all connections we're tracking + const tasks = [] + for (const connectionList of this.connections.values()) { + for (const connection of connectionList) { + tasks.push(connection.close()) + } + } + + log('closing %d connections', tasks.length) + await Promise.all(tasks) + this.connections.clear() + } + + /** + * Sets the value of the given peer. Peers with lower values + * will be disconnected first. + */ + setPeerValue (peerId: PeerId, value: number) { + if (value < 0 || value > 1) { + throw new Error('value should be a number between 0 and 1') + } + + this.peerValues.set(peerId.toString(), value) + } + + /** + * Checks the libp2p metrics to determine if any values have exceeded + * the configured maximums. + * + * @private + */ + async _checkMetrics () { + const metrics = this.components.getMetrics() + + if (metrics != null) { + try { + const movingAverages = metrics.getGlobal().getMovingAverages() + const received = movingAverages.dataReceived[this.init.movingAverageInterval].movingAverage + await this._checkMaxLimit('maxReceivedData', received) + const sent = movingAverages.dataSent[this.init.movingAverageInterval].movingAverage + await this._checkMaxLimit('maxSentData', sent) + const total = received + sent + await this._checkMaxLimit('maxData', total) + log('metrics update', total) + } finally { + this.timer = retimer(this._checkMetrics, this.init.pollInterval) + } + } + } + + /** + * Tracks the incoming connection and check the connection limit + */ + async onConnect (evt: CustomEvent) { + const { detail: connection } = evt + + if (!this.started) { + // This can happen when we are in the process of shutting down the node + await connection.close() + return + } + + const peerId = connection.remotePeer + const peerIdStr = peerId.toString() + const storedConns = this.connections.get(peerIdStr) + + this.dispatchEvent(new CustomEvent('peer:connect', { detail: connection })) + + if (storedConns != null) { + storedConns.push(connection) + } else { + this.connections.set(peerIdStr, [connection]) + } + + if (peerId.publicKey != null) { + await this.components.getPeerStore().keyBook.set(peerId, peerId.publicKey) + } + + if (!this.peerValues.has(peerIdStr)) { + this.peerValues.set(peerIdStr, this.init.defaultPeerValue) + } + + await this._checkMaxLimit('maxConnections', this.getConnectionList().length) + } + + /** + * Removes the connection from tracking + */ + onDisconnect (evt: CustomEvent) { + const { detail: connection } = evt + + if (!this.started) { + // This can happen when we are in the process of shutting down the node + return + } + + const peerId = connection.remotePeer.toString() + let storedConn = this.connections.get(peerId) + + if (storedConn != null && storedConn.length > 1) { + storedConn = storedConn.filter((conn) => conn.id !== connection.id) + this.connections.set(peerId, storedConn) + } else if (storedConn != null) { + this.connections.delete(peerId) + this.peerValues.delete(connection.remotePeer.toString()) + this.dispatchEvent(new CustomEvent('peer:disconnect', { detail: connection })) + + this.components.getMetrics()?.onPeerDisconnected(connection.remotePeer) + } + } + + getConnectionMap (): Map { + return this.connections + } + + getConnectionList (): Connection[] { + let output: Connection[] = [] + + for (const connections of this.connections.values()) { + output = output.concat(connections) + } + + return output + } + + getConnections (peerId: PeerId): Connection[] { + return this.connections.get(peerId.toString()) ?? [] + } + + /** + * Get a connection with a peer + */ + getConnection (peerId: PeerId): Connection | undefined { + const connections = this.getAll(peerId) + + if (connections.length > 0) { + return connections[0] + } + + return undefined + } + + /** + * Get all open connections with a peer + */ + getAll (peerId: PeerId): Connection[] { + if (!isPeerId(peerId)) { + throw errCode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) + } + + const id = peerId.toString() + const connections = this.connections.get(id) + + // Return all open connections + if (connections != null) { + return connections.filter(connection => connection.stat.status === STATUS.OPEN) + } + + return [] + } + + /** + * If the event loop is slow, maybe close a connection + */ + _onLatencyMeasure (evt: CustomEvent) { + const { detail: summary } = evt + + this._checkMaxLimit('maxEventLoopDelay', summary.avgMs) + .catch(err => { + log.error(err) + }) + } + + /** + * If the `value` of `name` has exceeded its limit, maybe close a connection + */ + async _checkMaxLimit (name: keyof ConnectionManagerInit, value: number) { + const limit = this.init[name] + log.trace('checking limit of %s. current value: %d of %d', name, value, limit) + if (value > limit) { + log('%s: limit exceeded: %p, %d', this.components.getPeerId(), name, value) + await this._maybeDisconnectOne() + } + } + + /** + * If we have more connections than our maximum, close a connection + * to the lowest valued peer. + */ + async _maybeDisconnectOne () { + if (this.init.minConnections < this.connections.size) { + const peerValues = Array.from(new Map([...this.peerValues.entries()].sort((a, b) => a[1] - b[1]))) + + log('%p: sorted peer values: %j', this.components.getPeerId(), peerValues) + const disconnectPeer = peerValues[0] + + if (disconnectPeer != null) { + const peerId = disconnectPeer[0] + log('%p: lowest value peer is %s', this.components.getPeerId(), peerId) + log('%p: closing a connection to %j', this.components.getPeerId(), peerId) + + for (const connections of this.connections.values()) { + if (connections[0].remotePeer.toString() === peerId) { + void connections[0].close() + .catch(err => { + log.error(err) + }) + + // TODO: should not need to invoke this manually + this.onDisconnect(new CustomEvent('connectionEnd', { + detail: connections[0] + })) + break + } + } + } + } + } +} diff --git a/src/connection-manager/latency-monitor.js b/src/connection-manager/latency-monitor.js deleted file mode 100644 index 374794c85e..0000000000 --- a/src/connection-manager/latency-monitor.js +++ /dev/null @@ -1,264 +0,0 @@ -// @ts-nocheck -'use strict' - -/** - * This code is based on `latency-monitor` (https://github.com/mlucool/latency-monitor) by `mlucool` (https://github.com/mlucool), available under Apache License 2.0 (https://github.com/mlucool/latency-monitor/blob/master/LICENSE) - */ - -const { EventEmitter } = require('events') -const VisibilityChangeEmitter = require('./visibility-change-emitter') -const debug = require('debug')('latency-monitor:LatencyMonitor') - -/** - * @typedef {Object} SummaryObject - * @property {number} events How many events were called - * @property {number} minMS What was the min time for a cb to be called - * @property {number} maxMS What was the max time for a cb to be called - * @property {number} avgMs What was the average time for a cb to be called - * @property {number} lengthMs How long this interval was in ms - * - * @typedef {Object} LatencyMonitorOptions - * @property {number} [latencyCheckIntervalMs=500] - How often to add a latency check event (ms) - * @property {number} [dataEmitIntervalMs=5000] - How often to summarize latency check events. null or 0 disables event firing - * @property {Function} [asyncTestFn] - What cb-style async function to use - * @property {number} [latencyRandomPercentage=5] - What percent (+/-) of latencyCheckIntervalMs should we randomly use? This helps avoid alignment to other events. - */ - -/** - * A class to monitor latency of any async function which works in a browser or node. This works by periodically calling - * the asyncTestFn and timing how long it takes the callback to be called. It can also periodically emit stats about this. - * This can be disabled and stats can be pulled via setting dataEmitIntervalMs = 0. - * - * @extends {EventEmitter} - * - * The default implementation is an event loop latency monitor. This works by firing periodic events into the event loop - * and timing how long it takes to get back. - * - * @example - * const monitor = new LatencyMonitor(); - * monitor.on('data', (summary) => console.log('Event Loop Latency: %O', summary)); - * - * @example - * const monitor = new LatencyMonitor({latencyCheckIntervalMs: 1000, dataEmitIntervalMs: 60000, asyncTestFn:ping}); - * monitor.on('data', (summary) => console.log('Ping Pong Latency: %O', summary)); - */ -class LatencyMonitor extends EventEmitter { - /** - * @class - * @param {LatencyMonitorOptions} [options] - */ - constructor ({ latencyCheckIntervalMs, dataEmitIntervalMs, asyncTestFn, latencyRandomPercentage } = {}) { - super() - const that = this - - // 0 isn't valid here, so its ok to use || - that.latencyCheckIntervalMs = latencyCheckIntervalMs || 500 // 0.5s - that.latencyRandomPercentage = latencyRandomPercentage || 10 - that._latecyCheckMultiply = 2 * (that.latencyRandomPercentage / 100.0) * that.latencyCheckIntervalMs - that._latecyCheckSubtract = that._latecyCheckMultiply / 2 - - that.dataEmitIntervalMs = (dataEmitIntervalMs === null || dataEmitIntervalMs === 0) - ? undefined - : dataEmitIntervalMs || 5 * 1000 // 5s - debug('latencyCheckIntervalMs: %s dataEmitIntervalMs: %s', - that.latencyCheckIntervalMs, that.dataEmitIntervalMs) - if (that.dataEmitIntervalMs) { - debug('Expecting ~%s events per summary', that.latencyCheckIntervalMs / that.dataEmitIntervalMs) - } else { - debug('Not emitting summaries') - } - - that.asyncTestFn = asyncTestFn // If there is no asyncFn, we measure latency - } - - start () { - // If process: use high resolution timer - if (globalThis.process && globalThis.process.hrtime) { // eslint-disable-line no-undef - debug('Using process.hrtime for timing') - this.now = globalThis.process.hrtime // eslint-disable-line no-undef - this.getDeltaMS = (startTime) => { - const hrtime = this.now(startTime) - return (hrtime[0] * 1000) + (hrtime[1] / 1000000) - } - // Let's try for a timer that only monotonically increases - } else if (typeof window !== 'undefined' && window.performance && window.performance.now) { - debug('Using performance.now for timing') - this.now = window.performance.now.bind(window.performance) - this.getDeltaMS = (startTime) => Math.round(this.now() - startTime) - } else { - debug('Using Date.now for timing') - this.now = Date.now - this.getDeltaMS = (startTime) => this.now() - startTime - } - - this._latencyData = this._initLatencyData() - - // We check for isBrowser because of browsers set max rates of timeouts when a page is hidden, - // so we fall back to another library - // See: http://stackoverflow.com/questions/6032429/chrome-timeouts-interval-suspended-in-background-tabs - if (isBrowser()) { - this._visibilityChangeEmitter = new VisibilityChangeEmitter() - - this._visibilityChangeEmitter.on('visibilityChange', (pageInFocus) => { - if (pageInFocus) { - this._startTimers() - } else { - this._emitSummary() - this._stopTimers() - } - }) - } - - if (!this._visibilityChangeEmitter || this._visibilityChangeEmitter.isVisible()) { - this._startTimers() - } - } - - stop () { - this._stopTimers() - } - - /** - * Start internal timers - * - * @private - */ - _startTimers () { - // Timer already started, ignore this - if (this._checkLatencyID) { - return - } - this._checkLatency() - if (this.dataEmitIntervalMs) { - this._emitIntervalID = setInterval(() => this._emitSummary(), this.dataEmitIntervalMs) - if (typeof this._emitIntervalID.unref === 'function') { - this._emitIntervalID.unref() // Doesn't block exit - } - } - } - - /** - * Stop internal timers - * - * @private - */ - _stopTimers () { - if (this._checkLatencyID) { - clearTimeout(this._checkLatencyID) - this._checkLatencyID = undefined - } - if (this._emitIntervalID) { - clearInterval(this._emitIntervalID) - this._emitIntervalID = undefined - } - } - - /** - * Emit summary only if there were events. It might not have any events if it was forced via a page hidden/show - * - * @private - */ - _emitSummary () { - const summary = this.getSummary() - if (summary.events > 0) { - this.emit('data', summary) - } - } - - /** - * Calling this function will end the collection period. If a timing event was already fired and somewhere in the queue, - * it will not count for this time period - * - * @returns {SummaryObject} - */ - getSummary () { - // We might want to adjust for the number of expected events - // Example: first 1 event it comes back, then such a long blocker that the next emit check comes - // Then this fires - looks like no latency!! - const latency = { - events: this._latencyData.events, - minMs: this._latencyData.minMs, - maxMs: this._latencyData.maxMs, - avgMs: this._latencyData.events - ? this._latencyData.totalMs / this._latencyData.events - : Number.POSITIVE_INFINITY, - lengthMs: this.getDeltaMS(this._latencyData.startTime) - } - this._latencyData = this._initLatencyData() // Clear - - debug('Summary: %O', latency) - return latency - } - - /** - * Randomly calls an async fn every roughly latencyCheckIntervalMs (plus some randomness). If no async fn is found, - * it will simply report on event loop latency. - * - * @private - */ - _checkLatency () { - const that = this - // Randomness is needed to avoid alignment by accident to regular things in the event loop - const randomness = (Math.random() * that._latecyCheckMultiply) - that._latecyCheckSubtract - - // We use this to ensure that in case some overlap somehow, we don't take the wrong startTime/offset - const localData = { - deltaOffset: Math.ceil(that.latencyCheckIntervalMs + randomness), - startTime: that.now() - } - - const cb = () => { - // We are already stopped, ignore this datapoint - if (!this._checkLatencyID) { - return - } - const deltaMS = that.getDeltaMS(localData.startTime) - localData.deltaOffset - that._checkLatency() // Start again ASAP - - // Add the data point. If this gets complex, refactor it - that._latencyData.events++ - that._latencyData.minMs = Math.min(that._latencyData.minMs, deltaMS) - that._latencyData.maxMs = Math.max(that._latencyData.maxMs, deltaMS) - that._latencyData.totalMs += deltaMS - debug('MS: %s Data: %O', deltaMS, that._latencyData) - } - debug('localData: %O', localData) - - this._checkLatencyID = setTimeout(() => { - // This gets rid of including event loop - if (that.asyncTestFn) { - // Clear timing related things - localData.deltaOffset = 0 - localData.startTime = that.now() - that.asyncTestFn(cb) - } else { - // setTimeout is not more accurate than 1ms, so this will ensure positive numbers. Add 1 to emitted data to remove. - // This is not the best, but for now it'll be just fine. This isn't meant to be sub ms accurate. - localData.deltaOffset -= 1 - // If there is no function to test, we mean check latency which is a special case that is really cb => cb() - // We avoid that for the few extra function all overheads. Also, we want to keep the timers different - cb() - } - }, localData.deltaOffset) - - if (typeof this._checkLatencyID.unref === 'function') { - this._checkLatencyID.unref() // Doesn't block exit - } - } - - _initLatencyData () { - return { - startTime: this.now(), - minMs: Number.POSITIVE_INFINITY, - maxMs: Number.NEGATIVE_INFINITY, - events: 0, - totalMs: 0 - } - } -} - -function isBrowser () { - return typeof window !== 'undefined' -} - -module.exports = LatencyMonitor diff --git a/src/connection-manager/latency-monitor.ts b/src/connection-manager/latency-monitor.ts new file mode 100644 index 0000000000..bb7ce62633 --- /dev/null +++ b/src/connection-manager/latency-monitor.ts @@ -0,0 +1,319 @@ +/** + * This code is based on `latency-monitor` (https://github.com/mlucool/latency-monitor) by `mlucool` (https://github.com/mlucool), available under Apache License 2.0 (https://github.com/mlucool/latency-monitor/blob/master/LICENSE) + */ + +import { CustomEvent, EventEmitter } from '@libp2p/interfaces' +import { VisibilityChangeEmitter } from './visibility-change-emitter.js' +import { logger } from '@libp2p/logger' + +const log = logger('libp2p:connection-manager:latency-monitor') + +export interface LatencyMonitorEvents { + 'data': CustomEvent +} + +export interface LatencyMonitorInit { + /** + * How often to add a latency check event (ms) + */ + latencyCheckIntervalMs?: number + + /** + * How often to summarize latency check events. null or 0 disables event firing + */ + dataEmitIntervalMs?: number + + /** + * What cb-style async function to use + */ + asyncTestFn?: (cb: () => void) => void + + /** + * What percent (+/-) of latencyCheckIntervalMs should we randomly use? This helps avoid alignment to other events. + */ + latencyRandomPercentage?: number +} + +export interface SummaryObject { + /** + * How many events were called + */ + events: number + + /** + * What was the min time for a cb to be called + */ + minMs: number + + /** + * What was the max time for a cb to be called + */ + maxMs: number + + /** + * What was the average time for a cb to be called + */ + avgMs: number + + /** + * How long this interval was in ms + */ + lengthMs: number +} + +interface LatencyData { + startTime: number + events: number + minMs: number + maxMs: number + totalMs: number +} + +/** + * A class to monitor latency of any async function which works in a browser or node. This works by periodically calling + * the asyncTestFn and timing how long it takes the callback to be called. It can also periodically emit stats about this. + * This can be disabled and stats can be pulled via setting dataEmitIntervalMs = 0. + * + * @extends {EventEmitter} + * + * The default implementation is an event loop latency monitor. This works by firing periodic events into the event loop + * and timing how long it takes to get back. + * + * @example + * const monitor = new LatencyMonitor(); + * monitor.on('data', (summary) => console.log('Event Loop Latency: %O', summary)); + * + * @example + * const monitor = new LatencyMonitor({latencyCheckIntervalMs: 1000, dataEmitIntervalMs: 60000, asyncTestFn:ping}); + * monitor.on('data', (summary) => console.log('Ping Pong Latency: %O', summary)); + */ +export class LatencyMonitor extends EventEmitter { + private readonly latencyCheckIntervalMs: number + private readonly latencyRandomPercentage: number + private readonly latencyCheckMultiply: number + private readonly latencyCheckSubtract: number + private readonly dataEmitIntervalMs?: number + private readonly asyncTestFn?: (cb: () => void) => void + + private readonly now: (num?: any) => any + private readonly getDeltaMS: (num: number) => number + private visibilityChangeEmitter?: VisibilityChangeEmitter + private latencyData: LatencyData + private checkLatencyID?: NodeJS.Timeout + private emitIntervalID?: NodeJS.Timeout + + constructor (init: LatencyMonitorInit = {}) { + super() + + const { latencyCheckIntervalMs, dataEmitIntervalMs, asyncTestFn, latencyRandomPercentage } = init + + // 0 isn't valid here, so its ok to use || + this.latencyCheckIntervalMs = latencyCheckIntervalMs ?? 500 // 0.5s + this.latencyRandomPercentage = latencyRandomPercentage ?? 10 + this.latencyCheckMultiply = 2 * (this.latencyRandomPercentage / 100.0) * this.latencyCheckIntervalMs + this.latencyCheckSubtract = this.latencyCheckMultiply / 2 + + this.dataEmitIntervalMs = (dataEmitIntervalMs === null || dataEmitIntervalMs === 0) + ? undefined + : dataEmitIntervalMs ?? 5 * 1000 // 5s + log('latencyCheckIntervalMs: %s dataEmitIntervalMs: %s', + this.latencyCheckIntervalMs, this.dataEmitIntervalMs) + if (this.dataEmitIntervalMs != null) { + log('Expecting ~%s events per summary', this.latencyCheckIntervalMs / this.dataEmitIntervalMs) + } else { + log('Not emitting summaries') + } + + this.asyncTestFn = asyncTestFn // If there is no asyncFn, we measure latency + + // If process: use high resolution timer + if (globalThis.process?.hrtime != null) { + log('Using process.hrtime for timing') + this.now = globalThis.process.hrtime // eslint-disable-line no-undef + this.getDeltaMS = (startTime) => { + const hrtime = this.now(startTime) + return (hrtime[0] * 1000) + (hrtime[1] / 1000000) + } + // Let's try for a timer that only monotonically increases + } else if (typeof window !== 'undefined' && window.performance?.now != null) { + log('Using performance.now for timing') + this.now = window.performance.now.bind(window.performance) + this.getDeltaMS = (startTime) => Math.round(this.now() - startTime) + } else { + log('Using Date.now for timing') + this.now = Date.now + this.getDeltaMS = (startTime) => this.now() - startTime + } + + this.latencyData = this.initLatencyData() + } + + start () { + // We check for isBrowser because of browsers set max rates of timeouts when a page is hidden, + // so we fall back to another library + // See: http://stackoverflow.com/questions/6032429/chrome-timeouts-interval-suspended-in-background-tabs + if (isBrowser()) { + this.visibilityChangeEmitter = new VisibilityChangeEmitter() + + this.visibilityChangeEmitter.addEventListener('visibilityChange', (evt) => { + const { detail: pageInFocus } = evt + + if (pageInFocus) { + this._startTimers() + } else { + this._emitSummary() + this._stopTimers() + } + }) + } + + if (this.visibilityChangeEmitter?.isVisible() === true) { + this._startTimers() + } + } + + stop () { + this._stopTimers() + } + + /** + * Start internal timers + * + * @private + */ + _startTimers () { + // Timer already started, ignore this + if (this.checkLatencyID != null) { + return + } + + this.checkLatency() + + if (this.dataEmitIntervalMs != null) { + this.emitIntervalID = setInterval(() => this._emitSummary(), this.dataEmitIntervalMs) + if (typeof this.emitIntervalID.unref === 'function') { + this.emitIntervalID.unref() // Doesn't block exit + } + } + } + + /** + * Stop internal timers + * + * @private + */ + _stopTimers () { + if (this.checkLatencyID != null) { + clearTimeout(this.checkLatencyID) + this.checkLatencyID = undefined + } + if (this.emitIntervalID != null) { + clearInterval(this.emitIntervalID) + this.emitIntervalID = undefined + } + } + + /** + * Emit summary only if there were events. It might not have any events if it was forced via a page hidden/show + * + * @private + */ + _emitSummary () { + const summary = this.getSummary() + if (summary.events > 0) { + this.dispatchEvent(new CustomEvent('data', { + detail: summary + })) + } + } + + /** + * Calling this function will end the collection period. If a timing event was already fired and somewhere in the queue, + * it will not count for this time period + */ + getSummary (): SummaryObject { + // We might want to adjust for the number of expected events + // Example: first 1 event it comes back, then such a long blocker that the next emit check comes + // Then this fires - looks like no latency!! + const latency = { + events: this.latencyData.events, + minMs: this.latencyData.minMs, + maxMs: this.latencyData.maxMs, + avgMs: this.latencyData.events > 0 + ? this.latencyData.totalMs / this.latencyData.events + : Number.POSITIVE_INFINITY, + lengthMs: this.getDeltaMS(this.latencyData.startTime) + } + this.latencyData = this.initLatencyData() // Clear + + log.trace('Summary: %O', latency) + return latency + } + + /** + * Randomly calls an async fn every roughly latencyCheckIntervalMs (plus some randomness). If no async fn is found, + * it will simply report on event loop latency. + */ + checkLatency () { + // Randomness is needed to avoid alignment by accident to regular things in the event loop + const randomness = (Math.random() * this.latencyCheckMultiply) - this.latencyCheckSubtract + + // We use this to ensure that in case some overlap somehow, we don't take the wrong startTime/offset + const localData = { + deltaOffset: Math.ceil(this.latencyCheckIntervalMs + randomness), + startTime: this.now() + } + + const cb = () => { + // We are already stopped, ignore this datapoint + if (this.checkLatencyID == null) { + return + } + const deltaMS = this.getDeltaMS(localData.startTime) - localData.deltaOffset + this.checkLatency() // Start again ASAP + + // Add the data point. If this gets complex, refactor it + this.latencyData.events++ + this.latencyData.minMs = Math.min(this.latencyData.minMs, deltaMS) + this.latencyData.maxMs = Math.max(this.latencyData.maxMs, deltaMS) + this.latencyData.totalMs += deltaMS + log.trace('MS: %s Data: %O', deltaMS, this.latencyData) + } + log.trace('localData: %O', localData) + + this.checkLatencyID = setTimeout(() => { + // This gets rid of including event loop + if (this.asyncTestFn != null) { + // Clear timing related things + localData.deltaOffset = 0 + localData.startTime = this.now() + this.asyncTestFn(cb) + } else { + // setTimeout is not more accurate than 1ms, so this will ensure positive numbers. Add 1 to emitted data to remove. + // This is not the best, but for now it'll be just fine. This isn't meant to be sub ms accurate. + localData.deltaOffset -= 1 + // If there is no function to test, we mean check latency which is a special case that is really cb => cb() + // We avoid that for the few extra function all overheads. Also, we want to keep the timers different + cb() + } + }, localData.deltaOffset) + + if (typeof this.checkLatencyID.unref === 'function') { + this.checkLatencyID.unref() // Doesn't block exit + } + } + + initLatencyData (): LatencyData { + return { + startTime: this.now(), + minMs: Number.POSITIVE_INFINITY, + maxMs: Number.NEGATIVE_INFINITY, + events: 0, + totalMs: 0 + } + } +} + +function isBrowser () { + return typeof globalThis.window !== 'undefined' +} diff --git a/src/connection-manager/visibility-change-emitter.js b/src/connection-manager/visibility-change-emitter.ts similarity index 53% rename from src/connection-manager/visibility-change-emitter.js rename to src/connection-manager/visibility-change-emitter.ts index ebe5e7d076..0eddbf39b2 100644 --- a/src/connection-manager/visibility-change-emitter.js +++ b/src/connection-manager/visibility-change-emitter.ts @@ -1,14 +1,17 @@ -// @ts-nocheck -/* global document */ - /** * This code is based on `latency-monitor` (https://github.com/mlucool/latency-monitor) by `mlucool` (https://github.com/mlucool), available under Apache License 2.0 (https://github.com/mlucool/latency-monitor/blob/master/LICENSE) */ -'use strict' -const { EventEmitter } = require('events') +import { CustomEvent, EventEmitter } from '@libp2p/interfaces' +import { logger } from '@libp2p/logger' + +const log = logger('libp2p:connection-manager:latency-monitor:visibility-change-emitter') + +interface VisibilityChangeEmitterEvents { + 'visibilityChange': CustomEvent +} -const debug = require('debug')('latency-monitor:VisibilityChangeEmitter') +type Hidden = 'hidden' | 'mozHidden' | 'msHidden' | 'webkitHidden' /** * Listen to page visibility change events (i.e. when the page is focused / blurred) by an event emitter. @@ -32,20 +35,20 @@ const debug = require('debug')('latency-monitor:VisibilityChangeEmitter') * // To access the visibility state directly, call: * console.log('Am I focused now? ' + myVisibilityEmitter.isVisible()); */ -class VisibilityChangeEmitter extends EventEmitter { - /** - * Creates a VisibilityChangeEmitter - * - * @class - */ +export class VisibilityChangeEmitter extends EventEmitter { + private hidden: Hidden + private visibilityChange: string + constructor () { super() - if (typeof document === 'undefined') { - debug('This is not a browser, no "document" found. Stopping.') - return + + this.hidden = 'hidden' + this.visibilityChange = 'visibilityChange' + + if (globalThis.document != null) { + this._initializeVisibilityVarNames() + this._addVisibilityChangeListener() } - this._initializeVisibilityVarNames() - this._addVisibilityChangeListener() } /** @@ -58,23 +61,28 @@ class VisibilityChangeEmitter extends EventEmitter { * @private */ _initializeVisibilityVarNames () { - let hidden - let visibilityChange - if (typeof document.hidden !== 'undefined') { // Opera 12.10 and Firefox 18 and later support + let hidden: Hidden = 'hidden' + let visibilityChange = 'visibilitychange' + + if (typeof globalThis.document.hidden !== 'undefined') { // Opera 12.10 and Firefox 18 and later support hidden = 'hidden' visibilityChange = 'visibilitychange' - } else if (typeof document.mozHidden !== 'undefined') { + // @ts-expect-error mozHidden is a non-standard field name + } else if (typeof globalThis.document.mozHidden !== 'undefined') { hidden = 'mozHidden' visibilityChange = 'mozvisibilitychange' - } else if (typeof document.msHidden !== 'undefined') { + // @ts-expect-error msHidden is a non-standard field name + } else if (typeof globalThis.document.msHidden !== 'undefined') { hidden = 'msHidden' visibilityChange = 'msvisibilitychange' - } else if (typeof document.webkitHidden !== 'undefined') { + // @ts-expect-error webkitHidden is a non-standard field name + } else if (typeof globalThis.document.webkitHidden !== 'undefined') { hidden = 'webkitHidden' visibilityChange = 'webkitvisibilitychange' } - this._hidden = hidden - this._visibilityChange = visibilityChange + + this.hidden = hidden + this.visibilityChange = visibilityChange } /** @@ -84,27 +92,27 @@ class VisibilityChangeEmitter extends EventEmitter { * @private */ _addVisibilityChangeListener () { - if (typeof document.addEventListener === 'undefined' || - typeof document[this._hidden] === 'undefined') { - debug('Checking page visibility requires a browser that supports the Page Visibility API.') + // @ts-expect-error cannot index document object with string key + if (typeof globalThis.document.addEventListener === 'undefined' || typeof document[this.hidden] === 'undefined') { + log('Checking page visibility requires a browser that supports the Page Visibility API.') } else { // Handle page visibility change - document.addEventListener(this._visibilityChange, this._handleVisibilityChange.bind(this), false) + globalThis.document.addEventListener(this.visibilityChange, this._handleVisibilityChange.bind(this), false) } } /** * The function returns ```true``` if the page is visible or ```false``` if the page is not visible and * ```undefined``` if the page visibility API is not supported by the browser. - * - * @returns {boolean | void} whether the page is now visible or not (undefined is unknown) */ isVisible () { - if (this._hidden === undefined || document[this._hidden] === undefined) { + // @ts-expect-error cannot index document object with string key + if (this.hidden === undefined || document[this.hidden] === undefined) { return undefined } - return !document[this._hidden] + // @ts-expect-error cannot index document object with string key + return document[this.hidden] == null } /** @@ -115,11 +123,13 @@ class VisibilityChangeEmitter extends EventEmitter { * @private */ _handleVisibilityChange () { - const visible = !document[this._hidden] - debug(visible ? 'Page Visible' : 'Page Hidden') + // @ts-expect-error cannot index document object with string key + const visible = globalThis.document[this.hidden] === false + log(visible ? 'Page Visible' : 'Page Hidden') + // Emit the event - this.emit('visibilityChange', visible) + this.dispatchEvent(new CustomEvent('visibilityChange', { + detail: visible + })) } } - -module.exports = VisibilityChangeEmitter diff --git a/src/constants.js b/src/constants.js deleted file mode 100644 index 76a2abd863..0000000000 --- a/src/constants.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict' - -module.exports = { - DIAL_TIMEOUT: 30e3, // How long in ms a dial attempt is allowed to take - MAX_PARALLEL_DIALS: 100, // Maximum allowed concurrent dials - MAX_PER_PEER_DIALS: 4, // Allowed parallel dials per DialRequest - MAX_ADDRS_TO_DIAL: 25, // Maximum number of allowed addresses to attempt to dial - METRICS: { - computeThrottleMaxQueueSize: 1000, - computeThrottleTimeout: 2000, - movingAverageIntervals: [ - 60 * 1000, // 1 minute - 5 * 60 * 1000, // 5 minutes - 15 * 60 * 1000 // 15 minutes - ], - maxOldPeersRetention: 50 - } -} diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000000..fcb7e94312 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,31 @@ + +/** + * How long in ms a dial attempt is allowed to take + */ +export const DIAL_TIMEOUT = 30e3 + +/** + * Maximum allowed concurrent dials + */ +export const MAX_PARALLEL_DIALS = 100 + +/** + * Allowed parallel dials per DialRequest + */ +export const MAX_PER_PEER_DIALS = 4 + +/** + * Maximum number of allowed addresses to attempt to dial + */ +export const MAX_ADDRS_TO_DIAL = 25 + +export const METRICS = { + computeThrottleMaxQueueSize: 1000, + computeThrottleTimeout: 2000, + movingAverageIntervals: [ + 60 * 1000, // 1 minute + 5 * 60 * 1000, // 5 minutes + 15 * 60 * 1000 // 15 minutes + ], + maxOldPeersRetention: 50 +} diff --git a/src/content-routing/index.js b/src/content-routing/index.js deleted file mode 100644 index df04225d7c..0000000000 --- a/src/content-routing/index.js +++ /dev/null @@ -1,163 +0,0 @@ -'use strict' - -const errCode = require('err-code') -const { messages, codes } = require('../errors') -const { - storeAddresses, - uniquePeers, - requirePeers, - maybeLimitSource -} = require('./utils') -const drain = require('it-drain') -const merge = require('it-merge') -const { pipe } = require('it-pipe') -const { DHTContentRouting } = require('../dht/dht-content-routing') - -/** - * @typedef {import('peer-id')} PeerId - * @typedef {import('multiaddr').Multiaddr} Multiaddr - * @typedef {import('multiformats/cid').CID} CID - * @typedef {import('libp2p-interfaces/src/content-routing/types').ContentRouting} ContentRoutingModule - */ - -/** - * @typedef {Object} GetData - * @property {PeerId} from - * @property {Uint8Array} val - */ - -class ContentRouting { - /** - * @class - * @param {import('..')} libp2p - */ - constructor (libp2p) { - this.libp2p = libp2p - /** @type {ContentRoutingModule[]} */ - this.routers = libp2p._modules.contentRouting || [] - this.dht = libp2p._dht - - // If we have the dht, add it to the available content routers - if (this.dht && libp2p._config.dht.enabled) { - this.routers.push(new DHTContentRouting(this.dht)) - } - } - - /** - * Iterates over all content routers in parallel to find providers of the given key. - * - * @param {CID} key - The CID key of the content to find - * @param {object} [options] - * @param {number} [options.timeout] - How long the query should run - * @param {number} [options.maxNumProviders] - maximum number of providers to find - * @returns {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} - */ - async * findProviders (key, options = {}) { - if (!this.routers.length) { - throw errCode(new Error('No content this.routers available'), codes.ERR_NO_ROUTERS_AVAILABLE) - } - - yield * pipe( - merge( - ...this.routers.map(router => router.findProviders(key, options)) - ), - (source) => storeAddresses(source, this.libp2p.peerStore), - (source) => uniquePeers(source), - (source) => maybeLimitSource(source, options.maxNumProviders), - (source) => requirePeers(source) - ) - } - - /** - * Iterates over all content routers in parallel to notify it is - * a provider of the given key. - * - * @param {CID} key - The CID key of the content to find - * @returns {Promise} - */ - async provide (key) { - if (!this.routers.length) { - throw errCode(new Error('No content routers available'), codes.ERR_NO_ROUTERS_AVAILABLE) - } - - await Promise.all(this.routers.map((router) => router.provide(key))) - } - - /** - * Store the given key/value pair in the DHT. - * - * @param {Uint8Array} key - * @param {Uint8Array} value - * @param {Object} [options] - put options - * @param {number} [options.minPeers] - minimum number of peers required to successfully put - * @returns {Promise} - */ - async put (key, value, options) { - if (!this.libp2p.isStarted() || !this.dht.isStarted) { - throw errCode(new Error(messages.NOT_STARTED_YET), codes.DHT_NOT_STARTED) - } - - await drain(this.dht.put(key, value, options)) - } - - /** - * 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) - * @returns {Promise} - */ - async get (key, options) { - if (!this.libp2p.isStarted() || !this.dht.isStarted) { - throw errCode(new Error(messages.NOT_STARTED_YET), codes.DHT_NOT_STARTED) - } - - for await (const event of this.dht.get(key, options)) { - if (event.name === 'VALUE') { - return { from: event.peerId, val: event.value } - } - } - - throw errCode(new Error(messages.NOT_FOUND), codes.ERR_NOT_FOUND) - } - - /** - * Get the `n` values to the given key without sorting. - * - * @param {Uint8Array} key - * @param {number} nVals - * @param {Object} [options] - get options - * @param {number} [options.timeout] - optional timeout (default: 60000) - */ - async * getMany (key, nVals, options) { // eslint-disable-line require-await - if (!this.libp2p.isStarted() || !this.dht.isStarted) { - throw errCode(new Error(messages.NOT_STARTED_YET), codes.DHT_NOT_STARTED) - } - - if (!nVals) { - return - } - - let gotValues = 0 - - for await (const event of this.dht.get(key, options)) { - if (event.name === 'VALUE') { - yield { from: event.peerId, val: event.value } - - gotValues++ - - if (gotValues === nVals) { - break - } - } - } - - if (gotValues === 0) { - throw errCode(new Error(messages.NOT_FOUND), codes.ERR_NOT_FOUND) - } - } -} - -module.exports = ContentRouting diff --git a/src/content-routing/index.ts b/src/content-routing/index.ts new file mode 100644 index 0000000000..a52450c9f1 --- /dev/null +++ b/src/content-routing/index.ts @@ -0,0 +1,143 @@ +import errCode from 'err-code' +import { messages, codes } from '../errors.js' +import { + storeAddresses, + uniquePeers, + requirePeers +} from './utils.js' +import drain from 'it-drain' +import merge from 'it-merge' +import { pipe } from 'it-pipe' +import type { ContentRouting } from '@libp2p/interfaces/content-routing' +import type { AbortOptions, Startable } from '@libp2p/interfaces' +import type { CID } from 'multiformats/cid' +import type { Components } from '@libp2p/interfaces/components' + +export interface CompoundContentRoutingInit { + routers: ContentRouting[] +} + +export class CompoundContentRouting implements ContentRouting, Startable { + private readonly routers: ContentRouting[] + private started: boolean + private readonly components: Components + + constructor (components: Components, init: CompoundContentRoutingInit) { + this.routers = init.routers ?? [] + this.started = false + this.components = components + } + + isStarted () { + return this.started + } + + async start () { + this.started = true + } + + async stop () { + this.started = false + } + + /** + * Iterates over all content routers in parallel to find providers of the given key + */ + async * findProviders (key: CID, options: AbortOptions = {}) { + if (this.routers.length === 0) { + throw errCode(new Error('No content this.routers available'), codes.ERR_NO_ROUTERS_AVAILABLE) + } + + yield * pipe( + merge( + ...this.routers.map(router => router.findProviders(key, options)) + ), + (source) => storeAddresses(source, this.components.getPeerStore()), + (source) => uniquePeers(source), + (source) => requirePeers(source) + ) + } + + /** + * Iterates over all content routers in parallel to notify it is + * a provider of the given key + */ + async provide (key: CID, options: AbortOptions = {}) { + if (this.routers.length === 0) { + throw errCode(new Error('No content routers available'), codes.ERR_NO_ROUTERS_AVAILABLE) + } + + await Promise.all(this.routers.map(async (router) => await router.provide(key, options))) + } + + /** + * Store the given key/value pair in the available content routings + */ + async put (key: Uint8Array, value: Uint8Array, options?: AbortOptions) { + if (!this.isStarted()) { + throw errCode(new Error(messages.NOT_STARTED_YET), codes.DHT_NOT_STARTED) + } + + const dht = this.components.getDHT() + + if (dht != null) { + await drain(dht.put(key, value, options)) + } + } + + /** + * Get the value to the given key. + * Times out after 1 minute by default. + */ + async get (key: Uint8Array, options?: AbortOptions): Promise { + if (!this.isStarted()) { + throw errCode(new Error(messages.NOT_STARTED_YET), codes.DHT_NOT_STARTED) + } + + const dht = this.components.getDHT() + + if (dht != null) { + for await (const event of dht.get(key, options)) { + if (event.name === 'VALUE') { + return event.value + } + } + } + + throw errCode(new Error(messages.NOT_FOUND), codes.ERR_NOT_FOUND) + } + + /** + * Get the `n` values to the given key without sorting + */ + async * getMany (key: Uint8Array, nVals: number, options: AbortOptions) { // eslint-disable-line require-await + if (!this.isStarted()) { + throw errCode(new Error(messages.NOT_STARTED_YET), codes.DHT_NOT_STARTED) + } + + if (nVals == null || nVals === 0) { + return + } + + let gotValues = 0 + const dht = this.components.getDHT() + + if (dht != null) { + for await (const event of dht.get(key, options)) { + if (event.name === 'VALUE') { + yield { from: event.from, val: event.value } + + gotValues++ + + if (gotValues === nVals) { + break + } + } + } + } + + if (gotValues === 0) { + throw errCode(new Error(messages.NOT_FOUND), codes.ERR_NOT_FOUND) + } + } +} diff --git a/src/content-routing/utils.js b/src/content-routing/utils.js deleted file mode 100644 index adcf8f2c07..0000000000 --- a/src/content-routing/utils.js +++ /dev/null @@ -1,89 +0,0 @@ -'use strict' - -const errCode = require('err-code') -const filter = require('it-filter') -const map = require('it-map') -const take = require('it-take') - -/** - * @typedef {import('peer-id')} PeerId - * @typedef {import('multiaddr').Multiaddr} Multiaddr - */ - -/** - * Store the multiaddrs from every peer in the passed peer store - * - * @param {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} source - * @param {import('../peer-store/types').PeerStore} peerStore - */ -async function * storeAddresses (source, peerStore) { - yield * map(source, async (peer) => { - // ensure we have the addresses for a given peer - await peerStore.addressBook.add(peer.id, peer.multiaddrs) - - return peer - }) -} - -/** - * Filter peers by unique peer id - * - * @param {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} source - */ -function uniquePeers (source) { - /** @type Set */ - const seen = new Set() - - return filter(source, (peer) => { - // dedupe by peer id - if (seen.has(peer.id.toString())) { - return false - } - - seen.add(peer.id.toString()) - - return true - }) -} - -/** - * Require at least `min` peers to be yielded from `source` - * - * @param {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} source - * @param {number} min - */ -async function * requirePeers (source, min = 1) { - let seen = 0 - - for await (const peer of source) { - seen++ - - yield peer - } - - if (seen < min) { - throw errCode(new Error('not found'), 'NOT_FOUND') - } -} - -/** - * If `max` is passed, only take that number of peers from the source - * otherwise take all the peers - * - * @param {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} source - * @param {number} [max] - */ -function maybeLimitSource (source, max) { - if (max) { - return take(source, max) - } - - return source -} - -module.exports = { - storeAddresses, - uniquePeers, - requirePeers, - maybeLimitSource -} diff --git a/src/content-routing/utils.ts b/src/content-routing/utils.ts new file mode 100644 index 0000000000..a4ca4d15ee --- /dev/null +++ b/src/content-routing/utils.ts @@ -0,0 +1,54 @@ +import errCode from 'err-code' +import filter from 'it-filter' +import map from 'it-map' +import type { Source } from 'it-stream-types' +import type { PeerInfo } from '@libp2p/interfaces/peer-info' +import type { PeerStore } from '@libp2p/interfaces/peer-store' + +/** + * Store the multiaddrs from every peer in the passed peer store + */ +export async function * storeAddresses (source: Source, peerStore: PeerStore) { + yield * map(source, async (peer) => { + // ensure we have the addresses for a given peer + await peerStore.addressBook.add(peer.id, peer.multiaddrs) + + return peer + }) +} + +/** + * Filter peers by unique peer id + */ +export function uniquePeers (source: Source) { + /** @type Set */ + const seen = new Set() + + return filter(source, (peer) => { + // dedupe by peer id + if (seen.has(peer.id.toString())) { + return false + } + + seen.add(peer.id.toString()) + + return true + }) +} + +/** + * Require at least `min` peers to be yielded from `source` + */ +export async function * requirePeers (source: Source, min: number = 1) { + let seen = 0 + + for await (const peer of source) { + seen++ + + yield peer + } + + if (seen < min) { + throw errCode(new Error('not found'), 'NOT_FOUND') + } +} diff --git a/src/dht/dht-content-routing.js b/src/dht/dht-content-routing.js deleted file mode 100644 index ead668f3d4..0000000000 --- a/src/dht/dht-content-routing.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict' - -const drain = require('it-drain') - -/** - * @typedef {import('peer-id')} PeerId - * @typedef {import('libp2p-interfaces/src/content-routing/types').ContentRouting} ContentRoutingModule - * @typedef {import('multiformats/cid').CID} CID - */ - -/** - * Wrapper class to convert events into returned values - * - * @implements {ContentRoutingModule} - */ -class DHTContentRouting { - /** - * @param {import('libp2p-kad-dht').DHT} dht - */ - constructor (dht) { - this._dht = dht - } - - /** - * @param {CID} cid - */ - async provide (cid) { - await drain(this._dht.provide(cid)) - } - - /** - * @param {CID} cid - * @param {*} options - */ - async * findProviders (cid, options) { - for await (const event of this._dht.findProviders(cid, options)) { - if (event.name === 'PROVIDER') { - yield * event.providers - } - } - } -} - -module.exports = { DHTContentRouting } diff --git a/src/dht/dht-content-routing.ts b/src/dht/dht-content-routing.ts new file mode 100644 index 0000000000..12552d47dd --- /dev/null +++ b/src/dht/dht-content-routing.ts @@ -0,0 +1,43 @@ +import drain from 'it-drain' +import errCode from 'err-code' +import type { DHT } from '@libp2p/interfaces/dht' +import type { ContentRouting } from '@libp2p/interfaces/content-routing' +import type { CID } from 'multiformats/cid' +import type { AbortOptions } from '@libp2p/interfaces' + +/** + * Wrapper class to convert events into returned values + */ +export class DHTContentRouting implements ContentRouting { + private readonly dht: DHT + + constructor (dht: DHT) { + this.dht = dht + } + + async provide (cid: CID) { + await drain(this.dht.provide(cid)) + } + + async * findProviders (cid: CID, options: AbortOptions = {}) { + for await (const event of this.dht.findProviders(cid, options)) { + if (event.name === 'PROVIDER') { + yield * event.providers + } + } + } + + async put (key: Uint8Array, value: Uint8Array, options?: AbortOptions): Promise { + await drain(this.dht.put(key, value, options)) + } + + async get (key: Uint8Array, options?: AbortOptions): Promise { + for await (const event of this.dht.get(key, options)) { + if (event.name === 'VALUE') { + return event.value + } + } + + throw errCode(new Error('Not found'), 'ERR_NOT_FOUND') + } +} diff --git a/src/dht/dht-peer-routing.js b/src/dht/dht-peer-routing.js deleted file mode 100644 index 762abc80fa..0000000000 --- a/src/dht/dht-peer-routing.js +++ /dev/null @@ -1,51 +0,0 @@ -'use strict' - -const errCode = require('err-code') -const { messages, codes } = require('../errors') - -/** - * @typedef {import('peer-id')} PeerId - * @typedef {import('libp2p-interfaces/src/peer-routing/types').PeerRouting} PeerRoutingModule - */ - -/** - * Wrapper class to convert events into returned values - * - * @implements {PeerRoutingModule} - */ -class DHTPeerRouting { - /** - * @param {import('libp2p-kad-dht').DHT} dht - */ - constructor (dht) { - this._dht = dht - } - - /** - * @param {PeerId} peerId - * @param {any} options - */ - async findPeer (peerId, options = {}) { - for await (const event of this._dht.findPeer(peerId, options)) { - if (event.name === 'FINAL_PEER') { - return event.peer - } - } - - throw errCode(new Error(messages.NOT_FOUND), codes.ERR_NOT_FOUND) - } - - /** - * @param {Uint8Array} key - * @param {any} options - */ - async * getClosestPeers (key, options = {}) { - for await (const event of this._dht.getClosestPeers(key, options)) { - if (event.name === 'PEER_RESPONSE') { - yield * event.closer - } - } - } -} - -module.exports = { DHTPeerRouting } diff --git a/src/dht/dht-peer-routing.ts b/src/dht/dht-peer-routing.ts new file mode 100644 index 0000000000..45950cde39 --- /dev/null +++ b/src/dht/dht-peer-routing.ts @@ -0,0 +1,35 @@ +import errCode from 'err-code' +import { messages, codes } from '../errors.js' +import type { PeerRouting } from '@libp2p/interfaces/peer-routing' +import type { DHT } from '@libp2p/interfaces/dht' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { AbortOptions } from '@libp2p/interfaces' + +/** + * Wrapper class to convert events into returned values + */ +export class DHTPeerRouting implements PeerRouting { + private readonly dht: DHT + + constructor (dht: DHT) { + this.dht = dht + } + + async findPeer (peerId: PeerId, options: AbortOptions = {}) { + for await (const event of this.dht.findPeer(peerId, options)) { + if (event.name === 'FINAL_PEER') { + return event.peer + } + } + + throw errCode(new Error(messages.NOT_FOUND), codes.ERR_NOT_FOUND) + } + + async * getClosestPeers (key: Uint8Array, options: AbortOptions = {}) { + for await (const event of this.dht.getClosestPeers(key, options)) { + if (event.name === 'PEER_RESPONSE') { + yield * event.closer + } + } + } +} diff --git a/src/dialer/auto-dialer.ts b/src/dialer/auto-dialer.ts new file mode 100644 index 0000000000..5674a94d60 --- /dev/null +++ b/src/dialer/auto-dialer.ts @@ -0,0 +1,40 @@ +import type { PeerInfo } from '@libp2p/interfaces/peer-info' +import { logger } from '@libp2p/logger' +import type { Components } from '@libp2p/interfaces/components' + +const log = logger('libp2p:dialer:auto-dialer') + +export interface AutoDialerInit { + enabled: boolean + minConnections: number +} + +export class AutoDialer { + private readonly components: Components + private readonly enabled: boolean + private readonly minConnections: number + + constructor (components: Components, init: AutoDialerInit) { + this.components = components + this.enabled = init.enabled + this.minConnections = init.minConnections + } + + public handle (evt: CustomEvent) { + const { detail: peer } = evt + + // If auto dialing is on and we have no connection to the peer, check if we should dial + if (this.enabled && this.components.getConnectionManager().getConnection(peer.id) == null) { + const minConnections = this.minConnections ?? 0 + + if (minConnections > this.components.getConnectionManager().getConnectionList().length) { + log('auto-dialing discovered peer %p', peer.id) + + void this.components.getDialer().dial(peer.id) + .catch(err => { + log.error('could not connect to discovered peer %p with %o', peer.id, err) + }) + } + } + } +} diff --git a/src/dialer/dial-request.js b/src/dialer/dial-request.ts similarity index 51% rename from src/dialer/dial-request.js rename to src/dialer/dial-request.ts index 810cdfee69..23c007a4dc 100644 --- a/src/dialer/dial-request.js +++ b/src/dialer/dial-request.ts @@ -1,91 +1,102 @@ -'use strict' - -const errCode = require('err-code') -const { anySignal } = require('any-signal') -// @ts-ignore p-fifo does not export types -const FIFO = require('p-fifo') -const pAny = require('p-any') -// @ts-expect-error setMaxListeners is missing from the types -const { setMaxListeners } = require('events') -const { codes } = require('../errors') - -/** - * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection - * @typedef {import('./')} Dialer - * @typedef {import('multiaddr').Multiaddr} Multiaddr - */ - -/** - * @typedef {Object} DialOptions - * @property {AbortSignal} signal - * - * @typedef {Object} DialRequestOptions - * @property {Multiaddr[]} addrs - * @property {(m: Multiaddr, options: DialOptions) => Promise} dialAction - * @property {Dialer} dialer - */ - -class DialRequest { +import errCode from 'err-code' +import { anySignal } from 'any-signal' +import FIFO from 'p-fifo' +// @ts-expect-error setMaxListeners is missing from the node 16 types +import { setMaxListeners } from 'events' +import { codes } from '../errors.js' +import { logger } from '@libp2p/logger' +import type { Multiaddr } from '@multiformats/multiaddr' +import type { Connection } from '@libp2p/interfaces/connection' +import type { AbortOptions } from '@libp2p/interfaces' +import type { DefaultDialer } from './index.js' + +const log = logger('libp2p:dialer:dial-request') + +export interface DialAction { + (m: Multiaddr, options: AbortOptions): Promise +} + +export interface DialRequestOptions { + addrs: Multiaddr[] + dialAction: DialAction + dialer: DefaultDialer +} + +export class DialRequest { + private readonly addrs: Multiaddr[] + private readonly dialer: DefaultDialer + private readonly dialAction: DialAction + /** * Manages running the `dialAction` on multiple provided `addrs` in parallel * up to a maximum determined by the number of tokens returned * from `dialer.getTokens`. Once a DialRequest is created, it can be * started using `DialRequest.run(options)`. Once a single dial has succeeded, * all other dials in the request will be cancelled. - * - * @class - * @param {DialRequestOptions} options */ - constructor ({ - addrs, - dialAction, - dialer - }) { + constructor (options: DialRequestOptions) { + const { + addrs, + dialAction, + dialer + } = options + this.addrs = addrs this.dialer = dialer this.dialAction = dialAction } - /** - * @async - * @param {object} [options] - * @param {AbortSignal} [options.signal] - An AbortController signal - * @returns {Promise} - */ - async run (options = {}) { + async run (options: AbortOptions = {}): Promise { const tokens = this.dialer.getTokens(this.addrs.length) + // If no tokens are available, throw if (tokens.length < 1) { throw errCode(new Error('No dial tokens available'), codes.ERR_NO_DIAL_TOKENS) } - const tokenHolder = new FIFO() - tokens.forEach(token => tokenHolder.push(token)) + const tokenHolder = new FIFO() + + for (const token of tokens) { + void tokenHolder.push(token).catch(err => { + log.error(err) + }) + } + const dialAbortControllers = this.addrs.map(() => { const controller = new AbortController() try { // fails on node < 15.4 - setMaxListeners && setMaxListeners(Infinity, controller.signal) + setMaxListeners?.(Infinity, controller.signal) } catch {} return controller }) + + if (options.signal != null) { + try { + // fails on node < 15.4 + setMaxListeners?.(Infinity, options.signal) + } catch {} + } + let completedDials = 0 try { - return await pAny(this.addrs.map(async (addr, i) => { + return await Promise.any(this.addrs.map(async (addr, i) => { const token = await tokenHolder.shift() // get token let conn try { const signal = dialAbortControllers[i].signal - conn = await this.dialAction(addr, { ...options, signal: options.signal ? anySignal([signal, options.signal]) : signal }) + conn = await this.dialAction(addr, { ...options, signal: (options.signal != null) ? anySignal([signal, options.signal]) : signal }) // Remove the successful AbortController so it is not aborted dialAbortControllers.splice(i, 1) } finally { completedDials++ // If we have more or equal dials remaining than tokens, recycle the token, otherwise release it if (this.addrs.length - completedDials >= tokens.length) { - tokenHolder.push(token) + void tokenHolder.push(token).catch(err => { + log.error(err) + }) } else { this.dialer.releaseToken(tokens.splice(tokens.indexOf(token), 1)[0]) } @@ -99,5 +110,3 @@ class DialRequest { } } } - -module.exports = DialRequest diff --git a/src/dialer/index.js b/src/dialer/index.js deleted file mode 100644 index f2e82cd30b..0000000000 --- a/src/dialer/index.js +++ /dev/null @@ -1,376 +0,0 @@ -'use strict' - -const debug = require('debug') -const all = require('it-all') -const filter = require('it-filter') -const { pipe } = require('it-pipe') -const log = Object.assign(debug('libp2p:dialer'), { - error: debug('libp2p:dialer:err') -}) -const errCode = require('err-code') -const { Multiaddr } = require('multiaddr') -const { TimeoutController } = require('timeout-abort-controller') -const { AbortError } = require('abortable-iterator') -const { anySignal } = require('any-signal') -// @ts-expect-error setMaxListeners is missing from the types -const { setMaxListeners } = require('events') -const DialRequest = require('./dial-request') -const { publicAddressesFirst } = require('libp2p-utils/src/address-sort') -const getPeer = require('../get-peer') -const trackedMap = require('../metrics/tracked-map') -const { codes } = require('../errors') -const { - DIAL_TIMEOUT, - MAX_PARALLEL_DIALS, - MAX_PER_PEER_DIALS, - MAX_ADDRS_TO_DIAL -} = require('../constants') - -const METRICS_COMPONENT = 'dialler' -const METRICS_PENDING_DIALS = 'pending-dials' -const METRICS_PENDING_DIAL_TARGETS = 'pending-dial-targets' - -/** - * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection - * @typedef {import('peer-id')} PeerId - * @typedef {import('../peer-store/types').PeerStore} PeerStore - * @typedef {import('../peer-store/types').Address} Address - * @typedef {import('../transport-manager')} TransportManager - * @typedef {import('../types').ConnectionGater} ConnectionGater - */ - -/** - * @typedef {Object} DialerProperties - * @property {PeerStore} peerStore - * @property {TransportManager} transportManager - * @property {ConnectionGater} connectionGater - * - * @typedef {(addr:Multiaddr) => Promise} Resolver - * - * @typedef {Object} DialerOptions - * @property {(addresses: Address[]) => Address[]} [options.addressSorter = publicAddressesFirst] - Sort the known addresses of a peer before trying to dial. - * @property {number} [maxParallelDials = MAX_PARALLEL_DIALS] - Number of max concurrent dials. - * @property {number} [maxAddrsToDial = MAX_ADDRS_TO_DIAL] - Number of max addresses to dial for a given peer. - * @property {number} [maxDialsPerPeer = MAX_PER_PEER_DIALS] - Number of max concurrent dials per peer. - * @property {number} [dialTimeout = DIAL_TIMEOUT] - How long a dial attempt is allowed to take. - * @property {Record} [resolvers = {}] - multiaddr resolvers to use when dialing - * @property {import('../metrics')} [metrics] - * - * @typedef DialTarget - * @property {string} id - * @property {Multiaddr[]} addrs - * - * @typedef PendingDial - * @property {import('./dial-request')} dialRequest - * @property {import('timeout-abort-controller').TimeoutController} controller - * @property {Promise} promise - * @property {function():void} destroy - */ - -class Dialer { - /** - * @class - * @param {DialerProperties & DialerOptions} options - */ - constructor ({ - transportManager, - peerStore, - connectionGater, - addressSorter = publicAddressesFirst, - maxParallelDials = MAX_PARALLEL_DIALS, - maxAddrsToDial = MAX_ADDRS_TO_DIAL, - dialTimeout = DIAL_TIMEOUT, - maxDialsPerPeer = MAX_PER_PEER_DIALS, - resolvers = {}, - metrics - }) { - this.connectionGater = connectionGater - this.transportManager = transportManager - this.peerStore = peerStore - this.addressSorter = addressSorter - this.maxParallelDials = maxParallelDials - this.maxAddrsToDial = maxAddrsToDial - this.timeout = dialTimeout - this.maxDialsPerPeer = maxDialsPerPeer - this.tokens = [...new Array(maxParallelDials)].map((_, index) => index) - - /** @type {Map} */ - this._pendingDials = trackedMap({ - component: METRICS_COMPONENT, - metric: METRICS_PENDING_DIALS, - metrics - }) - - /** @type {Map void, reject: (err: Error) => void}>} */ - this._pendingDialTargets = trackedMap({ - component: METRICS_COMPONENT, - metric: METRICS_PENDING_DIAL_TARGETS, - metrics - }) - - for (const [key, value] of Object.entries(resolvers)) { - Multiaddr.resolvers.set(key, value) - } - } - - /** - * Clears any pending dials - */ - destroy () { - for (const dial of this._pendingDials.values()) { - try { - dial.controller.abort() - } catch (/** @type {any} */ err) { - log.error(err) - } - } - this._pendingDials.clear() - - for (const pendingTarget of this._pendingDialTargets.values()) { - pendingTarget.reject(new AbortError('Dialer was destroyed')) - } - this._pendingDialTargets.clear() - } - - /** - * Connects to a given `peer` by dialing all of its known addresses. - * The dial to the first address that is successfully able to upgrade a connection - * will be used. - * - * @param {PeerId|Multiaddr|string} peer - The peer to dial - * @param {object} [options] - * @param {AbortSignal} [options.signal] - An AbortController signal - * @returns {Promise} - */ - async connectToPeer (peer, options = {}) { - const { id } = getPeer(peer) - - if (await this.connectionGater.denyDialPeer(id)) { - throw errCode(new Error('The dial request is blocked by gater.allowDialPeer'), codes.ERR_PEER_DIAL_INTERCEPTED) - } - - const dialTarget = await this._createCancellableDialTarget(peer) - - if (!dialTarget.addrs.length) { - throw errCode(new Error('The dial request has no valid addresses'), codes.ERR_NO_VALID_ADDRESSES) - } - const pendingDial = this._pendingDials.get(dialTarget.id) || this._createPendingDial(dialTarget, options) - - try { - const connection = await pendingDial.promise - log('dial succeeded to %s', dialTarget.id) - return connection - } catch (/** @type {any} */ err) { - // Error is a timeout - if (pendingDial.controller.signal.aborted) { - err.code = codes.ERR_TIMEOUT - } - log.error(err) - throw err - } finally { - pendingDial.destroy() - } - } - - /** - * Connects to a given `peer` by dialing all of its known addresses. - * The dial to the first address that is successfully able to upgrade a connection - * will be used. - * - * @param {PeerId|Multiaddr|string} peer - The peer to dial - * @returns {Promise} - */ - async _createCancellableDialTarget (peer) { - // Make dial target promise cancellable - const id = `${(parseInt(String(Math.random() * 1e9), 10)).toString() + Date.now()}` - const cancellablePromise = new Promise((resolve, reject) => { - this._pendingDialTargets.set(id, { resolve, reject }) - }) - - try { - const dialTarget = await Promise.race([ - this._createDialTarget(peer), - cancellablePromise - ]) - - return dialTarget - } finally { - this._pendingDialTargets.delete(id) - } - } - - /** - * 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. - * Multiaddrs not supported by the available transports will be filtered out. - * - * @private - * @param {PeerId|Multiaddr|string} peer - A PeerId or Multiaddr - * @returns {Promise} - */ - async _createDialTarget (peer) { - const { id, multiaddrs } = getPeer(peer) - - if (multiaddrs) { - await this.peerStore.addressBook.add(id, multiaddrs) - } - - let knownAddrs = await pipe( - await this.peerStore.addressBook.getMultiaddrsForPeer(id, this.addressSorter), - (source) => filter(source, async (multiaddr) => { - return !(await this.connectionGater.denyDialMultiaddr(id, multiaddr)) - }), - (source) => all(source) - ) - - // 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)) { - knownAddrs = knownAddrs.filter((addr) => !peer.equals(addr)) - knownAddrs.unshift(peer) - } - - /** @type {Multiaddr[]} */ - const addrs = [] - for (const a of knownAddrs) { - const resolvedAddrs = await this._resolve(a) - resolvedAddrs.forEach(ra => addrs.push(ra)) - } - - // Multiaddrs not supported by the available transports will be filtered out. - const supportedAddrs = addrs.filter(a => this.transportManager.transportForMultiaddr(a)) - - if (supportedAddrs.length > this.maxAddrsToDial) { - await this.peerStore.delete(id) - throw errCode(new Error('dial with more addresses than allowed'), codes.ERR_TOO_MANY_ADDRESSES) - } - - return { - id: id.toB58String(), - addrs: supportedAddrs - } - } - - /** - * Creates a PendingDial that wraps the underlying DialRequest - * - * @private - * @param {DialTarget} dialTarget - * @param {object} [options] - * @param {AbortSignal} [options.signal] - An AbortController signal - * @returns {PendingDial} - */ - _createPendingDial (dialTarget, options = {}) { - /** - * @param {Multiaddr} addr - * @param {{ signal: { aborted: any; }; }} options - */ - const dialAction = (addr, options) => { - if (options.signal.aborted) throw errCode(new Error('already aborted'), codes.ERR_ALREADY_ABORTED) - return this.transportManager.dial(addr, options) - } - - const dialRequest = new DialRequest({ - addrs: dialTarget.addrs, - dialAction, - dialer: this - }) - - // Combine the timeout signal and options.signal, if provided - const timeoutController = new TimeoutController(this.timeout) - - const signals = [timeoutController.signal] - options.signal && signals.push(options.signal) - const signal = anySignal(signals) - - // this signal will potentially be used while dialing lots of - // peers so prevent MaxListenersExceededWarning appearing in the console - try { - // fails on node < 15.4 - setMaxListeners && setMaxListeners(Infinity, signal) - } catch {} - - const pendingDial = { - dialRequest, - controller: timeoutController, - promise: dialRequest.run({ ...options, signal }), - destroy: () => { - timeoutController.clear() - this._pendingDials.delete(dialTarget.id) - } - } - this._pendingDials.set(dialTarget.id, pendingDial) - - return pendingDial - } - - /** - * @param {number} num - */ - getTokens (num) { - const total = Math.min(num, this.maxDialsPerPeer, this.tokens.length) - const tokens = this.tokens.splice(0, total) - log('%d tokens request, returning %d, %d remaining', num, total, this.tokens.length) - return tokens - } - - /** - * @param {number} token - */ - releaseToken (token) { - // Guard against duplicate releases - if (this.tokens.indexOf(token) > -1) return - log('token %d released', token) - this.tokens.push(token) - } - - /** - * Resolve multiaddr recursively. - * - * @param {Multiaddr} ma - * @returns {Promise} - */ - 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) - })) - - const addrs = recursiveMultiaddrs.flat() - return addrs.reduce((array, newM) => { - if (!array.find(m => m.equals(newM))) { - array.push(newM) - } - return array - }, /** @type {Multiaddr[]} */([])) - } - - /** - * Resolve a given multiaddr. If this fails, an empty array will be returned - * - * @param {Multiaddr} ma - * @returns {Promise} - */ - async _resolveRecord (ma) { - try { - ma = new 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 diff --git a/src/dialer/index.ts b/src/dialer/index.ts new file mode 100644 index 0000000000..30dc731e03 --- /dev/null +++ b/src/dialer/index.ts @@ -0,0 +1,374 @@ +import { logger } from '@libp2p/logger' +import all from 'it-all' +import filter from 'it-filter' +import { pipe } from 'it-pipe' +import errCode from 'err-code' +import { Multiaddr } from '@multiformats/multiaddr' +import { TimeoutController } from 'timeout-abort-controller' +import { AbortError } from '@libp2p/interfaces/errors' +import { anySignal } from 'any-signal' +// @ts-expect-error setMaxListeners is missing from the node 16 types +import { setMaxListeners } from 'events' +import { DialAction, DialRequest } from './dial-request.js' +import { publicAddressesFirst } from '@libp2p/utils/address-sort' +import { trackedMap } from '@libp2p/tracked-map' +import { codes } from '../errors.js' +import { + DIAL_TIMEOUT, + MAX_PARALLEL_DIALS, + MAX_PER_PEER_DIALS, + MAX_ADDRS_TO_DIAL +} from '../constants.js' +import type { Connection } from '@libp2p/interfaces/connection' +import type { AbortOptions, Startable } from '@libp2p/interfaces' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import { getPeer } from '../get-peer.js' +import sort from 'it-sort' +import type { Components } from '@libp2p/interfaces/components' +import type { Dialer, DialerInit } from '@libp2p/interfaces/dialer' +import map from 'it-map' +import type { AddressSorter } from '@libp2p/interfaces/peer-store' + +const log = logger('libp2p:dialer') + +const METRICS_COMPONENT = 'dialler' +const METRICS_PENDING_DIALS = 'pending-dials' +const METRICS_PENDING_DIAL_TARGETS = 'pending-dial-targets' + +export interface DialTarget { + id: string + addrs: Multiaddr[] +} + +export interface PendingDial { + dialRequest: DialRequest + controller: TimeoutController + promise: Promise + destroy: () => void +} + +export interface PendingDialTarget { + resolve: (value: any) => void + reject: (err: Error) => void +} + +export class DefaultDialer implements Dialer, Startable { + private readonly components: Components + private readonly addressSorter: AddressSorter + private readonly maxAddrsToDial: number + private readonly timeout: number + private readonly maxDialsPerPeer: number + public tokens: number[] + public pendingDials: Map + public pendingDialTargets: Map + private started: boolean + + constructor (components: Components, init: DialerInit = {}) { + this.components = components + this.started = false + this.addressSorter = init.addressSorter ?? publicAddressesFirst + this.maxAddrsToDial = init.maxAddrsToDial ?? MAX_ADDRS_TO_DIAL + this.timeout = init.dialTimeout ?? DIAL_TIMEOUT + this.maxDialsPerPeer = init.maxDialsPerPeer ?? MAX_PER_PEER_DIALS + this.tokens = [...new Array(init.maxParallelDials ?? MAX_PARALLEL_DIALS)].map((_, index) => index) + this.pendingDials = trackedMap({ + component: METRICS_COMPONENT, + metric: METRICS_PENDING_DIALS, + metrics: init.metrics + }) + this.pendingDialTargets = trackedMap({ + component: METRICS_COMPONENT, + metric: METRICS_PENDING_DIAL_TARGETS, + metrics: init.metrics + }) + + for (const [key, value] of Object.entries(init.resolvers ?? {})) { + Multiaddr.resolvers.set(key, value) + } + } + + isStarted () { + return this.started + } + + async start () { + this.started = true + } + + /** + * Clears any pending dials + */ + async stop () { + this.started = false + + for (const dial of this.pendingDials.values()) { + try { + dial.controller.abort() + } catch (err: any) { + log.error(err) + } + } + this.pendingDials.clear() + + for (const pendingTarget of this.pendingDialTargets.values()) { + pendingTarget.reject(new AbortError('Dialer was destroyed')) + } + this.pendingDialTargets.clear() + } + + /** + * Connects to a given `peer` by dialing all of its known addresses. + * The dial to the first address that is successfully able to upgrade a connection + * will be used. + */ + async dial (peer: PeerId | Multiaddr, options: AbortOptions = {}): Promise { + const { id, multiaddrs } = getPeer(peer) + + if (this.components.getPeerId().equals(id)) { + throw errCode(new Error('Tried to dial self'), codes.ERR_DIALED_SELF) + } + + log('check multiaddrs %p', id) + + if (multiaddrs != null && multiaddrs.length > 0) { + log('storing multiaddrs %p', id, multiaddrs) + await this.components.getPeerStore().addressBook.add(id, multiaddrs) + } + + if (await this.components.getConnectionGater().denyDialPeer(id)) { + throw errCode(new Error('The dial request is blocked by gater.allowDialPeer'), codes.ERR_PEER_DIAL_INTERCEPTED) + } + + log('dial to %p', id) + + const existingConnection = this.components.getConnectionManager().getConnection(id) + + if (existingConnection != null) { + log('had an existing connection to %p', id) + + return existingConnection + } + + log('creating dial target for %p', id) + + const dialTarget = await this._createCancellableDialTarget(id) + + if (dialTarget.addrs.length === 0) { + throw errCode(new Error('The dial request has no valid addresses'), codes.ERR_NO_VALID_ADDRESSES) + } + + const pendingDial = this.pendingDials.get(dialTarget.id) ?? this._createPendingDial(dialTarget, options) + + try { + const connection = await pendingDial.promise + log('dial succeeded to %s', dialTarget.id) + return connection + } catch (err: any) { + log('dial failed to %s', dialTarget.id, err) + // Error is a timeout + if (pendingDial.controller.signal.aborted) { + err.code = codes.ERR_TIMEOUT + } + log.error(err) + throw err + } finally { + pendingDial.destroy() + } + } + + async dialProtocol (peer: PeerId | Multiaddr, protocols: string | string[], options: AbortOptions = {}) { + if (protocols == null) { + throw errCode(new Error('no protocols were provided to open a stream'), codes.ERR_INVALID_PROTOCOLS_FOR_STREAM) + } + + protocols = Array.isArray(protocols) ? protocols : [protocols] + + if (protocols.length === 0) { + throw errCode(new Error('no protocols were provided to open a stream'), codes.ERR_INVALID_PROTOCOLS_FOR_STREAM) + } + + const connection = await this.dial(peer, options) + + return await connection.newStream(protocols) + } + + /** + * Connects to a given `peer` by dialing all of its known addresses. + * The dial to the first address that is successfully able to upgrade a connection + * will be used. + */ + async _createCancellableDialTarget (peer: PeerId): Promise { + // Make dial target promise cancellable + const id = `${(parseInt(String(Math.random() * 1e9), 10)).toString()}${Date.now()}` + const cancellablePromise = new Promise((resolve, reject) => { + this.pendingDialTargets.set(id, { resolve, reject }) + }) + + try { + const dialTarget = await Promise.race([ + this._createDialTarget(peer), + cancellablePromise + ]) + + return dialTarget + } finally { + this.pendingDialTargets.delete(id) + } + } + + /** + * 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. + * Multiaddrs not supported by the available transports will be filtered out. + */ + async _createDialTarget (peer: PeerId): Promise { + const knownAddrs = await pipe( + await this.components.getPeerStore().addressBook.get(peer), + (source) => filter(source, async (address) => { + return !(await this.components.getConnectionGater().denyDialMultiaddr(peer, address.multiaddr)) + }), + (source) => sort(source, this.addressSorter), + (source) => map(source, (address) => { + const ma = address.multiaddr + + if (peer.toString() === ma.getPeerId()) { + return ma + } + + return ma.encapsulate(`/p2p/${peer.toString()}`) + }), + async (source) => await all(source) + ) + + const addrs: Multiaddr[] = [] + for (const a of knownAddrs) { + const resolvedAddrs = await this._resolve(a) + + log('resolved %s to %s', a, resolvedAddrs) + + resolvedAddrs.forEach(ra => addrs.push(ra)) + } + + // Multiaddrs not supported by the available transports will be filtered out. + const supportedAddrs = addrs.filter(a => this.components.getTransportManager().transportForMultiaddr(a)) + + if (supportedAddrs.length > this.maxAddrsToDial) { + await this.components.getPeerStore().delete(peer) + throw errCode(new Error('dial with more addresses than allowed'), codes.ERR_TOO_MANY_ADDRESSES) + } + + return { + id: peer.toString(), + addrs: supportedAddrs + } + } + + /** + * Creates a PendingDial that wraps the underlying DialRequest + */ + _createPendingDial (dialTarget: DialTarget, options: AbortOptions = {}): PendingDial { + /** + * @param {Multiaddr} addr + * @param {{ signal: { aborted: any; }; }} options + */ + const dialAction: DialAction = async (addr, options = {}) => { + if (options.signal?.aborted === true) { + throw errCode(new Error('already aborted'), codes.ERR_ALREADY_ABORTED) + } + + return await this.components.getTransportManager().dial(addr, options) + } + + const dialRequest = new DialRequest({ + addrs: dialTarget.addrs, + dialAction, + dialer: this + }) + + // Combine the timeout signal and options.signal, if provided + const timeoutController = new TimeoutController(this.timeout) + + const signals = [timeoutController.signal] + ;(options.signal != null) && signals.push(options.signal) + const signal = anySignal(signals) + + // this signal will potentially be used while dialing lots of + // peers so prevent MaxListenersExceededWarning appearing in the console + try { + // fails on node < 15.4 + setMaxListeners?.(Infinity, signal) + } catch {} + + const pendingDial = { + dialRequest, + controller: timeoutController, + promise: dialRequest.run({ ...options, signal }), + destroy: () => { + timeoutController.clear() + this.pendingDials.delete(dialTarget.id) + } + } + this.pendingDials.set(dialTarget.id, pendingDial) + + return pendingDial + } + + getTokens (num: number) { + const total = Math.min(num, this.maxDialsPerPeer, this.tokens.length) + const tokens = this.tokens.splice(0, total) + log('%d tokens request, returning %d, %d remaining', num, total, this.tokens.length) + return tokens + } + + releaseToken (token: number) { + // Guard against duplicate releases + if (this.tokens.includes(token)) { + return + } + + log('token %d released', token) + this.tokens.push(token) + } + + /** + * Resolve multiaddr recursively + */ + async _resolve (ma: Multiaddr): Promise { + // 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(async (nm) => { + return await this._resolve(nm) + })) + + const addrs = recursiveMultiaddrs.flat() + return addrs.reduce((array, newM) => { + if (array.find(m => m.equals(newM)) == null) { + array.push(newM) + } + return array + }, ([])) + } + + /** + * Resolve a given multiaddr. If this fails, an empty array will be returned + */ + async _resolveRecord (ma: Multiaddr): Promise { + try { + ma = new Multiaddr(ma.toString()) // Use current multiaddr module + const multiaddrs = await ma.resolve() + return multiaddrs + } catch (err) { + log.error(`multiaddr ${ma.toString()} could not be resolved`, err) + return [] + } + } +} diff --git a/src/errors.js b/src/errors.js deleted file mode 100644 index 4c34ae511a..0000000000 --- a/src/errors.js +++ /dev/null @@ -1,66 +0,0 @@ -'use strict' - -exports.messages = { - NOT_STARTED_YET: 'The libp2p node is not started yet', - DHT_DISABLED: 'DHT is not available', - CONN_ENCRYPTION_REQUIRED: 'At least one connection encryption module is required', - NOT_FOUND: 'Not found' -} - -exports.codes = { - DHT_DISABLED: 'ERR_DHT_DISABLED', - PUBSUB_NOT_STARTED: 'ERR_PUBSUB_NOT_STARTED', - DHT_NOT_STARTED: 'ERR_DHT_NOT_STARTED', - CONN_ENCRYPTION_REQUIRED: 'ERR_CONN_ENCRYPTION_REQUIRED', - ERR_PEER_DIAL_INTERCEPTED: 'ERR_PEER_DIAL_INTERCEPTED', - ERR_CONNECTION_INTERCEPTED: 'ERR_CONNECTION_INTERCEPTED', - ERR_INVALID_PROTOCOLS_FOR_STREAM: 'ERR_INVALID_PROTOCOLS_FOR_STREAM', - ERR_CONNECTION_ENDED: 'ERR_CONNECTION_ENDED', - ERR_CONNECTION_FAILED: 'ERR_CONNECTION_FAILED', - ERR_NODE_NOT_STARTED: 'ERR_NODE_NOT_STARTED', - ERR_ALREADY_ABORTED: 'ERR_ALREADY_ABORTED', - ERR_TOO_MANY_ADDRESSES: 'ERR_TOO_MANY_ADDRESSES', - ERR_NO_VALID_ADDRESSES: 'ERR_NO_VALID_ADDRESSES', - ERR_RELAYED_DIAL: 'ERR_RELAYED_DIAL', - ERR_DIALED_SELF: 'ERR_DIALED_SELF', - ERR_DISCOVERED_SELF: 'ERR_DISCOVERED_SELF', - ERR_DUPLICATE_TRANSPORT: 'ERR_DUPLICATE_TRANSPORT', - ERR_ENCRYPTION_FAILED: 'ERR_ENCRYPTION_FAILED', - ERR_HOP_REQUEST_FAILED: 'ERR_HOP_REQUEST_FAILED', - ERR_INVALID_KEY: 'ERR_INVALID_KEY', - ERR_INVALID_MESSAGE: 'ERR_INVALID_MESSAGE', - ERR_INVALID_PARAMETERS: 'ERR_INVALID_PARAMETERS', - ERR_INVALID_PEER: 'ERR_INVALID_PEER', - ERR_MUXER_UNAVAILABLE: 'ERR_MUXER_UNAVAILABLE', - ERR_NOT_FOUND: 'ERR_NOT_FOUND', - ERR_TIMEOUT: 'ERR_TIMEOUT', - ERR_TRANSPORT_UNAVAILABLE: 'ERR_TRANSPORT_UNAVAILABLE', - ERR_TRANSPORT_DIAL_FAILED: 'ERR_TRANSPORT_DIAL_FAILED', - ERR_UNSUPPORTED_PROTOCOL: 'ERR_UNSUPPORTED_PROTOCOL', - ERR_INVALID_MULTIADDR: 'ERR_INVALID_MULTIADDR', - ERR_SIGNATURE_NOT_VALID: 'ERR_SIGNATURE_NOT_VALID', - ERR_FIND_SELF: 'ERR_FIND_SELF', - ERR_NO_ROUTERS_AVAILABLE: 'ERR_NO_ROUTERS_AVAILABLE', - ERR_CONNECTION_NOT_MULTIPLEXED: 'ERR_CONNECTION_NOT_MULTIPLEXED', - ERR_NO_DIAL_TOKENS: 'ERR_NO_DIAL_TOKENS', - ERR_KEYCHAIN_REQUIRED: 'ERR_KEYCHAIN_REQUIRED', - ERR_INVALID_CMS: 'ERR_INVALID_CMS', - ERR_MISSING_KEYS: 'ERR_MISSING_KEYS', - ERR_NO_KEY: 'ERR_NO_KEY', - ERR_INVALID_KEY_NAME: 'ERR_INVALID_KEY_NAME', - ERR_INVALID_KEY_TYPE: 'ERR_INVALID_KEY_TYPE', - ERR_KEY_ALREADY_EXISTS: 'ERR_KEY_ALREADY_EXISTS', - ERR_INVALID_KEY_SIZE: 'ERR_INVALID_KEY_SIZE', - ERR_KEY_NOT_FOUND: 'ERR_KEY_NOT_FOUND', - ERR_OLD_KEY_NAME_INVALID: 'ERR_OLD_KEY_NAME_INVALID', - ERR_NEW_KEY_NAME_INVALID: 'ERR_NEW_KEY_NAME_INVALID', - ERR_PASSWORD_REQUIRED: 'ERR_PASSWORD_REQUIRED', - ERR_PEM_REQUIRED: 'ERR_PEM_REQUIRED', - ERR_CANNOT_READ_KEY: 'ERR_CANNOT_READ_KEY', - ERR_MISSING_PRIVATE_KEY: 'ERR_MISSING_PRIVATE_KEY', - ERR_INVALID_OLD_PASS_TYPE: 'ERR_INVALID_OLD_PASS_TYPE', - ERR_INVALID_NEW_PASS_TYPE: 'ERR_INVALID_NEW_PASS_TYPE', - ERR_INVALID_PASS_LENGTH: 'ERR_INVALID_PASS_LENGTH', - ERR_NOT_IMPLEMENTED: 'ERR_NOT_IMPLEMENTED', - ERR_WRONG_PING_ACK: 'ERR_WRONG_PING_ACK' -} diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000000..8b1eff04f6 --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,71 @@ +export enum messages { + NOT_STARTED_YET = 'The libp2p node is not started yet', + DHT_DISABLED = 'DHT is not available', + CONN_ENCRYPTION_REQUIRED = 'At least one connection encryption module is required', + ERR_TRANSPORTS_REQUIRED = 'At least one transport module is required', + ERR_PROTECTOR_REQUIRED = 'Private network is enforced, but no protector was provided', + NOT_FOUND = 'Not found' +} + +export enum codes { + DHT_DISABLED = 'ERR_DHT_DISABLED', + PUBSUB_NOT_STARTED = 'ERR_PUBSUB_NOT_STARTED', + DHT_NOT_STARTED = 'ERR_DHT_NOT_STARTED', + CONN_ENCRYPTION_REQUIRED = 'ERR_CONN_ENCRYPTION_REQUIRED', + ERR_TRANSPORTS_REQUIRED = 'ERR_TRANSPORTS_REQUIRED', + ERR_PROTECTOR_REQUIRED = 'ERR_PROTECTOR_REQUIRED', + ERR_PEER_DIAL_INTERCEPTED = 'ERR_PEER_DIAL_INTERCEPTED', + ERR_CONNECTION_INTERCEPTED = 'ERR_CONNECTION_INTERCEPTED', + ERR_INVALID_PROTOCOLS_FOR_STREAM = 'ERR_INVALID_PROTOCOLS_FOR_STREAM', + ERR_CONNECTION_ENDED = 'ERR_CONNECTION_ENDED', + ERR_CONNECTION_FAILED = 'ERR_CONNECTION_FAILED', + ERR_NODE_NOT_STARTED = 'ERR_NODE_NOT_STARTED', + ERR_ALREADY_ABORTED = 'ERR_ALREADY_ABORTED', + ERR_TOO_MANY_ADDRESSES = 'ERR_TOO_MANY_ADDRESSES', + ERR_NO_VALID_ADDRESSES = 'ERR_NO_VALID_ADDRESSES', + ERR_RELAYED_DIAL = 'ERR_RELAYED_DIAL', + ERR_DIALED_SELF = 'ERR_DIALED_SELF', + ERR_DISCOVERED_SELF = 'ERR_DISCOVERED_SELF', + ERR_DUPLICATE_TRANSPORT = 'ERR_DUPLICATE_TRANSPORT', + ERR_ENCRYPTION_FAILED = 'ERR_ENCRYPTION_FAILED', + ERR_HOP_REQUEST_FAILED = 'ERR_HOP_REQUEST_FAILED', + ERR_INVALID_KEY = 'ERR_INVALID_KEY', + ERR_INVALID_MESSAGE = 'ERR_INVALID_MESSAGE', + ERR_INVALID_PARAMETERS = 'ERR_INVALID_PARAMETERS', + ERR_INVALID_PEER = 'ERR_INVALID_PEER', + ERR_MUXER_UNAVAILABLE = 'ERR_MUXER_UNAVAILABLE', + ERR_NOT_FOUND = 'ERR_NOT_FOUND', + ERR_TIMEOUT = 'ERR_TIMEOUT', + ERR_TRANSPORT_UNAVAILABLE = 'ERR_TRANSPORT_UNAVAILABLE', + ERR_TRANSPORT_DIAL_FAILED = 'ERR_TRANSPORT_DIAL_FAILED', + ERR_UNSUPPORTED_PROTOCOL = 'ERR_UNSUPPORTED_PROTOCOL', + ERR_PROTOCOL_HANDLER_ALREADY_REGISTERED = 'ERR_PROTOCOL_HANDLER_ALREADY_REGISTERED', + ERR_INVALID_MULTIADDR = 'ERR_INVALID_MULTIADDR', + ERR_SIGNATURE_NOT_VALID = 'ERR_SIGNATURE_NOT_VALID', + ERR_FIND_SELF = 'ERR_FIND_SELF', + ERR_NO_ROUTERS_AVAILABLE = 'ERR_NO_ROUTERS_AVAILABLE', + ERR_CONNECTION_NOT_MULTIPLEXED = 'ERR_CONNECTION_NOT_MULTIPLEXED', + ERR_NO_DIAL_TOKENS = 'ERR_NO_DIAL_TOKENS', + ERR_KEYCHAIN_REQUIRED = 'ERR_KEYCHAIN_REQUIRED', + ERR_INVALID_CMS = 'ERR_INVALID_CMS', + ERR_MISSING_KEYS = 'ERR_MISSING_KEYS', + ERR_NO_KEY = 'ERR_NO_KEY', + ERR_INVALID_KEY_NAME = 'ERR_INVALID_KEY_NAME', + ERR_INVALID_KEY_TYPE = 'ERR_INVALID_KEY_TYPE', + ERR_KEY_ALREADY_EXISTS = 'ERR_KEY_ALREADY_EXISTS', + ERR_INVALID_KEY_SIZE = 'ERR_INVALID_KEY_SIZE', + ERR_KEY_NOT_FOUND = 'ERR_KEY_NOT_FOUND', + ERR_OLD_KEY_NAME_INVALID = 'ERR_OLD_KEY_NAME_INVALID', + ERR_NEW_KEY_NAME_INVALID = 'ERR_NEW_KEY_NAME_INVALID', + ERR_PASSWORD_REQUIRED = 'ERR_PASSWORD_REQUIRED', + ERR_PEM_REQUIRED = 'ERR_PEM_REQUIRED', + ERR_CANNOT_READ_KEY = 'ERR_CANNOT_READ_KEY', + ERR_MISSING_PRIVATE_KEY = 'ERR_MISSING_PRIVATE_KEY', + ERR_MISSING_PUBLIC_KEY = 'ERR_MISSING_PUBLIC_KEY', + ERR_INVALID_OLD_PASS_TYPE = 'ERR_INVALID_OLD_PASS_TYPE', + ERR_INVALID_NEW_PASS_TYPE = 'ERR_INVALID_NEW_PASS_TYPE', + ERR_INVALID_PASS_LENGTH = 'ERR_INVALID_PASS_LENGTH', + ERR_NOT_IMPLEMENTED = 'ERR_NOT_IMPLEMENTED', + ERR_WRONG_PING_ACK = 'ERR_WRONG_PING_ACK', + ERR_INVALID_RECORD = 'ERR_INVALID_RECORD' +} diff --git a/src/fetch/README.md b/src/fetch/README.md index 7ea9997a5e..0ab7e8772b 100644 --- a/src/fetch/README.md +++ b/src/fetch/README.md @@ -12,7 +12,7 @@ The fetch protocol is a simple protocol for requesting a value corresponding to ## Usage ```javascript -const Libp2p = require('libp2p') +import { createLibp2p } from 'libp2p' /** * Given a key (as a string) returns a value (as a Uint8Array), or null if the key isn't found. diff --git a/src/fetch/constants.js b/src/fetch/constants.js deleted file mode 100644 index 2c1044e535..0000000000 --- a/src/fetch/constants.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict' - -module.exports = { - // https://github.com/libp2p/specs/tree/master/fetch#wire-protocol - PROTOCOL: '/libp2p/fetch/0.0.1' -} diff --git a/src/fetch/constants.ts b/src/fetch/constants.ts new file mode 100644 index 0000000000..c9c425d60d --- /dev/null +++ b/src/fetch/constants.ts @@ -0,0 +1,3 @@ + +// https://github.com/libp2p/specs/tree/master/fetch#wire-protocol +export const PROTOCOL = '/libp2p/fetch/0.0.1' diff --git a/src/fetch/index.js b/src/fetch/index.ts similarity index 52% rename from src/fetch/index.js rename to src/fetch/index.ts index ef8c37f153..65f906dbf1 100644 --- a/src/fetch/index.js +++ b/src/fetch/index.ts @@ -1,24 +1,30 @@ -'use strict' - -const debug = require('debug') -const log = Object.assign(debug('libp2p:fetch'), { - error: debug('libp2p:fetch:err') -}) -const errCode = require('err-code') -const { codes } = require('../errors') -const lp = require('it-length-prefixed') -const { FetchRequest, FetchResponse } = require('./proto') -// @ts-ignore it-handshake does not export types -const handshake = require('it-handshake') -const { PROTOCOL } = require('./constants') +import { logger } from '@libp2p/logger' +import errCode from 'err-code' +import { codes } from '../errors.js' +import * as lp from 'it-length-prefixed' +import { FetchRequest, FetchResponse } from './pb/proto.js' +import { handshake } from 'it-handshake' +import { PROTOCOL } from './constants.js' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { Startable } from '@libp2p/interfaces' +import type { Stream } from '@libp2p/interfaces/connection' +import type { IncomingStreamData } from '@libp2p/interfaces/registrar' +import type { Components } from '@libp2p/interfaces/components' + +const log = logger('libp2p:fetch') + +export interface FetchInit { + protocolPrefix: string +} -/** - * @typedef {import('../')} Libp2p - * @typedef {import('multiaddr').Multiaddr} Multiaddr - * @typedef {import('peer-id')} PeerId - * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream - * @typedef {(key: string) => Promise} LookupFunction - */ +export interface HandleMessageOptions { + stream: Stream + protocol: string +} + +export interface LookupFunction { + (key: string): Promise +} /** * A simple libp2p protocol for requesting a value corresponding to a key from a peer. @@ -26,36 +32,54 @@ const { PROTOCOL } = require('./constants') * a given key. Each lookup function must act on a distinct part of the overall key space, defined * by a fixed prefix that all keys that should be routed to that lookup function will start with. */ -class FetchProtocol { - /** - * @param {Libp2p} libp2p - */ - constructor (libp2p) { - this._lookupFunctions = new Map() // Maps key prefix to value lookup function - this._libp2p = libp2p +export class FetchService implements Startable { + private readonly components: Components + private readonly lookupFunctions: Map + private readonly protocol: string + private started: boolean + + constructor (components: Components, init: FetchInit) { + this.started = false + this.components = components + this.protocol = PROTOCOL + this.lookupFunctions = new Map() // Maps key prefix to value lookup function this.handleMessage = this.handleMessage.bind(this) } + async start () { + await this.components.getRegistrar().handle(this.protocol, (data) => { + void this.handleMessage(data).catch(err => { + log.error(err) + }) + }) + this.started = true + } + + async stop () { + await this.components.getRegistrar().unhandle(this.protocol) + this.started = false + } + + isStarted () { + return this.started + } + /** - * Sends a request to fetch the value associated with the given key from the given peer. - * - * @param {PeerId|Multiaddr} peer - * @param {string} key - * @returns {Promise} + * Sends a request to fetch the value associated with the given key from the given peer */ - async fetch (peer, key) { - // @ts-ignore multiaddr might not have toB58String - log('dialing %s to %s', this._protocol, peer.toB58String ? peer.toB58String() : peer) + async fetch (peer: PeerId, key: string): Promise { + log('dialing %s to %p', this.protocol, peer) - const connection = await this._libp2p.dial(peer) - const { stream } = await connection.newStream(FetchProtocol.PROTOCOL) + const connection = await this.components.getDialer().dial(peer) + const { stream } = await connection.newStream([this.protocol]) const shake = handshake(stream) // send message const request = new FetchRequest({ identifier: key }) - shake.write(lp.encode.single(FetchRequest.encode(request).finish())) + shake.write(lp.encode.single(FetchRequest.encode(request).finish()).slice()) // read response + // @ts-expect-error fromReader returns a Source which has no .next method const response = FetchResponse.decode((await lp.decode.fromReader(shake.reader).next()).value.slice()) switch (response.status) { case (FetchResponse.StatusCode.OK): { @@ -78,21 +102,18 @@ class FetchProtocol { * Invoked when a fetch request is received. Reads the request message off the given stream and * responds based on looking up the key in the request via the lookup callback that corresponds * to the key's prefix. - * - * @param {object} options - * @param {MuxedStream} options.stream - * @param {string} options.protocol */ - async handleMessage (options) { - const { stream } = options + async handleMessage (data: IncomingStreamData) { + const { stream } = data const shake = handshake(stream) + // @ts-expect-error fromReader returns a Source which has no .next method const request = FetchRequest.decode((await lp.decode.fromReader(shake.reader).next()).value.slice()) let response const lookup = this._getLookupFunction(request.identifier) - if (lookup) { + if (lookup != null) { const data = await lookup(request.identifier) - if (data) { + if (data != null) { response = new FetchResponse({ status: FetchResponse.StatusCode.OK, data }) } else { response = new FetchResponse({ status: FetchResponse.StatusCode.NOT_FOUND }) @@ -102,58 +123,46 @@ class FetchProtocol { response = new FetchResponse({ status: FetchResponse.StatusCode.ERROR, data: errmsg }) } - shake.write(lp.encode.single(FetchResponse.encode(response).finish())) + shake.write(lp.encode.single(FetchResponse.encode(response).finish()).slice()) } /** * Given a key, finds the appropriate function for looking up its corresponding value, based on * the key's prefix. - * - * @param {string} key */ - _getLookupFunction (key) { - for (const prefix of this._lookupFunctions.keys()) { + _getLookupFunction (key: string) { + for (const prefix of this.lookupFunctions.keys()) { if (key.startsWith(prefix)) { - return this._lookupFunctions.get(prefix) + return this.lookupFunctions.get(prefix) } } - return null } /** * Registers a new lookup callback that can map keys to values, for a given set of keys that - * share the same prefix. - * - * @param {string} prefix - * @param {LookupFunction} lookup + * share the same prefix */ - registerLookupFunction (prefix, lookup) { - if (this._lookupFunctions.has(prefix)) { + registerLookupFunction (prefix: string, lookup: LookupFunction) { + if (this.lookupFunctions.has(prefix)) { throw errCode(new Error("Fetch protocol handler for key prefix '" + prefix + "' already registered"), codes.ERR_KEY_ALREADY_EXISTS) } - this._lookupFunctions.set(prefix, lookup) + + this.lookupFunctions.set(prefix, lookup) } /** * Registers a new lookup callback that can map keys to values, for a given set of keys that * share the same prefix. - * - * @param {string} prefix - * @param {LookupFunction} [lookup] */ - unregisterLookupFunction (prefix, lookup) { + unregisterLookupFunction (prefix: string, lookup?: LookupFunction) { if (lookup != null) { - const existingLookup = this._lookupFunctions.get(prefix) + const existingLookup = this.lookupFunctions.get(prefix) if (existingLookup !== lookup) { return } } - this._lookupFunctions.delete(prefix) + this.lookupFunctions.delete(prefix) } } - -FetchProtocol.PROTOCOL = PROTOCOL - -exports = module.exports = FetchProtocol diff --git a/src/fetch/proto.d.ts b/src/fetch/pb/proto.d.ts similarity index 100% rename from src/fetch/proto.d.ts rename to src/fetch/pb/proto.d.ts diff --git a/src/fetch/proto.js b/src/fetch/pb/proto.js similarity index 95% rename from src/fetch/proto.js rename to src/fetch/pb/proto.js index f7de2b1dd5..b77d6375a8 100644 --- a/src/fetch/proto.js +++ b/src/fetch/pb/proto.js @@ -1,15 +1,13 @@ /*eslint-disable*/ -"use strict"; - -var $protobuf = require("protobufjs/minimal"); +import $protobuf from "protobufjs/minimal.js"; // Common aliases -var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; +const $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; // Exported root namespace -var $root = $protobuf.roots["libp2p-fetch"] || ($protobuf.roots["libp2p-fetch"] = {}); +const $root = $protobuf.roots["libp2p-fetch"] || ($protobuf.roots["libp2p-fetch"] = {}); -$root.FetchRequest = (function() { +export const FetchRequest = $root.FetchRequest = (() => { /** * Properties of a FetchRequest. @@ -141,7 +139,7 @@ $root.FetchRequest = (function() { return FetchRequest; })(); -$root.FetchResponse = (function() { +export const FetchResponse = $root.FetchResponse = (() => { /** * Properties of a FetchResponse. @@ -320,7 +318,7 @@ $root.FetchResponse = (function() { * @property {number} ERROR=2 ERROR value */ FetchResponse.StatusCode = (function() { - var valuesById = {}, values = Object.create(valuesById); + const valuesById = {}, values = Object.create(valuesById); values[valuesById[0] = "OK"] = 0; values[valuesById[1] = "NOT_FOUND"] = 1; values[valuesById[2] = "ERROR"] = 2; @@ -330,4 +328,4 @@ $root.FetchResponse = (function() { return FetchResponse; })(); -module.exports = $root; +export { $root as default }; diff --git a/src/fetch/proto.proto b/src/fetch/pb/proto.proto similarity index 100% rename from src/fetch/proto.proto rename to src/fetch/pb/proto.proto diff --git a/src/get-peer.js b/src/get-peer.js deleted file mode 100644 index afad64df06..0000000000 --- a/src/get-peer.js +++ /dev/null @@ -1,49 +0,0 @@ -'use strict' - -const PeerId = require('peer-id') -const { Multiaddr } = require('multiaddr') -const errCode = require('err-code') - -const { codes } = require('./errors') - -/** - * Converts the given `peer` to a `Peer` object. - * If a multiaddr is received, the addressBook is updated. - * - * @param {PeerId|Multiaddr|string} peer - * @returns {{ id: PeerId, multiaddrs: Multiaddr[]|undefined }} - */ -function getPeer (peer) { - if (typeof peer === 'string') { - peer = new Multiaddr(peer) - } - - let addr - if (Multiaddr.isMultiaddr(peer)) { - addr = peer - const idStr = peer.getPeerId() - - if (!idStr) { - throw errCode( - new Error(`${peer} does not have a valid peer type`), - codes.ERR_INVALID_MULTIADDR - ) - } - - try { - peer = PeerId.createFromB58String(idStr) - } catch (/** @type {any} */ err) { - throw errCode( - new Error(`${peer} is not a valid peer type`), - codes.ERR_INVALID_MULTIADDR - ) - } - } - - return { - id: peer, - multiaddrs: addr ? [addr] : undefined - } -} - -module.exports = getPeer diff --git a/src/get-peer.ts b/src/get-peer.ts new file mode 100644 index 0000000000..bc4129d176 --- /dev/null +++ b/src/get-peer.ts @@ -0,0 +1,57 @@ +import { peerIdFromString } from '@libp2p/peer-id' +import { Multiaddr } from '@multiformats/multiaddr' +import errCode from 'err-code' +import { codes } from './errors.js' +import { isPeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerInfo } from '@libp2p/interfaces/peer-info' + +function peerIdFromMultiaddr (ma: Multiaddr) { + const idStr = ma.getPeerId() + + if (idStr == null) { + throw errCode( + new Error(`${ma.toString()} does not have a valid peer type`), + codes.ERR_INVALID_MULTIADDR + ) + } + + try { + return peerIdFromString(idStr) + } catch (err: any) { + throw errCode( + new Error(`${ma.toString()} is not a valid peer type`), + codes.ERR_INVALID_MULTIADDR + ) + } +} + +/** + * Converts the given `peer` to a `Peer` object. + */ +export function getPeer (peer: PeerId | Multiaddr | string): PeerInfo { + if (isPeerId(peer)) { + return { + id: peer, + multiaddrs: [], + protocols: [] + } + } + + if (typeof peer === 'string') { + peer = new Multiaddr(peer) + } + + let addr + + if (Multiaddr.isMultiaddr(peer)) { + addr = peer + peer = peerIdFromMultiaddr(peer) + } + + return { + id: peer, + multiaddrs: addr != null ? [addr] : [], + protocols: [] + } +} diff --git a/src/identify/consts.js b/src/identify/consts.js deleted file mode 100644 index 7c2484aad9..0000000000 --- a/src/identify/consts.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict' - -// @ts-ignore file not listed within the file list of projects -const libp2pVersion = require('../../package.json').version - -module.exports.PROTOCOL_VERSION = 'ipfs/0.1.0' // deprecated -module.exports.AGENT_VERSION = `js-libp2p/${libp2pVersion}` -module.exports.MULTICODEC_IDENTIFY = '/ipfs/id/1.0.0' // deprecated -module.exports.MULTICODEC_IDENTIFY_PUSH = '/ipfs/id/push/1.0.0' // deprecated - -module.exports.IDENTIFY_PROTOCOL_VERSION = '0.1.0' -module.exports.MULTICODEC_IDENTIFY_PROTOCOL_NAME = 'id' -module.exports.MULTICODEC_IDENTIFY_PUSH_PROTOCOL_NAME = 'id/push' -module.exports.MULTICODEC_IDENTIFY_PROTOCOL_VERSION = '1.0.0' -module.exports.MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION = '1.0.0' diff --git a/src/identify/consts.ts b/src/identify/consts.ts new file mode 100644 index 0000000000..adb38720af --- /dev/null +++ b/src/identify/consts.ts @@ -0,0 +1,13 @@ + +import { version } from '../version.js' + +export const PROTOCOL_VERSION = 'ipfs/0.1.0' // deprecated +export const AGENT_VERSION = `js-libp2p/${version}` +export const MULTICODEC_IDENTIFY = '/ipfs/id/1.0.0' // deprecated +export const MULTICODEC_IDENTIFY_PUSH = '/ipfs/id/push/1.0.0' // deprecated + +export const IDENTIFY_PROTOCOL_VERSION = '0.1.0' +export const MULTICODEC_IDENTIFY_PROTOCOL_NAME = 'id' +export const MULTICODEC_IDENTIFY_PUSH_PROTOCOL_NAME = 'id/push' +export const MULTICODEC_IDENTIFY_PROTOCOL_VERSION = '1.0.0' +export const MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION = '1.0.0' diff --git a/src/identify/index.js b/src/identify/index.js deleted file mode 100644 index 16d23a66b4..0000000000 --- a/src/identify/index.js +++ /dev/null @@ -1,384 +0,0 @@ -'use strict' - -const debug = require('debug') -const log = Object.assign(debug('libp2p:identify'), { - error: debug('libp2p:identify:err') -}) -const errCode = require('err-code') -const lp = require('it-length-prefixed') -const { pipe } = require('it-pipe') -const { collect, take, consume } = require('streaming-iterables') -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') - -const PeerId = require('peer-id') -const { Multiaddr } = require('multiaddr') -// @ts-ignore it-buffer does not have types -const { toBuffer } = require('it-buffer') - -const Message = require('./message') - -const Envelope = require('../record/envelope') -const PeerRecord = require('../record/peer-record') - -const { - MULTICODEC_IDENTIFY, - MULTICODEC_IDENTIFY_PUSH, - IDENTIFY_PROTOCOL_VERSION, - MULTICODEC_IDENTIFY_PROTOCOL_NAME, - MULTICODEC_IDENTIFY_PUSH_PROTOCOL_NAME, - MULTICODEC_IDENTIFY_PROTOCOL_VERSION, - MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION -} = require('./consts') - -const { codes } = require('../errors') - -/** - * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection - * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream - */ - -/** - * @typedef {Object} HostProperties - * @property {string} agentVersion - */ - -class IdentifyService { - /** - * @param {import('../')} libp2p - */ - static getProtocolStr (libp2p) { - return { - identifyProtocolStr: `/${libp2p._config.protocolPrefix}/${MULTICODEC_IDENTIFY_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PROTOCOL_VERSION}`, - identifyPushProtocolStr: `/${libp2p._config.protocolPrefix}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION}` - } - } - - /** - * @class - * @param {Object} options - * @param {import('../')} options.libp2p - */ - constructor ({ libp2p }) { - this._libp2p = libp2p - this.peerStore = libp2p.peerStore - this.addressManager = libp2p.addressManager - this.connectionManager = libp2p.connectionManager - this.peerId = libp2p.peerId - - this.handleMessage = this.handleMessage.bind(this) - - const protocolStr = IdentifyService.getProtocolStr(libp2p) - this.identifyProtocolStr = protocolStr.identifyProtocolStr - this.identifyPushProtocolStr = protocolStr.identifyPushProtocolStr - - // Store self host metadata - this._host = { - protocolVersion: `${libp2p._config.protocolPrefix}/${IDENTIFY_PROTOCOL_VERSION}`, - ...libp2p._options.host - } - - // When a new connection happens, trigger identify - this.connectionManager.on('peer:connect', (connection) => { - this.identify(connection).catch(log.error) - }) - - // When self multiaddrs change, trigger identify-push - this.peerStore.on('change:multiaddrs', ({ peerId }) => { - if (peerId.toString() === this.peerId.toString()) { - this.pushToPeerStore().catch(err => log.error(err)) - } - }) - - // When self protocols change, trigger identify-push - this.peerStore.on('change:protocols', ({ peerId }) => { - if (peerId.toString() === this.peerId.toString()) { - this.pushToPeerStore().catch(err => log.error(err)) - } - }) - } - - async start () { - await this.peerStore.metadataBook.setValue(this.peerId, 'AgentVersion', uint8ArrayFromString(this._host.agentVersion)) - await this.peerStore.metadataBook.setValue(this.peerId, 'ProtocolVersion', uint8ArrayFromString(this._host.protocolVersion)) - } - - async stop () { - - } - - /** - * Send an Identify Push update to the list of connections - * - * @param {Connection[]} connections - * @returns {Promise} - */ - async push (connections) { - const signedPeerRecord = await this.peerStore.addressBook.getRawEnvelope(this.peerId) - const listenAddrs = this._libp2p.multiaddrs.map((ma) => ma.bytes) - const protocols = await this.peerStore.protoBook.get(this.peerId) - - const pushes = connections.map(async connection => { - try { - const { stream } = await connection.newStream(this.identifyPushProtocolStr) - - await pipe( - [Message.Identify.encode({ - listenAddrs, - signedPeerRecord, - protocols - }).finish()], - lp.encode(), - stream, - consume - ) - } catch (/** @type {any} */ err) { - // Just log errors - log.error('could not push identify update to peer', err) - } - }) - - return Promise.all(pushes) - } - - /** - * Calls `push` for all peers in the `peerStore` that are connected - */ - async pushToPeerStore () { - // Do not try to push if libp2p node is not running - if (!this._libp2p.isStarted()) { - return - } - - const connections = [] - let connection - for await (const peer of this.peerStore.getPeers()) { - if (peer.protocols.includes(this.identifyPushProtocolStr) && (connection = this.connectionManager.get(peer.id))) { - connections.push(connection) - } - } - - await this.push(connections) - } - - /** - * Requests the `Identify` message from peer associated with the given `connection`. - * If the identified peer does not match the `PeerId` associated with the connection, - * an error will be thrown. - * - * @async - * @param {Connection} connection - * @returns {Promise} - */ - async identify (connection) { - const { stream } = await connection.newStream(this.identifyProtocolStr) - const [data] = await pipe( - [], - stream, - lp.decode(), - take(1), - toBuffer, - collect - ) - - if (!data) { - throw errCode(new Error('No data could be retrieved'), codes.ERR_CONNECTION_ENDED) - } - - let message - try { - message = Message.Identify.decode(data) - } catch (/** @type {any} */ err) { - throw errCode(err, codes.ERR_INVALID_MESSAGE) - } - - const { - publicKey, - listenAddrs, - protocols, - observedAddr, - signedPeerRecord - } = message - - const id = await PeerId.createFromPubKey(publicKey) - - if (connection.remotePeer.toB58String() !== id.toB58String()) { - throw errCode(new Error('identified peer does not match the expected peer'), codes.ERR_INVALID_PEER) - } - - // Get the observedAddr if there is one - const cleanObservedAddr = IdentifyService.getCleanMultiaddr(observedAddr) - - try { - const envelope = await Envelope.openAndCertify(signedPeerRecord, PeerRecord.DOMAIN) - if (await this.peerStore.addressBook.consumePeerRecord(envelope)) { - await this.peerStore.protoBook.set(id, protocols) - await this.peerStore.metadataBook.setValue(id, 'AgentVersion', uint8ArrayFromString(message.agentVersion)) - await this.peerStore.metadataBook.setValue(id, 'ProtocolVersion', uint8ArrayFromString(message.protocolVersion)) - return - } - } catch (/** @type {any} */ err) { - log('received invalid envelope, discard it and fallback to listenAddrs is available', err) - } - - // LEGACY: Update peers data in PeerStore - try { - await this.peerStore.addressBook.set(id, listenAddrs.map((addr) => new Multiaddr(addr))) - } catch (/** @type {any} */ err) { - log.error('received invalid addrs', err) - } - - await this.peerStore.protoBook.set(id, protocols) - await this.peerStore.metadataBook.setValue(id, 'AgentVersion', uint8ArrayFromString(message.agentVersion)) - await this.peerStore.metadataBook.setValue(id, 'ProtocolVersion', uint8ArrayFromString(message.protocolVersion)) - - // TODO: Add and score our observed addr - log('received observed address of %s', cleanObservedAddr) - // this.addressManager.addObservedAddr(observedAddr) - } - - /** - * A handler to register with Libp2p to process identify messages. - * - * @param {Object} options - * @param {Connection} options.connection - * @param {MuxedStream} options.stream - * @param {string} options.protocol - * @returns {Promise|undefined} - */ - handleMessage ({ connection, stream, protocol }) { - switch (protocol) { - case this.identifyProtocolStr: - return this._handleIdentify({ connection, stream }) - case this.identifyPushProtocolStr: - return this._handlePush({ connection, stream }) - default: - log.error('cannot handle unknown protocol %s', protocol) - } - } - - /** - * Sends the `Identify` response with the Signed Peer Record - * to the requesting peer over the given `connection` - * - * @private - * @param {Object} options - * @param {MuxedStream} options.stream - * @param {Connection} options.connection - * @returns {Promise} - */ - async _handleIdentify ({ connection, stream }) { - try { - let publicKey = new Uint8Array(0) - if (this.peerId.pubKey) { - publicKey = this.peerId.pubKey.bytes - } - - const signedPeerRecord = await this.peerStore.addressBook.getRawEnvelope(this.peerId) - const protocols = await this.peerStore.protoBook.get(this.peerId) - - const message = Message.Identify.encode({ - protocolVersion: this._host.protocolVersion, - agentVersion: this._host.agentVersion, - publicKey, - listenAddrs: this._libp2p.multiaddrs.map((ma) => ma.bytes), - signedPeerRecord, - observedAddr: connection.remoteAddr.bytes, - protocols - }).finish() - - await pipe( - [message], - lp.encode(), - stream, - consume - ) - } catch (/** @type {any} */ err) { - log.error('could not respond to identify request', err) - } - } - - /** - * Reads the Identify Push message from the given `connection` - * - * @private - * @param {object} options - * @param {MuxedStream} options.stream - * @param {Connection} options.connection - * @returns {Promise} - */ - async _handlePush ({ connection, stream }) { - let message - try { - const [data] = await pipe( - [], - stream, - lp.decode(), - take(1), - toBuffer, - collect - ) - message = Message.Identify.decode(data) - } catch (/** @type {any} */ err) { - return log.error('received invalid message', err) - } - - const id = connection.remotePeer - - try { - const envelope = await Envelope.openAndCertify(message.signedPeerRecord, PeerRecord.DOMAIN) - if (await this.peerStore.addressBook.consumePeerRecord(envelope)) { - await this.peerStore.protoBook.set(id, message.protocols) - return - } - } catch (/** @type {any} */ err) { - log('received invalid envelope, discard it and fallback to listenAddrs is available', err) - } - - // LEGACY: Update peers data in PeerStore - try { - await this.peerStore.addressBook.set(id, - message.listenAddrs.map((addr) => new Multiaddr(addr))) - } catch (/** @type {any} */ err) { - log.error('received invalid addrs', err) - } - - // Update the protocols - try { - await this.peerStore.protoBook.set(id, message.protocols) - } catch (/** @type {any} */ err) { - log.error('received invalid protocols', err) - } - } - - /** - * Takes the `addr` and converts it to a Multiaddr if possible - * - * @param {Uint8Array | string} addr - * @returns {Multiaddr|null} - */ - static getCleanMultiaddr (addr) { - if (addr && addr.length > 0) { - try { - return new Multiaddr(addr) - } catch (_) { - return null - } - } - return null - } -} - -/** - * The protocols the IdentifyService supports - * - * @property multicodecs - */ -const multicodecs = { - IDENTIFY: MULTICODEC_IDENTIFY, - IDENTIFY_PUSH: MULTICODEC_IDENTIFY_PUSH -} - -IdentifyService.multicodecs = multicodecs -IdentifyService.Messsage = Message - -module.exports = IdentifyService diff --git a/src/identify/index.ts b/src/identify/index.ts new file mode 100644 index 0000000000..bfd3b03390 --- /dev/null +++ b/src/identify/index.ts @@ -0,0 +1,445 @@ +import { logger } from '@libp2p/logger' +import errCode from 'err-code' +import * as lp from 'it-length-prefixed' +import { pipe } from 'it-pipe' +import all from 'it-all' +import take from 'it-take' +import drain from 'it-drain' +import first from 'it-first' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { Multiaddr, protocols } from '@multiformats/multiaddr' +import Message from './pb/message.js' +import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record' +import { + MULTICODEC_IDENTIFY, + MULTICODEC_IDENTIFY_PUSH, + IDENTIFY_PROTOCOL_VERSION, + MULTICODEC_IDENTIFY_PROTOCOL_NAME, + MULTICODEC_IDENTIFY_PUSH_PROTOCOL_NAME, + MULTICODEC_IDENTIFY_PROTOCOL_VERSION, + MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION +} from './consts.js' +import { codes } from '../errors.js' +import type { IncomingStreamData } from '@libp2p/interfaces/registrar' +import type { Connection } from '@libp2p/interfaces/connection' +import type { Startable } from '@libp2p/interfaces' +import { peerIdFromKeys, peerIdFromString } from '@libp2p/peer-id' +import type { Components } from '@libp2p/interfaces/components' + +const log = logger('libp2p:identify') + +export interface HostProperties { + agentVersion: string +} + +export interface IdentifyServiceInit { + protocolPrefix: string + host: HostProperties +} + +export class IdentifyService implements Startable { + private readonly components: Components + private readonly identifyProtocolStr: string + private readonly identifyPushProtocolStr: string + private readonly host: { + protocolVersion: string + agentVersion: string + } + + private started: boolean + + constructor (components: Components, init: IdentifyServiceInit) { + this.components = components + this.started = false + + this.handleMessage = this.handleMessage.bind(this) + + this.identifyProtocolStr = `/${init.protocolPrefix}/${MULTICODEC_IDENTIFY_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PROTOCOL_VERSION}` + this.identifyPushProtocolStr = `/${init.protocolPrefix}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION}` + + // Store self host metadata + this.host = { + protocolVersion: `${init.protocolPrefix}/${IDENTIFY_PROTOCOL_VERSION}`, + ...init.host + } + + // When a new connection happens, trigger identify + this.components.getConnectionManager().addEventListener('peer:connect', (evt) => { + const connection = evt.detail + this.identify(connection).catch(log.error) + }) + + // When self multiaddrs change, trigger identify-push + this.components.getPeerStore().addEventListener('change:multiaddrs', (evt) => { + const { peerId } = evt.detail + + if (this.components.getPeerId().equals(peerId)) { + void this.pushToPeerStore().catch(err => log.error(err)) + } + }) + + // When self protocols change, trigger identify-push + this.components.getPeerStore().addEventListener('change:protocols', (evt) => { + const { peerId } = evt.detail + + if (this.components.getPeerId().equals(peerId)) { + void this.pushToPeerStore().catch(err => log.error(err)) + } + }) + } + + isStarted () { + return this.started + } + + async start () { + if (this.started) { + return + } + + await this.components.getPeerStore().metadataBook.setValue(this.components.getPeerId(), 'AgentVersion', uint8ArrayFromString(this.host.agentVersion)) + await this.components.getPeerStore().metadataBook.setValue(this.components.getPeerId(), 'ProtocolVersion', uint8ArrayFromString(this.host.protocolVersion)) + + await this.components.getRegistrar().handle([ + this.identifyProtocolStr, + this.identifyPushProtocolStr + ], (data) => { + void this.handleMessage(data)?.catch(err => { + log.error(err) + }) + }) + + this.started = true + } + + async stop () { + await this.components.getRegistrar().unhandle(this.identifyProtocolStr) + await this.components.getRegistrar().unhandle(this.identifyPushProtocolStr) + + this.started = false + } + + /** + * Send an Identify Push update to the list of connections + */ + async push (connections: Connection[]): Promise { + const signedPeerRecord = await this.components.getPeerStore().addressBook.getRawEnvelope(this.components.getPeerId()) + const listenAddrs = this.components.getAddressManager().getAddresses().map((ma) => ma.bytes) + const protocols = await this.components.getPeerStore().protoBook.get(this.components.getPeerId()) + + const pushes = connections.map(async connection => { + try { + const { stream } = await connection.newStream([this.identifyPushProtocolStr]) + + await pipe( + [Message.Identify.encode({ + listenAddrs, + signedPeerRecord, + protocols + }).finish()], + lp.encode(), + stream, + drain + ) + } catch (err: any) { + // Just log errors + log.error('could not push identify update to peer', err) + } + }) + + await Promise.all(pushes) + } + + /** + * Calls `push` on all peer connections + */ + async pushToPeerStore () { + // Do not try to push if we are not running + if (!this.isStarted()) { + return + } + + const connections: Connection[] = [] + + for (const [peerIdStr, conns] of this.components.getConnectionManager().getConnectionMap().entries()) { + const peerId = peerIdFromString(peerIdStr) + const peer = await this.components.getPeerStore().get(peerId) + + if (!peer.protocols.includes(this.identifyPushProtocolStr)) { + continue + } + + connections.push(...conns) + } + + await this.push(connections) + } + + /** + * Requests the `Identify` message from peer associated with the given `connection`. + * If the identified peer does not match the `PeerId` associated with the connection, + * an error will be thrown. + */ + async identify (connection: Connection): Promise { + const { stream } = await connection.newStream([this.identifyProtocolStr]) + const [data] = await pipe( + [], + stream, + lp.decode(), + (source) => take(source, 1), + async (source) => await all(source) + ) + + if (data == null) { + throw errCode(new Error('No data could be retrieved'), codes.ERR_CONNECTION_ENDED) + } + + let message + try { + message = Message.Identify.decode(data) + } catch (err: any) { + throw errCode(err, codes.ERR_INVALID_MESSAGE) + } + + const { + publicKey, + listenAddrs, + protocols, + observedAddr, + signedPeerRecord, + agentVersion, + protocolVersion + } = message + + if (publicKey == null) { + throw errCode(new Error('public key was missing from identify message'), codes.ERR_MISSING_PUBLIC_KEY) + } + + const id = await peerIdFromKeys(publicKey) + + if (!connection.remotePeer.equals(id)) { + throw errCode(new Error('identified peer does not match the expected peer'), codes.ERR_INVALID_PEER) + } + + if (this.components.getPeerId().equals(id)) { + throw errCode(new Error('identified peer is our own peer id?'), codes.ERR_INVALID_PEER) + } + + // Get the observedAddr if there is one + const cleanObservedAddr = IdentifyService.getCleanMultiaddr(observedAddr) + + if (signedPeerRecord != null) { + log('received signed peer record from %p', id) + + try { + const envelope = await RecordEnvelope.openAndCertify(signedPeerRecord, PeerRecord.DOMAIN) + + if (!envelope.peerId.equals(id)) { + throw errCode(new Error('identified peer does not match the expected peer'), codes.ERR_INVALID_PEER) + } + + if (await this.components.getPeerStore().addressBook.consumePeerRecord(envelope)) { + await this.components.getPeerStore().protoBook.set(id, protocols) + + if (agentVersion != null) { + await this.components.getPeerStore().metadataBook.setValue(id, 'AgentVersion', uint8ArrayFromString(agentVersion)) + } + + if (protocolVersion != null) { + await this.components.getPeerStore().metadataBook.setValue(id, 'ProtocolVersion', uint8ArrayFromString(protocolVersion)) + } + + log('identify completed for peer %p and protocols %o', id, protocols) + + return + } + } catch (err: any) { + log('received invalid envelope, discard it and fallback to listenAddrs is available', err) + } + } else { + log('no signed peer record received from %p', id) + } + + log('falling back to legacy addresses from %p', id) + + // LEGACY: Update peers data in PeerStore + try { + await this.components.getPeerStore().addressBook.set(id, listenAddrs.map((addr) => new Multiaddr(addr))) + } catch (err: any) { + log.error('received invalid addrs', err) + } + + await this.components.getPeerStore().protoBook.set(id, protocols) + + if (agentVersion != null) { + await this.components.getPeerStore().metadataBook.setValue(id, 'AgentVersion', uint8ArrayFromString(agentVersion)) + } + + if (protocolVersion != null) { + await this.components.getPeerStore().metadataBook.setValue(id, 'ProtocolVersion', uint8ArrayFromString(protocolVersion)) + } + + log('identify completed for peer %p and protocols %o', id, protocols) + + // TODO: Add and score our observed addr + log('received observed address of %s', cleanObservedAddr?.toString()) + // this.components.getAddressManager().addObservedAddr(observedAddr) + } + + /** + * A handler to register with Libp2p to process identify messages + */ + handleMessage (data: IncomingStreamData) { + const { protocol } = data + + switch (protocol) { + case this.identifyProtocolStr: + return this._handleIdentify(data) + case this.identifyPushProtocolStr: + return this._handlePush(data) + default: + log.error('cannot handle unknown protocol %s', protocol) + } + } + + /** + * Sends the `Identify` response with the Signed Peer Record + * to the requesting peer over the given `connection` + */ + async _handleIdentify (data: IncomingStreamData) { + const { connection, stream } = data + try { + const publicKey = this.components.getPeerId().publicKey ?? new Uint8Array(0) + const peerData = await this.components.getPeerStore().get(this.components.getPeerId()) + const multiaddrs = this.components.getAddressManager().getAddresses().map(ma => ma.decapsulateCode(protocols('p2p').code)) + let signedPeerRecord = peerData.peerRecordEnvelope + + if (multiaddrs.length > 0 && signedPeerRecord == null) { + const peerRecord = new PeerRecord({ + peerId: this.components.getPeerId(), + multiaddrs + }) + + const envelope = await RecordEnvelope.seal(peerRecord, this.components.getPeerId()) + await this.components.getPeerStore().addressBook.consumePeerRecord(envelope) + signedPeerRecord = envelope.marshal() + } + + const message = Message.Identify.encode({ + protocolVersion: this.host.protocolVersion, + agentVersion: this.host.agentVersion, + publicKey, + listenAddrs: multiaddrs.map(addr => addr.bytes), + signedPeerRecord, + observedAddr: connection.remoteAddr.bytes, + protocols: peerData.protocols + }).finish() + + await pipe( + [message], + lp.encode(), + stream, + drain + ) + } catch (err: any) { + log.error('could not respond to identify request', err) + } + } + + /** + * Reads the Identify Push message from the given `connection` + */ + async _handlePush (data: IncomingStreamData) { + const { connection, stream } = data + + let message + try { + const data = await pipe( + [], + stream, + lp.decode(), + async (source) => await first(source) + ) + + if (data != null) { + message = Message.Identify.decode(data) + } + } catch (err: any) { + return log.error('received invalid message', err) + } + + if (message == null) { + return log.error('received invalid message') + } + + const id = connection.remotePeer + + if (this.components.getPeerId().equals(id)) { + log('received push from ourselves?') + return + } + + log('received push from %p', id) + + if (message.signedPeerRecord != null) { + log('received signedPeerRecord in push') + + try { + const envelope = await RecordEnvelope.openAndCertify(message.signedPeerRecord, PeerRecord.DOMAIN) + + if (await this.components.getPeerStore().addressBook.consumePeerRecord(envelope)) { + log('consumed signedPeerRecord sent in push') + + await this.components.getPeerStore().protoBook.set(id, message.protocols) + return + } else { + log('failed to consume signedPeerRecord sent in push') + } + } catch (err: any) { + log('received invalid envelope, discard it and fallback to listenAddrs is available', err) + } + } else { + log('did not receive signedPeerRecord in push') + } + + // LEGACY: Update peers data in PeerStore + try { + await this.components.getPeerStore().addressBook.set(id, + message.listenAddrs.map((addr) => new Multiaddr(addr))) + } catch (err: any) { + log.error('received invalid addrs', err) + } + + // Update the protocols + try { + await this.components.getPeerStore().protoBook.set(id, message.protocols) + } catch (err: any) { + log.error('received invalid protocols', err) + } + + log('handled push from %p', id) + } + + /** + * Takes the `addr` and converts it to a Multiaddr if possible + */ + static getCleanMultiaddr (addr: Uint8Array | string | null | undefined) { + if (addr != null && addr.length > 0) { + try { + return new Multiaddr(addr) + } catch { + + } + } + } +} + +/** + * The protocols the IdentifyService supports + */ +export const multicodecs = { + IDENTIFY: MULTICODEC_IDENTIFY, + IDENTIFY_PUSH: MULTICODEC_IDENTIFY_PUSH +} + +export { Message } diff --git a/src/identify/message.d.ts b/src/identify/pb/message.d.ts similarity index 80% rename from src/identify/message.d.ts rename to src/identify/pb/message.d.ts index ba49c586aa..561dbc5569 100644 --- a/src/identify/message.d.ts +++ b/src/identify/pb/message.d.ts @@ -34,25 +34,40 @@ export class Identify implements IIdentify { constructor(p?: IIdentify); /** Identify protocolVersion. */ - public protocolVersion: string; + public protocolVersion?: (string|null); /** Identify agentVersion. */ - public agentVersion: string; + public agentVersion?: (string|null); /** Identify publicKey. */ - public publicKey: Uint8Array; + public publicKey?: (Uint8Array|null); /** Identify listenAddrs. */ public listenAddrs: Uint8Array[]; /** Identify observedAddr. */ - public observedAddr: Uint8Array; + public observedAddr?: (Uint8Array|null); /** Identify protocols. */ public protocols: string[]; /** Identify signedPeerRecord. */ - public signedPeerRecord: Uint8Array; + public signedPeerRecord?: (Uint8Array|null); + + /** Identify _protocolVersion. */ + public _protocolVersion?: "protocolVersion"; + + /** Identify _agentVersion. */ + public _agentVersion?: "agentVersion"; + + /** Identify _publicKey. */ + public _publicKey?: "publicKey"; + + /** Identify _observedAddr. */ + public _observedAddr?: "observedAddr"; + + /** Identify _signedPeerRecord. */ + public _signedPeerRecord?: "signedPeerRecord"; /** * Encodes the specified Identify message. Does not implicitly {@link Identify.verify|verify} messages. diff --git a/src/identify/message.js b/src/identify/pb/message.js similarity index 77% rename from src/identify/message.js rename to src/identify/pb/message.js index f4f04febdb..80764cad56 100644 --- a/src/identify/message.js +++ b/src/identify/pb/message.js @@ -1,15 +1,13 @@ /*eslint-disable*/ -"use strict"; - -var $protobuf = require("protobufjs/minimal"); +import $protobuf from "protobufjs/minimal.js"; // Common aliases -var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; +const $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; // Exported root namespace -var $root = $protobuf.roots["libp2p-identify"] || ($protobuf.roots["libp2p-identify"] = {}); +const $root = $protobuf.roots["libp2p-identify"] || ($protobuf.roots["libp2p-identify"] = {}); -$root.Identify = (function() { +export const Identify = $root.Identify = (() => { /** * Properties of an Identify. @@ -43,27 +41,27 @@ $root.Identify = (function() { /** * Identify protocolVersion. - * @member {string} protocolVersion + * @member {string|null|undefined} protocolVersion * @memberof Identify * @instance */ - Identify.prototype.protocolVersion = ""; + Identify.prototype.protocolVersion = null; /** * Identify agentVersion. - * @member {string} agentVersion + * @member {string|null|undefined} agentVersion * @memberof Identify * @instance */ - Identify.prototype.agentVersion = ""; + Identify.prototype.agentVersion = null; /** * Identify publicKey. - * @member {Uint8Array} publicKey + * @member {Uint8Array|null|undefined} publicKey * @memberof Identify * @instance */ - Identify.prototype.publicKey = $util.newBuffer([]); + Identify.prototype.publicKey = null; /** * Identify listenAddrs. @@ -75,11 +73,11 @@ $root.Identify = (function() { /** * Identify observedAddr. - * @member {Uint8Array} observedAddr + * @member {Uint8Array|null|undefined} observedAddr * @memberof Identify * @instance */ - Identify.prototype.observedAddr = $util.newBuffer([]); + Identify.prototype.observedAddr = null; /** * Identify protocols. @@ -91,11 +89,69 @@ $root.Identify = (function() { /** * Identify signedPeerRecord. - * @member {Uint8Array} signedPeerRecord + * @member {Uint8Array|null|undefined} signedPeerRecord + * @memberof Identify + * @instance + */ + Identify.prototype.signedPeerRecord = null; + + // OneOf field names bound to virtual getters and setters + let $oneOfFields; + + /** + * Identify _protocolVersion. + * @member {"protocolVersion"|undefined} _protocolVersion + * @memberof Identify + * @instance + */ + Object.defineProperty(Identify.prototype, "_protocolVersion", { + get: $util.oneOfGetter($oneOfFields = ["protocolVersion"]), + set: $util.oneOfSetter($oneOfFields) + }); + + /** + * Identify _agentVersion. + * @member {"agentVersion"|undefined} _agentVersion * @memberof Identify * @instance */ - Identify.prototype.signedPeerRecord = $util.newBuffer([]); + Object.defineProperty(Identify.prototype, "_agentVersion", { + get: $util.oneOfGetter($oneOfFields = ["agentVersion"]), + set: $util.oneOfSetter($oneOfFields) + }); + + /** + * Identify _publicKey. + * @member {"publicKey"|undefined} _publicKey + * @memberof Identify + * @instance + */ + Object.defineProperty(Identify.prototype, "_publicKey", { + get: $util.oneOfGetter($oneOfFields = ["publicKey"]), + set: $util.oneOfSetter($oneOfFields) + }); + + /** + * Identify _observedAddr. + * @member {"observedAddr"|undefined} _observedAddr + * @memberof Identify + * @instance + */ + Object.defineProperty(Identify.prototype, "_observedAddr", { + get: $util.oneOfGetter($oneOfFields = ["observedAddr"]), + set: $util.oneOfSetter($oneOfFields) + }); + + /** + * Identify _signedPeerRecord. + * @member {"signedPeerRecord"|undefined} _signedPeerRecord + * @memberof Identify + * @instance + */ + Object.defineProperty(Identify.prototype, "_signedPeerRecord", { + get: $util.oneOfGetter($oneOfFields = ["signedPeerRecord"]), + set: $util.oneOfSetter($oneOfFields) + }); /** * Encodes the specified Identify message. Does not implicitly {@link Identify.verify|verify} messages. @@ -256,33 +312,10 @@ $root.Identify = (function() { d.listenAddrs = []; d.protocols = []; } - if (o.defaults) { - if (o.bytes === String) - d.publicKey = ""; - else { - d.publicKey = []; - if (o.bytes !== Array) - d.publicKey = $util.newBuffer(d.publicKey); - } - if (o.bytes === String) - d.observedAddr = ""; - else { - d.observedAddr = []; - if (o.bytes !== Array) - d.observedAddr = $util.newBuffer(d.observedAddr); - } - d.protocolVersion = ""; - d.agentVersion = ""; - if (o.bytes === String) - d.signedPeerRecord = ""; - else { - d.signedPeerRecord = []; - if (o.bytes !== Array) - d.signedPeerRecord = $util.newBuffer(d.signedPeerRecord); - } - } if (m.publicKey != null && m.hasOwnProperty("publicKey")) { d.publicKey = o.bytes === String ? $util.base64.encode(m.publicKey, 0, m.publicKey.length) : o.bytes === Array ? Array.prototype.slice.call(m.publicKey) : m.publicKey; + if (o.oneofs) + d._publicKey = "publicKey"; } if (m.listenAddrs && m.listenAddrs.length) { d.listenAddrs = []; @@ -298,15 +331,23 @@ $root.Identify = (function() { } if (m.observedAddr != null && m.hasOwnProperty("observedAddr")) { d.observedAddr = o.bytes === String ? $util.base64.encode(m.observedAddr, 0, m.observedAddr.length) : o.bytes === Array ? Array.prototype.slice.call(m.observedAddr) : m.observedAddr; + if (o.oneofs) + d._observedAddr = "observedAddr"; } if (m.protocolVersion != null && m.hasOwnProperty("protocolVersion")) { d.protocolVersion = m.protocolVersion; + if (o.oneofs) + d._protocolVersion = "protocolVersion"; } if (m.agentVersion != null && m.hasOwnProperty("agentVersion")) { d.agentVersion = m.agentVersion; + if (o.oneofs) + d._agentVersion = "agentVersion"; } if (m.signedPeerRecord != null && m.hasOwnProperty("signedPeerRecord")) { d.signedPeerRecord = o.bytes === String ? $util.base64.encode(m.signedPeerRecord, 0, m.signedPeerRecord.length) : o.bytes === Array ? Array.prototype.slice.call(m.signedPeerRecord) : m.signedPeerRecord; + if (o.oneofs) + d._signedPeerRecord = "signedPeerRecord"; } return d; }; @@ -325,4 +366,4 @@ $root.Identify = (function() { return Identify; })(); -module.exports = $root; +export { $root as default }; diff --git a/src/identify/message.proto b/src/identify/pb/message.proto similarity index 100% rename from src/identify/message.proto rename to src/identify/pb/message.proto diff --git a/src/index.js b/src/index.js deleted file mode 100644 index f5e0f6c041..0000000000 --- a/src/index.js +++ /dev/null @@ -1,813 +0,0 @@ -'use strict' - -const debug = require('debug') -const log = Object.assign(debug('libp2p'), { - error: debug('libp2p:err') -}) -const { EventEmitter } = require('events') - -const errCode = require('err-code') -const PeerId = require('peer-id') -const { Multiaddr } = require('multiaddr') -const { MemoryDatastore } = require('datastore-core/memory') -const PeerRouting = require('./peer-routing') -const ContentRouting = require('./content-routing') -const getPeer = require('./get-peer') -const { validate: validateConfig } = require('./config') -const { codes, messages } = require('./errors') - -const AddressManager = require('./address-manager') -const ConnectionManager = require('./connection-manager') -const AutoDialler = require('./connection-manager/auto-dialler') -const Circuit = require('./circuit/transport') -const Relay = require('./circuit') -const Dialer = require('./dialer') -const Keychain = require('./keychain') -const Metrics = require('./metrics') -const TransportManager = require('./transport-manager') -const Upgrader = require('./upgrader') -const PeerStore = require('./peer-store') -const PubsubAdapter = require('./pubsub-adapter') -const Registrar = require('./registrar') -const IdentifyService = require('./identify') -const FetchService = require('./fetch') -const PingService = require('./ping') -const NatManager = require('./nat-manager') -const { updateSelfPeerRecord } = require('./record/utils') - -/** - * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection - * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream - * @typedef {import('libp2p-interfaces/src/transport/types').TransportFactory} TransportFactory - * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxerFactory} MuxerFactory - * @typedef {import('libp2p-interfaces/src/content-routing/types').ContentRouting} ContentRoutingModule - * @typedef {import('libp2p-interfaces/src/peer-discovery/types').PeerDiscoveryFactory} PeerDiscoveryFactory - * @typedef {import('libp2p-interfaces/src/peer-routing/types').PeerRouting} PeerRoutingModule - * @typedef {import('libp2p-interfaces/src/crypto/types').Crypto} Crypto - * @typedef {import('libp2p-interfaces/src/pubsub')} Pubsub - * @typedef {import('libp2p-interfaces/src/pubsub').PubsubOptions} PubsubOptions - * @typedef {import('interface-datastore').Datastore} Datastore - * @typedef {import('./pnet')} Protector - * @typedef {import('./types').ConnectionGater} ConnectionGater - * @typedef {Object} PersistentPeerStoreOptions - * @property {number} [threshold] - */ - -/** - * @typedef {Object} HandlerProps - * @property {Connection} connection - * @property {MuxedStream} stream - * @property {string} protocol - * - * @typedef {Object} DhtOptions - * @property {boolean} [enabled = false] - * @property {number} [kBucketSize = 20] - * @property {boolean} [clientMode] - * @property {import('libp2p-interfaces/src/types').DhtSelectors} [selectors] - * @property {import('libp2p-interfaces/src/types').DhtValidators} [validators] - * - * @typedef {Object} KeychainOptions - * @property {Datastore} [datastore] - * - * @typedef {Object} PeerStoreOptions - * @property {boolean} persistence - * - * @typedef {Object} PubsubLocalOptions - * @property {boolean} enabled - * - * @typedef {Object} MetricsOptions - * @property {boolean} enabled - * - * @typedef {Object} RelayOptions - * @property {boolean} [enabled = true] - * @property {import('./circuit').RelayAdvertiseOptions} [advertise] - * @property {import('./circuit').HopOptions} [hop] - * @property {import('./circuit').AutoRelayOptions} [autoRelay] - * - * @typedef {Object} Libp2pConfig - * @property {DhtOptions} [dht] dht module options - * @property {import('./nat-manager').NatManagerOptions} [nat] - * @property {Record} [peerDiscovery] - * @property {PubsubLocalOptions & PubsubOptions} [pubsub] pubsub module options - * @property {RelayOptions} [relay] - * @property {Record} [transport] transport options indexed by transport key - * - * @typedef {Object} Libp2pModules - * @property {TransportFactory[]} transport - * @property {MuxerFactory[]} streamMuxer - * @property {Crypto[]} connEncryption - * @property {PeerDiscoveryFactory[]} [peerDiscovery] - * @property {PeerRoutingModule[]} [peerRouting] - * @property {ContentRoutingModule[]} [contentRouting] - * @property {Object} [dht] - * @property {{new(...args: any[]): Pubsub}} [pubsub] - * @property {Protector} [connProtector] - * - * @typedef {Object} Libp2pOptions - * @property {Libp2pModules} modules libp2p modules to use - * @property {import('./address-manager').AddressManagerOptions} [addresses] - * @property {import('./connection-manager').ConnectionManagerOptions} [connectionManager] - * @property {Partial} [connectionGater] - * @property {Datastore} [datastore] - * @property {import('./dialer').DialerOptions} [dialer] - * @property {import('./identify/index').HostProperties} [host] libp2p host - * @property {KeychainOptions & import('./keychain/index').KeychainOptions} [keychain] - * @property {MetricsOptions & import('./metrics').MetricsOptions} [metrics] - * @property {import('./peer-routing').PeerRoutingOptions} [peerRouting] - * @property {PeerStoreOptions} [peerStore] - * @property {import('./transport-manager').TransportManagerOptions} [transportManager] - * @property {Libp2pConfig} [config] - * - * @typedef {Object} constructorOptions - * @property {PeerId} peerId - * - * @typedef {Object} CreateOptions - * @property {PeerId} [peerId] - * - * @extends {EventEmitter} - * @fires Libp2p#error Emitted when an error occurs - * @fires Libp2p#peer:discovery Emitted when a peer is discovered - */ -class Libp2p extends EventEmitter { - /** - * Like `new Libp2p(options)` except it will create a `PeerId` - * instance if one is not provided in options. - * - * @param {Libp2pOptions & CreateOptions} options - Libp2p configuration options - * @returns {Promise} - */ - static async create (options) { - if (options.peerId) { - // @ts-ignore 'Libp2pOptions & CreateOptions' is not assignable to 'Libp2pOptions & constructorOptions' - return new Libp2p(options) - } - - const peerId = await PeerId.create() - - options.peerId = peerId - // @ts-ignore 'Libp2pOptions & CreateOptions' is not assignable to 'Libp2pOptions & constructorOptions' - return new Libp2p(options) - } - - /** - * Libp2p node. - * - * @class - * @param {Libp2pOptions & constructorOptions} _options - */ - constructor (_options) { - super() - // validateConfig will ensure the config is correct, - // and add default values where appropriate - this._options = validateConfig(_options) - - /** @type {PeerId} */ - this.peerId = this._options.peerId - this.datastore = this._options.datastore - - // Create Metrics - if (this._options.metrics.enabled) { - const metrics = new Metrics({ - ...this._options.metrics - }) - - this.metrics = metrics - } - - /** @type {ConnectionGater} */ - this.connectionGater = { - denyDialPeer: async () => Promise.resolve(false), - denyDialMultiaddr: async () => Promise.resolve(false), - denyInboundConnection: async () => Promise.resolve(false), - denyOutboundConnection: async () => Promise.resolve(false), - denyInboundEncryptedConnection: async () => Promise.resolve(false), - denyOutboundEncryptedConnection: async () => Promise.resolve(false), - denyInboundUpgradedConnection: async () => Promise.resolve(false), - denyOutboundUpgradedConnection: async () => Promise.resolve(false), - filterMultiaddrForPeer: async () => Promise.resolve(true), - ...this._options.connectionGater - } - - /** @type {import('./peer-store/types').PeerStore} */ - this.peerStore = new PeerStore({ - peerId: this.peerId, - datastore: (this.datastore && this._options.peerStore.persistence) ? this.datastore : new MemoryDatastore(), - addressFilter: this.connectionGater.filterMultiaddrForPeer - }) - - // Addresses {listen, announce, noAnnounce} - this.addresses = this._options.addresses - this.addressManager = new AddressManager(this.peerId, this._options.addresses) - - // when addresses change, update our peer record - this.addressManager.on('change:addresses', () => { - updateSelfPeerRecord(this).catch(err => { - log.error('Error updating self peer record', err) - }) - }) - - this._modules = this._options.modules - this._config = this._options.config - this._transport = [] // Transport instances/references - this._discovery = new Map() // Discovery service instances/references - - // Create the Connection Manager - this.connectionManager = new ConnectionManager(this, { - ...this._options.connectionManager - }) - this._autodialler = new AutoDialler(this, { - enabled: this._config.peerDiscovery.autoDial, - minConnections: this._options.connectionManager.minConnections, - autoDialInterval: this._options.connectionManager.autoDialInterval - }) - - // Create keychain - if (this._options.keychain && this._options.keychain.datastore) { - log('creating keychain') - - const keychainOpts = Keychain.generateOptions() - - this.keychain = new Keychain(this._options.keychain.datastore, { - ...keychainOpts, - ...this._options.keychain - }) - - log('keychain constructed') - } - - // Setup the Upgrader - this.upgrader = new Upgrader({ - connectionGater: this.connectionGater, - localPeer: this.peerId, - metrics: this.metrics, - onConnection: (connection) => this.connectionManager.onConnect(connection), - onConnectionEnd: (connection) => this.connectionManager.onDisconnect(connection) - }) - - // Setup the transport manager - this.transportManager = new TransportManager({ - libp2p: this, - upgrader: this.upgrader, - faultTolerance: this._options.transportManager.faultTolerance - }) - - // Create the Nat Manager - this.natManager = new NatManager({ - peerId: this.peerId, - addressManager: this.addressManager, - transportManager: this.transportManager, - // @ts-ignore Nat typedef is not understood as Object - ...this._options.config.nat - }) - - // Create the Registrar - this.registrar = new Registrar({ - peerStore: this.peerStore, - connectionManager: this.connectionManager - }) - - this.handle = this.handle.bind(this) - this.registrar.handle = this.handle - - // Attach crypto channels - if (!this._modules.connEncryption || !this._modules.connEncryption.length) { - throw errCode(new Error(messages.CONN_ENCRYPTION_REQUIRED), codes.CONN_ENCRYPTION_REQUIRED) - } - const cryptos = this._modules.connEncryption - cryptos.forEach((crypto) => { - this.upgrader.cryptos.set(crypto.protocol, crypto) - }) - - this.dialer = new Dialer({ - transportManager: this.transportManager, - connectionGater: this.connectionGater, - peerStore: this.peerStore, - metrics: this.metrics, - ...this._options.dialer - }) - - this._modules.transport.forEach((Transport) => { - const key = Transport.prototype[Symbol.toStringTag] - const transportOptions = this._config.transport[key] - this.transportManager.add(key, Transport, transportOptions) - }) - - if (this._config.relay.enabled) { - // @ts-ignore Circuit prototype - this.transportManager.add(Circuit.prototype[Symbol.toStringTag], Circuit) - this.relay = new Relay(this) - } - - // Attach stream multiplexers - if (this._modules.streamMuxer) { - const muxers = this._modules.streamMuxer - muxers.forEach((muxer) => { - this.upgrader.muxers.set(muxer.multicodec, muxer) - }) - - // Add the identify service since we can multiplex - this.identifyService = new IdentifyService({ libp2p: this }) - } - - // Attach private network protector - if (this._modules.connProtector) { - this.upgrader.protector = this._modules.connProtector - } else if (globalThis.process !== undefined && globalThis.process.env && globalThis.process.env.LIBP2P_FORCE_PNET) { // eslint-disable-line no-undef - throw new Error('Private network is enforced, but no protector was provided') - } - - // dht provided components (peerRouting, contentRouting, dht) - if (this._modules.dht) { - const DHT = this._modules.dht - // @ts-ignore TODO: types need fixing - DHT is an `object` which has no `create` method - this._dht = DHT.create({ - libp2p: this, - ...this._config.dht - }) - } - - // Create pubsub if provided - if (this._modules.pubsub) { - const Pubsub = this._modules.pubsub - // using pubsub adapter with *DEPRECATED* handlers functionality - /** @type {Pubsub} */ - this.pubsub = PubsubAdapter(Pubsub, this, this._config.pubsub) - } - - // Attach remaining APIs - // peer and content routing will automatically get modules from _modules and _dht - this.peerRouting = new PeerRouting(this) - this.contentRouting = new ContentRouting(this) - - this._onDiscoveryPeer = this._onDiscoveryPeer.bind(this) - - this.fetchService = new FetchService(this) - this.pingService = new PingService(this) - } - - /** - * 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 {boolean} - */ - emit (eventName, ...args) { - // TODO: do we still need this? - // @ts-ignore _events does not exist in libp2p - if (eventName === 'error' && !this._events.error) { - log.error(args) - return false - } else { - return super.emit(eventName, ...args) - } - } - - /** - * Starts the libp2p node and all its subsystems - * - * @returns {Promise} - */ - async start () { - log('libp2p is starting') - - if (this.identifyService) { - await this.handle(Object.values(IdentifyService.getProtocolStr(this)), this.identifyService.handleMessage) - } - - if (this.fetchService) { - await this.handle(FetchService.PROTOCOL, this.fetchService.handleMessage) - } - - if (this.pingService) { - await this.handle(PingService.getProtocolStr(this), this.pingService.handleMessage) - } - - try { - await this._onStarting() - await this._onDidStart() - log('libp2p has started') - } catch (/** @type {any} */ err) { - this.emit('error', err) - log.error('An error occurred starting libp2p', err) - await this.stop() - throw err - } - } - - /** - * Stop the libp2p node by closing its listeners and open connections - * - * @async - * @returns {Promise} - */ - async stop () { - log('libp2p is stopping') - - try { - this._isStarted = false - - if (this.identifyService) { - await this.identifyService.stop() - } - - this.relay && this.relay.stop() - this.peerRouting.stop() - await this._autodialler.stop() - await (this._dht && this._dht.stop()) - - for (const service of this._discovery.values()) { - service.removeListener('peer', this._onDiscoveryPeer) - } - - await Promise.all(Array.from(this._discovery.values(), s => s.stop())) - - this._discovery = new Map() - - await this.connectionManager.stop() - - await Promise.all([ - this.pubsub && this.pubsub.stop(), - this.metrics && this.metrics.stop() - ]) - - await this.natManager.stop() - await this.transportManager.close() - - await this.unhandle(FetchService.PROTOCOL) - await this.unhandle(PingService.getProtocolStr(this)) - - this.dialer.destroy() - } catch (/** @type {any} */ err) { - if (err) { - log.error(err) - this.emit('error', err) - } - } - log('libp2p has stopped') - } - - /** - * Load keychain keys from the datastore. - * Imports the private key as 'self', if needed. - * - * @async - * @returns {Promise} - */ - async loadKeychain () { - if (!this.keychain) { - return - } - - try { - await this.keychain.findKeyByName('self') - } catch (/** @type {any} */ err) { - await this.keychain.importPeer('self', this.peerId) - } - } - - isStarted () { - return this._isStarted - } - - /** - * Gets a Map of the current connections. The keys are the stringified - * `PeerId` of the peer. The value is an array of Connections to that peer. - * - * @returns {Map} - */ - get connections () { - return this.connectionManager.connections - } - - /** - * 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 {object} [options] - * @param {AbortSignal} [options.signal] - * @returns {Promise} - */ - dial (peer, options) { - return this._dial(peer, options) - } - - /** - * Dials to the provided peer and tries to handshake with the given protocols in order. - * If successful, the known metadata of the peer will be added to the nodes `peerStore`, - * and the `MuxedStream` will be returned together with the successful negotiated protocol. - * - * @async - * @param {PeerId|Multiaddr|string} peer - The peer to dial - * @param {string[]|string} protocols - * @param {object} [options] - * @param {AbortSignal} [options.signal] - */ - async dialProtocol (peer, protocols, options) { - if (!protocols || !protocols.length) { - throw errCode(new Error('no protocols were provided to open a stream'), codes.ERR_INVALID_PROTOCOLS_FOR_STREAM) - } - - const connection = await this._dial(peer, options) - return connection.newStream(protocols) - } - - /** - * @async - * @param {PeerId|Multiaddr|string} peer - The peer to dial - * @param {object} [options] - * @returns {Promise} - */ - async _dial (peer, options) { - const { id, multiaddrs } = getPeer(peer) - - if (id.equals(this.peerId)) { - throw errCode(new Error('Cannot dial self'), codes.ERR_DIALED_SELF) - } - - let connection = this.connectionManager.get(id) - - if (!connection) { - connection = await this.dialer.connectToPeer(peer, options) - } else if (multiaddrs) { - await this.peerStore.addressBook.add(id, multiaddrs) - } - - return connection - } - - /** - * Get a deduplicated list of peer advertising multiaddrs by concatenating - * the listen addresses used by transports with any configured - * announce addresses as well as observed addresses reported by peers. - * - * If Announce addrs are specified, configured listen addresses will be - * ignored though observed addresses will still be included. - * - * @returns {Multiaddr[]} - */ - get multiaddrs () { - let addrs = this.addressManager.getAnnounceAddrs().map(ma => ma.toString()) - - if (!addrs.length) { - // no configured announce addrs, add configured listen addresses - addrs = this.transportManager.getAddrs().map(ma => ma.toString()) - } - - addrs = addrs.concat(this.addressManager.getObservedAddrs().map(ma => ma.toString())) - - const announceFilter = this._options.addresses.announceFilter - - // dedupe multiaddrs - const addrSet = new Set(addrs) - - // Create advertising list - return announceFilter(Array.from(addrSet).map(str => new Multiaddr(str))) - } - - /** - * Disconnects all connections to the given `peer` - * - * @param {PeerId|Multiaddr|string} peer - the peer to close connections to - * @returns {Promise} - */ - async hangUp (peer) { - const { id } = getPeer(peer) - - const connections = this.connectionManager.connections.get(id.toB58String()) - - if (!connections) { - return - } - - await Promise.all( - connections.map(connection => { - return connection.close() - }) - ) - } - - /** - * Sends a request to fetch the value associated with the given key from the given peer. - * - * @param {PeerId|Multiaddr} peer - * @param {string} key - * @returns {Promise} - */ - fetch (peer, key) { - return this.fetchService.fetch(peer, key) - } - - /** - * Pings the given peer in order to obtain the operation latency. - * - * @param {PeerId|Multiaddr|string} peer - The peer to ping - * @returns {Promise} - */ - ping (peer) { - const { id, multiaddrs } = getPeer(peer) - - // If received multiaddr, ping it - if (multiaddrs) { - return this.pingService.ping(multiaddrs[0]) - } - - return this.pingService.ping(id) - } - - /** - * Registers the `handler` for each protocol - * - * @param {string[]|string} protocols - * @param {(props: HandlerProps) => void} handler - */ - async handle (protocols, handler) { - protocols = Array.isArray(protocols) ? protocols : [protocols] - protocols.forEach(protocol => { - this.upgrader.protocols.set(protocol, handler) - }) - - // Add new protocols to self protocols in the Protobook - await 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 - */ - async unhandle (protocols) { - protocols = Array.isArray(protocols) ? protocols : [protocols] - protocols.forEach(protocol => { - this.upgrader.protocols.delete(protocol) - }) - - // Remove protocols from self protocols in the Protobook - await this.peerStore.protoBook.remove(this.peerId, protocols) - } - - async _onStarting () { - // Listen on the provided transports for the provided addresses - const addrs = this.addressManager.getListenAddrs() - await this.transportManager.listen(addrs) - - // Manage your NATs - this.natManager.start() - - if (this._config.pubsub.enabled) { - this.pubsub && await this.pubsub.start() - } - - // DHT subsystem - if (this._config.dht.enabled) { - this._dht && await this._dht.start() - - // TODO: this should be modified once random-walk is used as - // the other discovery modules - this._dht.on('peer', this._onDiscoveryPeer) - } - - // Start metrics if present - this.metrics && this.metrics.start() - - if (this.identifyService) { - await this.identifyService.start() - } - } - - /** - * Called when libp2p has started and before it returns - * - * @private - */ - async _onDidStart () { - this._isStarted = true - - this.peerStore.on('peer', peerId => { - this.emit('peer:discovery', peerId) - this._maybeConnect(peerId).catch(err => { - log.error(err) - }) - }) - - // 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 await (const peer of this.peerStore.getPeers()) { - this.emit('peer:discovery', peer.id) - } - - this.connectionManager.start() - await this._autodialler.start() - - // Peer discovery - await this._setupPeerDiscovery() - - // Relay - this.relay && this.relay.start() - - this.peerRouting.start() - } - - /** - * Called whenever peer discovery services emit `peer` events. - * Known peers may be emitted. - * - * @private - * @param {{ id: PeerId, multiaddrs: Multiaddr[], protocols: string[] }} peer - */ - _onDiscoveryPeer (peer) { - if (peer.id.toB58String() === this.peerId.toB58String()) { - log.error(new Error(codes.ERR_DISCOVERED_SELF)) - return - } - - peer.multiaddrs && this.peerStore.addressBook.add(peer.id, peer.multiaddrs).catch(err => log.error(err)) - peer.protocols && this.peerStore.protoBook.set(peer.id, peer.protocols).catch(err => log.error(err)) - } - - /** - * Will dial to the given `peerId` if the current number of - * connected peers is less than the configured `ConnectionManager` - * 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 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) - } catch (/** @type {any} */ err) { - log.error(`could not connect to discovered peer ${peerId.toB58String()} with ${err}`) - } - } - } - } - - /** - * Initializes and starts peer discovery services - * - * @async - * @private - */ - async _setupPeerDiscovery () { - /** - * @param {PeerDiscoveryFactory} DiscoveryService - */ - const setupService = (DiscoveryService) => { - let config = { - enabled: true // on by default - } - - if (DiscoveryService.tag && - this._config.peerDiscovery && - this._config.peerDiscovery[DiscoveryService.tag]) { - // @ts-ignore PeerDiscovery not understood as an Object for spread - config = { ...config, ...this._config.peerDiscovery[DiscoveryService.tag] } - } - - if (config.enabled && - !this._discovery.has(DiscoveryService.tag)) { // not already added - let discoveryService - - if (typeof DiscoveryService === 'function') { - // @ts-ignore DiscoveryService has no constructor type inferred - discoveryService = new DiscoveryService(Object.assign({}, config, { - peerId: this.peerId, - libp2p: this - })) - } else { - discoveryService = DiscoveryService - } - - discoveryService.on('peer', this._onDiscoveryPeer) - this._discovery.set(DiscoveryService.tag, discoveryService) - } - } - - // Discovery modules - for (const DiscoveryService of this._modules.peerDiscovery || []) { - setupService(DiscoveryService) - } - - // Transport modules with discovery - for (const Transport of this.transportManager.getTransports()) { - // @ts-ignore Transport interface does not include discovery - if (Transport.discovery) { - // @ts-ignore Transport interface does not include discovery - setupService(Transport.discovery) - } - } - - await Promise.all(Array.from(this._discovery.values(), d => d.start())) - } -} - -module.exports = Libp2p diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000000..0f063df94a --- /dev/null +++ b/src/index.ts @@ -0,0 +1,234 @@ +import { createLibp2pNode } from './libp2p.js' +import type { AbortOptions, EventEmitter, RecursivePartial, Startable } from '@libp2p/interfaces' +import type { Multiaddr } from '@multiformats/multiaddr' +import type { FAULT_TOLERANCE } from './transport-manager.js' +import type { HostProperties } from './identify/index.js' +import type { DualDHT } from '@libp2p/interfaces/dht' +import type { Datastore } from 'interface-datastore' +import type { PeerStore, PeerStoreInit } from '@libp2p/interfaces/peer-store' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { AutoRelayConfig, RelayAdvertiseConfig } from './circuit/index.js' +import type { PeerDiscovery } from '@libp2p/interfaces/peer-discovery' +import type { Connection, ConnectionGater, ConnectionProtector, ProtocolStream } from '@libp2p/interfaces/connection' +import type { Transport } from '@libp2p/interfaces/transport' +import type { StreamMuxerFactory } from '@libp2p/interfaces/stream-muxer' +import type { ConnectionEncrypter } from '@libp2p/interfaces/connection-encrypter' +import type { PeerRouting } from '@libp2p/interfaces/peer-routing' +import type { ContentRouting } from '@libp2p/interfaces/content-routing' +import type { PubSub } from '@libp2p/interfaces/pubsub' +import type { ConnectionManager, StreamHandler } from '@libp2p/interfaces/registrar' +import type { MetricsInit } from '@libp2p/interfaces/metrics' +import type { PeerInfo } from '@libp2p/interfaces/peer-info' +import type { DialerInit } from '@libp2p/interfaces/dialer' +import type { KeyChain } from './keychain/index.js' + +export interface PersistentPeerStoreOptions { + threshold?: number +} + +export interface DEKConfig { + keyLength: number + iterationCount: number + salt: string + hash: string +} + +export interface KeychainConfig { + pass?: string + dek?: DEKConfig +} + +export interface MetricsConfig { + enabled?: boolean +} + +export interface HopConfig { + enabled?: boolean + active?: boolean +} + +export interface RelayConfig { + enabled: boolean + advertise: RelayAdvertiseConfig + hop: HopConfig + autoRelay: AutoRelayConfig +} + +export interface NatManagerConfig { + enabled: boolean + externalAddress?: string + localAddress?: string + description?: string + ttl?: number + keepAlive: boolean + gateway?: string +} + +export interface AddressesConfig { + listen: string[] + announce: string[] + noAnnounce: string[] + announceFilter: (multiaddrs: Multiaddr[]) => Multiaddr[] +} + +export interface ConnectionManagerConfig { + /** + * If true, try to connect to all discovered peers up to the connection manager limit + */ + autoDial?: boolean + + /** + * The maximum number of connections to keep open + */ + maxConnections: number + + /** + * The minimum number of connections to keep open + */ + minConnections: number + + /** + * How long to wait between attempting to keep our number of concurrent connections + * above minConnections + */ + autoDialInterval: number +} + +export interface TransportManagerConfig { + faultTolerance?: FAULT_TOLERANCE +} + +export interface PeerStoreConfig { + persistence?: boolean + threshold?: number +} + +export interface PeerRoutingConfig { + refreshManager: RefreshManagerConfig +} + +export interface RefreshManagerConfig { + enabled?: boolean + interval: number + bootDelay: number +} + +export interface Libp2pInit { + peerId: PeerId + host: HostProperties + addresses: AddressesConfig + connectionManager: ConnectionManagerConfig + connectionGater: Partial + transportManager: TransportManagerConfig + datastore: Datastore + dialer: DialerInit + metrics: MetricsInit + peerStore: PeerStoreInit + peerRouting: PeerRoutingConfig + keychain: KeychainConfig + protocolPrefix: string + nat: NatManagerConfig + relay: RelayConfig + + transports: Transport[] + streamMuxers?: StreamMuxerFactory[] + connectionEncryption?: ConnectionEncrypter[] + peerDiscovery?: PeerDiscovery[] + peerRouters?: PeerRouting[] + contentRouters?: ContentRouting[] + dht?: DualDHT + pubsub?: PubSub + connectionProtector?: ConnectionProtector +} + +export interface Libp2pEvents { + 'peer:discovery': CustomEvent +} + +export interface Libp2p extends Startable, EventEmitter { + peerId: PeerId + peerStore: PeerStore + peerRouting: PeerRouting + contentRouting: ContentRouting + keychain: KeyChain + connectionManager: ConnectionManager + + pubsub?: PubSub + dht?: DualDHT + + /** + * Load keychain keys from the datastore. + * Imports the private key as 'self', if needed. + */ + loadKeychain: () => Promise + + /** + * Get a deduplicated list of peer advertising multiaddrs by concatenating + * the listen addresses used by transports with any configured + * announce addresses as well as observed addresses reported by peers. + * + * If Announce addrs are specified, configured listen addresses will be + * ignored though observed addresses will still be included. + */ + getMultiaddrs: () => Multiaddr[] + + /** + * Return a list of all connections this node has open, optionally filtering + * by a PeerId + */ + getConnections: (peerId?: PeerId) => Connection[] + + /** + * Return a list of all peers we currently have a connection open to + */ + getPeers: () => PeerId[] + + /** + * Dials to the provided peer. If successful, the known metadata of the + * peer will be added to the nodes `peerStore` + */ + dial: (peer: PeerId | Multiaddr, options?: AbortOptions) => Promise + + /** + * Dials to the provided peer and tries to handshake with the given protocols in order. + * If successful, the known metadata of the peer will be added to the nodes `peerStore`, + * and the `MuxedStream` will be returned together with the successful negotiated protocol. + */ + dialProtocol: (peer: PeerId | Multiaddr, protocols: string | string[], options?: AbortOptions) => Promise + + /** + * Disconnects all connections to the given `peer` + */ + hangUp: (peer: PeerId | Multiaddr | string) => Promise + + /** + * Registers the `handler` for each protocol + */ + handle: (protocol: string | string[], handler: StreamHandler) => Promise + + /** + * Removes the handler for each protocol. The protocol + * will no longer be supported on streams. + */ + unhandle: (protocols: string[] | string) => Promise + + /** + * Pings the given peer in order to obtain the operation latency + */ + ping: (peer: Multiaddr |PeerId) => Promise + + /** + * Sends a request to fetch the value associated with the given key from the given peer. + */ + fetch: (peer: PeerId | Multiaddr | string, key: string) => Promise +} + +export type Libp2pOptions = RecursivePartial + +/** + * Returns a new instance of the Libp2p interface, generating a new PeerId + * if one is not passed as part of the options. + */ +export async function createLibp2p (options: Libp2pOptions): Promise { + return await createLibp2pNode(options) +} diff --git a/src/insecure/index.ts b/src/insecure/index.ts new file mode 100644 index 0000000000..3c941beff2 --- /dev/null +++ b/src/insecure/index.ts @@ -0,0 +1,97 @@ +import { logger } from '@libp2p/logger' +import { handshake } from 'it-handshake' +import * as lp from 'it-length-prefixed' +import { UnexpectedPeerError, InvalidCryptoExchangeError } from '@libp2p/interfaces/connection-encrypter/errors' +import { Exchange, IExchange, KeyType } from './pb/proto.js' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import { peerIdFromBytes, peerIdFromKeys } from '@libp2p/peer-id' +import type { ConnectionEncrypter, SecuredConnection } from '@libp2p/interfaces/connection-encrypter' +import type { Duplex } from 'it-stream-types' + +const log = logger('libp2p:plaintext') +const PROTOCOL = '/plaintext/2.0.0' + +function lpEncodeExchange (exchange: IExchange) { + const pb = Exchange.encode(exchange).finish() + + return lp.encode.single(pb) +} + +/** + * Encrypt connection + */ +async function encrypt (localId: PeerId, conn: Duplex, remoteId?: PeerId): Promise { + const shake = handshake(conn) + + let type = KeyType.RSA + + if (localId.type === 'Ed25519') { + type = KeyType.Ed25519 + } else if (localId.type === 'secp256k1') { + type = KeyType.Secp256k1 + } + + // Encode the public key and write it to the remote peer + shake.write( + lpEncodeExchange({ + id: localId.toBytes(), + pubkey: { + Type: type, + Data: localId.publicKey + } + }).slice() + ) + + log('write pubkey exchange to peer %p', remoteId) + + // Get the Exchange message + // @ts-expect-error needs to be generator + const response = (await lp.decode.fromReader(shake.reader).next()).value + const id = Exchange.decode(response.slice()) + log('read pubkey exchange from peer %p', remoteId) + + let peerId + try { + if (id.pubkey.Data.length === 0) { + throw new Error('Public key data too short') + } + + if (id.id == null) { + throw new Error('Remote id missing') + } + + peerId = await peerIdFromKeys(id.pubkey.Data) + + if (!peerId.equals(peerIdFromBytes(id.id))) { + throw new Error('Public key did not match id') + } + } catch (err: any) { + log.error(err) + throw new InvalidCryptoExchangeError('Remote did not provide its public key') + } + + if (remoteId != null && !peerId.equals(remoteId)) { + throw new UnexpectedPeerError() + } + + log('plaintext key exchange completed successfully with peer %p', peerId) + + shake.rest() + return { + conn: shake.stream, + remotePeer: peerId, + remoteEarlyData: new Uint8Array() + } +} + +export class Plaintext implements ConnectionEncrypter { + public protocol: string = PROTOCOL + + async secureInbound (localId: PeerId, conn: Duplex, remoteId?: PeerId): Promise { + return await encrypt(localId, conn, remoteId) + } + + async secureOutbound (localId: PeerId, conn: Duplex, remoteId: PeerId): Promise { + return await encrypt(localId, conn, remoteId) + } +} diff --git a/src/insecure/proto.d.ts b/src/insecure/pb/proto.d.ts similarity index 96% rename from src/insecure/proto.d.ts rename to src/insecure/pb/proto.d.ts index a4fbac0610..191c866968 100644 --- a/src/insecure/proto.d.ts +++ b/src/insecure/pb/proto.d.ts @@ -19,11 +19,17 @@ export class Exchange implements IExchange { constructor(p?: IExchange); /** Exchange id. */ - public id: Uint8Array; + public id?: (Uint8Array|null); /** Exchange pubkey. */ public pubkey?: (IPublicKey|null); + /** Exchange _id. */ + public _id?: "id"; + + /** Exchange _pubkey. */ + public _pubkey?: "pubkey"; + /** * Encodes the specified Exchange message. Does not implicitly {@link Exchange.verify|verify} messages. * @param m Exchange message or plain object to encode diff --git a/src/insecure/proto.js b/src/insecure/pb/proto.js similarity index 88% rename from src/insecure/proto.js rename to src/insecure/pb/proto.js index ab43d4a9ee..54fa39cf41 100644 --- a/src/insecure/proto.js +++ b/src/insecure/pb/proto.js @@ -1,15 +1,13 @@ /*eslint-disable*/ -"use strict"; - -var $protobuf = require("protobufjs/minimal"); +import $protobuf from "protobufjs/minimal.js"; // Common aliases -var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; +const $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; // Exported root namespace -var $root = $protobuf.roots["libp2p-plaintext"] || ($protobuf.roots["libp2p-plaintext"] = {}); +const $root = $protobuf.roots["libp2p-plaintext"] || ($protobuf.roots["libp2p-plaintext"] = {}); -$root.Exchange = (function() { +export const Exchange = $root.Exchange = (() => { /** * Properties of an Exchange. @@ -36,11 +34,11 @@ $root.Exchange = (function() { /** * Exchange id. - * @member {Uint8Array} id + * @member {Uint8Array|null|undefined} id * @memberof Exchange * @instance */ - Exchange.prototype.id = $util.newBuffer([]); + Exchange.prototype.id = null; /** * Exchange pubkey. @@ -50,6 +48,31 @@ $root.Exchange = (function() { */ Exchange.prototype.pubkey = null; + // OneOf field names bound to virtual getters and setters + let $oneOfFields; + + /** + * Exchange _id. + * @member {"id"|undefined} _id + * @memberof Exchange + * @instance + */ + Object.defineProperty(Exchange.prototype, "_id", { + get: $util.oneOfGetter($oneOfFields = ["id"]), + set: $util.oneOfSetter($oneOfFields) + }); + + /** + * Exchange _pubkey. + * @member {"pubkey"|undefined} _pubkey + * @memberof Exchange + * @instance + */ + Object.defineProperty(Exchange.prototype, "_pubkey", { + get: $util.oneOfGetter($oneOfFields = ["pubkey"]), + set: $util.oneOfSetter($oneOfFields) + }); + /** * Encodes the specified Exchange message. Does not implicitly {@link Exchange.verify|verify} messages. * @function encode @@ -140,21 +163,15 @@ $root.Exchange = (function() { if (!o) o = {}; var d = {}; - if (o.defaults) { - if (o.bytes === String) - d.id = ""; - else { - d.id = []; - if (o.bytes !== Array) - d.id = $util.newBuffer(d.id); - } - d.pubkey = null; - } if (m.id != null && m.hasOwnProperty("id")) { d.id = o.bytes === String ? $util.base64.encode(m.id, 0, m.id.length) : o.bytes === Array ? Array.prototype.slice.call(m.id) : m.id; + if (o.oneofs) + d._id = "id"; } if (m.pubkey != null && m.hasOwnProperty("pubkey")) { d.pubkey = $root.PublicKey.toObject(m.pubkey, o); + if (o.oneofs) + d._pubkey = "pubkey"; } return d; }; @@ -182,8 +199,8 @@ $root.Exchange = (function() { * @property {number} Secp256k1=2 Secp256k1 value * @property {number} ECDSA=3 ECDSA value */ -$root.KeyType = (function() { - var valuesById = {}, values = Object.create(valuesById); +export const KeyType = $root.KeyType = (() => { + const valuesById = {}, values = Object.create(valuesById); values[valuesById[0] = "RSA"] = 0; values[valuesById[1] = "Ed25519"] = 1; values[valuesById[2] = "Secp256k1"] = 2; @@ -191,7 +208,7 @@ $root.KeyType = (function() { return values; })(); -$root.PublicKey = (function() { +export const PublicKey = $root.PublicKey = (() => { /** * Properties of a PublicKey. @@ -368,4 +385,4 @@ $root.PublicKey = (function() { return PublicKey; })(); -module.exports = $root; +export { $root as default }; diff --git a/src/insecure/proto.proto b/src/insecure/pb/proto.proto similarity index 100% rename from src/insecure/proto.proto rename to src/insecure/pb/proto.proto diff --git a/src/insecure/plaintext.js b/src/insecure/plaintext.js deleted file mode 100644 index 99921e5376..0000000000 --- a/src/insecure/plaintext.js +++ /dev/null @@ -1,95 +0,0 @@ -'use strict' - -const debug = require('debug') -const log = Object.assign(debug('libp2p:plaintext'), { - error: debug('libp2p:plaintext:err') -}) -// @ts-ignore it-handshake do not export types -const handshake = require('it-handshake') -const lp = require('it-length-prefixed') -const PeerId = require('peer-id') -const { UnexpectedPeerError, InvalidCryptoExchangeError } = require('libp2p-interfaces/src/crypto/errors') - -const { Exchange, KeyType } = require('./proto') -const protocol = '/plaintext/2.0.0' - -/** - * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection - */ - -/** - * @param {import('./proto').IExchange} exchange - */ -function lpEncodeExchange (exchange) { - const pb = Exchange.encode(exchange).finish() - // @ts-ignore TODO: Uint8Array not assignable to Buffer - return lp.encode.single(pb) -} - -/** - * Encrypt connection. - * - * @param {PeerId} localId - * @param {Connection} conn - * @param {PeerId} [remoteId] - */ -async function encrypt (localId, conn, remoteId) { - const shake = handshake(conn) - - // Encode the public key and write it to the remote peer - shake.write(lpEncodeExchange({ - id: localId.toBytes(), - pubkey: { - Type: KeyType.RSA, // TODO: dont hard code - Data: localId.marshalPubKey() - } - })) - - log('write pubkey exchange to peer %j', remoteId) - - // Get the Exchange message - const response = (await lp.decode.fromReader(shake.reader).next()).value - const id = Exchange.decode(response.slice()) - log('read pubkey exchange from peer %j', remoteId) - - let peerId - try { - peerId = await PeerId.createFromPubKey(id.pubkey.Data) - } catch (/** @type {any} */ err) { - log.error(err) - throw new InvalidCryptoExchangeError('Remote did not provide its public key') - } - - if (remoteId && !peerId.equals(remoteId)) { - throw new UnexpectedPeerError() - } - - log('plaintext key exchange completed successfully with peer %j', peerId) - - shake.rest() - return { - conn: shake.stream, - remotePeer: peerId - } -} - -module.exports = - { - protocol, - /** - * @param {PeerId} localId - * @param {Connection} conn - * @param {PeerId | undefined} remoteId - */ - secureInbound: (localId, conn, remoteId) => { - return encrypt(localId, conn, remoteId) - }, - /** - * @param {PeerId} localId - * @param {Connection} conn - * @param {PeerId | undefined} remoteId - */ - secureOutbound: (localId, conn, remoteId) => { - return encrypt(localId, conn, remoteId) - } - } diff --git a/src/keychain/cms.js b/src/keychain/cms.ts similarity index 50% rename from src/keychain/cms.js rename to src/keychain/cms.ts index f929cb4e71..8a26c33868 100644 --- a/src/keychain/cms.js +++ b/src/keychain/cms.ts @@ -1,18 +1,18 @@ -'use strict' - -// @ts-ignore node-forge types not exported -require('node-forge/lib/pkcs7') -// @ts-ignore node-forge types not exported -require('node-forge/lib/pbe') -// @ts-ignore node-forge types not exported -const forge = require('node-forge/lib/forge') -const { certificateForKey, findAsync } = require('./util') -const errcode = require('err-code') -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') -const { codes } = require('../errors') - -const privates = new WeakMap() +import 'node-forge/lib/pkcs7.js' +import 'node-forge/lib/pbe.js' +// @ts-expect-error types are missing +import forge from 'node-forge/lib/forge.js' +import { certificateForKey, findAsync } from './util.js' +import errCode from 'err-code' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { codes } from '../errors.js' +import { logger } from '@libp2p/logger' +import type { KeyChain } from './index.js' + +const log = logger('libp2p:keychain:cms') + +const privates = new WeakMap() /** * Cryptographic Message Syntax (aka PKCS #7) @@ -23,16 +23,15 @@ const privates = new WeakMap() * * See RFC 5652 for all the details. */ -class CMS { +export class CMS { + private readonly keychain: KeyChain + /** * Creates a new instance with a keychain - * - * @param {import('./index')} keychain - the available keys - * @param {string} dek */ - constructor (keychain, dek) { - if (!keychain) { - throw errcode(new Error('keychain is required'), codes.ERR_KEYCHAIN_REQUIRED) + constructor (keychain: KeyChain, dek: string) { + if (keychain == null) { + throw errCode(new Error('keychain is required'), codes.ERR_KEYCHAIN_REQUIRED) } this.keychain = keychain @@ -43,20 +42,21 @@ class CMS { * Creates some protected data. * * The output Uint8Array contains the PKCS #7 message in DER. - * - * @param {string} name - The local key name. - * @param {Uint8Array} plain - The data to encrypt. - * @returns {Promise} */ - async encrypt (name, plain) { + async encrypt (name: string, plain: Uint8Array): Promise { if (!(plain instanceof Uint8Array)) { - throw errcode(new Error('Plain data must be a Uint8Array'), codes.ERR_INVALID_PARAMETERS) + throw errCode(new Error('Plain data must be a Uint8Array'), codes.ERR_INVALID_PARAMETERS) } const key = await this.keychain.findKeyByName(name) - const pem = await this.keychain._getPrivateKey(name) - /** @type {string} */ - const dek = privates.get(this).dek + const pem = await this.keychain.getPrivateKey(name) + const cached = privates.get(this) + + if (cached == null) { + throw errCode(new Error('dek missing'), codes.ERR_INVALID_PARAMETERS) + } + + const dek = cached.dek const privateKey = forge.pki.decryptRsaPrivateKey(pem, dek) const certificate = await certificateForKey(key, privateKey) @@ -76,71 +76,75 @@ class CMS { * * The keychain must contain one of the keys used to encrypt the data. If none of the keys * exists, an Error is returned with the property 'missingKeys'. It is array of key ids. - * - * @param {Uint8Array} cmsData - The CMS encrypted data to decrypt. - * @returns {Promise} */ - async decrypt (cmsData) { + async decrypt (cmsData: Uint8Array): Promise { if (!(cmsData instanceof Uint8Array)) { - throw errcode(new Error('CMS data is required'), codes.ERR_INVALID_PARAMETERS) + throw errCode(new Error('CMS data is required'), codes.ERR_INVALID_PARAMETERS) } - let cms + let cms: any try { const buf = forge.util.createBuffer(uint8ArrayToString(cmsData, 'ascii')) const obj = forge.asn1.fromDer(buf) - // @ts-ignore not defined + cms = forge.pkcs7.messageFromAsn1(obj) - } catch (/** @type {any} */ err) { - throw errcode(new Error('Invalid CMS: ' + err.message), codes.ERR_INVALID_CMS) + } catch (err: any) { + log.error(err) + throw errCode(new Error('Invalid CMS'), codes.ERR_INVALID_CMS) } // Find a recipient whose key we hold. We only deal with recipient certs // issued by ipfs (O=ipfs). - const recipients = cms.recipients - // @ts-ignore cms types not defined + const recipients: any = cms.recipients + // @ts-expect-error cms types not defined .filter(r => r.issuer.find(a => a.shortName === 'O' && a.value === 'ipfs')) - // @ts-ignore cms types not defined + // @ts-expect-error cms types not defined .filter(r => r.issuer.find(a => a.shortName === 'CN')) - // @ts-ignore cms types not defined + // @ts-expect-error cms types not defined .map(r => { return { recipient: r, - // @ts-ignore cms types not defined + // @ts-expect-error cms types not defined keyId: r.issuer.find(a => a.shortName === 'CN').value } }) - const r = await findAsync(recipients, async (recipient) => { + const r = await findAsync(recipients, async (recipient: any) => { try { const key = await this.keychain.findKeyById(recipient.keyId) - if (key) return true - } catch (/** @type {any} */ err) { + if (key != null) { + return true + } + } catch (err: any) { return false } return false }) - if (!r) { - // @ts-ignore cms types not defined - const missingKeys = recipients.map(r => r.keyId) - throw errcode(new Error('Decryption needs one of the key(s): ' + missingKeys.join(', ')), codes.ERR_MISSING_KEYS, { + if (r == null) { + // @ts-expect-error cms types not defined + const missingKeys: string[] = recipients.map(r => r.keyId) + throw errCode(new Error(`Decryption needs one of the key(s): ${missingKeys.join(', ')}`), codes.ERR_MISSING_KEYS, { missingKeys }) } const key = await this.keychain.findKeyById(r.keyId) - if (!key) { - throw errcode(new Error('No key available to decrypto'), codes.ERR_NO_KEY) + if (key == null) { + throw errCode(new Error('No key available to decrypto'), codes.ERR_NO_KEY) + } + + const pem = await this.keychain.getPrivateKey(key.name) + const cached = privates.get(this) + + if (cached == null) { + throw errCode(new Error('dek missing'), codes.ERR_INVALID_PARAMETERS) } - const pem = await this.keychain._getPrivateKey(key.name) - const dek = privates.get(this).dek + const dek = cached.dek const privateKey = forge.pki.decryptRsaPrivateKey(pem, dek) cms.decrypt(r.recipient, privateKey) return uint8ArrayFromString(cms.content.getBytes(), 'ascii') } } - -module.exports = CMS diff --git a/src/keychain/index.js b/src/keychain/index.js deleted file mode 100644 index f6a17ab62e..0000000000 --- a/src/keychain/index.js +++ /dev/null @@ -1,561 +0,0 @@ -/* eslint max-nested-callbacks: ["error", 5] */ -'use strict' -const debug = require('debug') -const log = Object.assign(debug('libp2p:keychain'), { - error: debug('libp2p:keychain:err') -}) -const sanitize = require('sanitize-filename') -const mergeOptions = require('merge-options') -const crypto = require('libp2p-crypto') -const { Key } = require('interface-datastore/key') -const CMS = require('./cms') -const errcode = require('err-code') -const { codes } = require('../errors') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') - -// @ts-ignore node-forge sha512 types not exported -require('node-forge/lib/sha512') - -/** - * @typedef {import('peer-id')} PeerId - * @typedef {import('interface-datastore').Datastore} Datastore - */ - -/** - * @typedef {Object} DekOptions - * @property {string} hash - * @property {string} salt - * @property {number} iterationCount - * @property {number} keyLength - * - * @typedef {Object} KeychainOptions - * @property {string} [pass] - * @property {DekOptions} [dek] - */ - -/** - * Information about a key. - * - * @typedef {Object} KeyInfo - * @property {string} id - The universally unique key id. - * @property {string} name - The local key name. - */ - -const keyPrefix = '/pkcs8/' -const infoPrefix = '/info/' -const privates = new WeakMap() - -// NIST SP 800-132 -const NIST = { - minKeyLength: 112 / 8, - minSaltLength: 128 / 8, - minIterationCount: 1000 -} - -const defaultOptions = { - // See https://cryptosense.com/parametesr-choice-for-pbkdf2/ - dek: { - keyLength: 512 / 8, - iterationCount: 10000, - salt: 'you should override this value with a crypto secure random number', - hash: 'sha2-512' - } -} - -/** - * @param {string} name - */ -function validateKeyName (name) { - if (!name) return false - if (typeof name !== 'string') return false - return name === sanitize(name.trim()) -} - -/** - * Throws an error after a delay - * - * This assumes than an error indicates that the keychain is under attack. Delay returning an - * error to make brute force attacks harder. - * - * @param {string|Error} err - The error - * @returns {Promise} - * @private - */ -async function throwDelayed (err) { - const min = 200 - const max = 1000 - const delay = Math.random() * (max - min) + min - - await new Promise(resolve => setTimeout(resolve, delay)) - throw err -} - -/** - * Converts a key name into a datastore name. - * - * @param {string} name - * @returns {Key} - * @private - */ -function DsName (name) { - return new Key(keyPrefix + name) -} - -/** - * Converts a key name into a datastore info name. - * - * @param {string} name - * @returns {Key} - * @private - */ -function DsInfoName (name) { - return new Key(infoPrefix + name) -} - -/** - * Manages the lifecycle of a key. Keys are encrypted at rest using PKCS #8. - * - * A key in the store has two entries - * - '/info/*key-name*', contains the KeyInfo for the key - * - '/pkcs8/*key-name*', contains the PKCS #8 for the key - * - */ -class Keychain { - /** - * Creates a new instance of a key chain. - * - * @param {Datastore} store - where the key are. - * @param {KeychainOptions} options - * @class - */ - constructor (store, options) { - if (!store) { - throw new Error('store is required') - } - this.store = store - - this.opts = mergeOptions(defaultOptions, options) - - // Enforce NIST SP 800-132 - if (this.opts.pass && this.opts.pass.length < 20) { - throw new Error('pass must be least 20 characters') - } - if (this.opts.dek.keyLength < NIST.minKeyLength) { - throw new Error(`dek.keyLength must be least ${NIST.minKeyLength} bytes`) - } - if (this.opts.dek.salt.length < NIST.minSaltLength) { - throw new Error(`dek.saltLength must be least ${NIST.minSaltLength} bytes`) - } - if (this.opts.dek.iterationCount < NIST.minIterationCount) { - throw new Error(`dek.iterationCount must be least ${NIST.minIterationCount}`) - } - - const dek = this.opts.pass - ? crypto.pbkdf2( - this.opts.pass, - this.opts.dek.salt, - this.opts.dek.iterationCount, - this.opts.dek.keyLength, - this.opts.dek.hash) - : '' - - privates.set(this, { dek }) - } - - /** - * Gets an object that can encrypt/decrypt protected data - * using the Cryptographic Message Syntax (CMS). - * - * CMS describes an encapsulation syntax for data protection. It - * is used to digitally sign, digest, authenticate, or encrypt - * arbitrary message content. - * - * @returns {CMS} - */ - get cms () { - return new CMS(this, privates.get(this).dek) - } - - /** - * Generates the options for a keychain. A random salt is produced. - * - * @returns {Object} - */ - static generateOptions () { - const options = Object.assign({}, defaultOptions) - const saltLength = Math.ceil(NIST.minSaltLength / 3) * 3 // no base64 padding - options.dek.salt = uint8ArrayToString(crypto.randomBytes(saltLength), 'base64') - return options - } - - /** - * Gets an object that can encrypt/decrypt protected data. - * The default options for a keychain. - * - * @returns {Object} - */ - static get options () { - return defaultOptions - } - - /** - * Create a new key. - * - * @param {string} name - The local key name; cannot already exist. - * @param {string} type - One of the key types; 'rsa'. - * @param {number} [size = 2048] - The key size in bits. Used for rsa keys only. - * @returns {Promise} - */ - async createKey (name, type, size = 2048) { - const self = this - - if (!validateKeyName(name) || name === 'self') { - return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME)) - } - - if (typeof type !== 'string') { - return throwDelayed(errcode(new Error(`Invalid key type '${type}'`), codes.ERR_INVALID_KEY_TYPE)) - } - - const dsname = DsName(name) - const exists = await self.store.has(dsname) - if (exists) return throwDelayed(errcode(new Error(`Key '${name}' already exists`), codes.ERR_KEY_ALREADY_EXISTS)) - - switch (type.toLowerCase()) { - case 'rsa': - if (!Number.isSafeInteger(size) || size < 2048) { - return throwDelayed(errcode(new Error(`Invalid RSA key size ${size}`), codes.ERR_INVALID_KEY_SIZE)) - } - break - default: - break - } - - let keyInfo - try { - // @ts-ignore Differences between several crypto return types need to be fixed in libp2p-crypto - const keypair = await crypto.keys.generateKeyPair(type, size) - const kid = await keypair.id() - /** @type {string} */ - const dek = privates.get(this).dek - const pem = await keypair.export(dek) - keyInfo = { - name: name, - id: kid - } - const batch = self.store.batch() - batch.put(dsname, uint8ArrayFromString(pem)) - batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo))) - - await batch.commit() - } catch (/** @type {any} */ err) { - return throwDelayed(err) - } - - return keyInfo - } - - /** - * List all the keys. - * - * @returns {Promise} - */ - async listKeys () { - const self = this - const query = { - prefix: infoPrefix - } - - const info = [] - for await (const value of self.store.query(query)) { - info.push(JSON.parse(uint8ArrayToString(value.value))) - } - - return info - } - - /** - * Find a key by it's id. - * - * @param {string} id - The universally unique key identifier. - * @returns {Promise} - */ - async findKeyById (id) { - try { - const keys = await this.listKeys() - return keys.find((k) => k.id === id) - } catch (/** @type {any} */ err) { - return throwDelayed(err) - } - } - - /** - * Find a key by it's name. - * - * @param {string} name - The local key name. - * @returns {Promise} - */ - async findKeyByName (name) { - if (!validateKeyName(name)) { - return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME)) - } - - const dsname = DsInfoName(name) - try { - const res = await this.store.get(dsname) - return JSON.parse(uint8ArrayToString(res)) - } catch (/** @type {any} */ err) { - return throwDelayed(errcode(new Error(`Key '${name}' does not exist. ${err.message}`), codes.ERR_KEY_NOT_FOUND)) - } - } - - /** - * Remove an existing key. - * - * @param {string} name - The local key name; must already exist. - * @returns {Promise} - */ - async removeKey (name) { - const self = this - if (!validateKeyName(name) || name === 'self') { - return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME)) - } - const dsname = DsName(name) - const keyInfo = await self.findKeyByName(name) - const batch = self.store.batch() - batch.delete(dsname) - batch.delete(DsInfoName(name)) - await batch.commit() - return keyInfo - } - - /** - * Rename a key - * - * @param {string} oldName - The old local key name; must already exist. - * @param {string} newName - The new local key name; must not already exist. - * @returns {Promise} - */ - async renameKey (oldName, newName) { - const self = this - if (!validateKeyName(oldName) || oldName === 'self') { - return throwDelayed(errcode(new Error(`Invalid old key name '${oldName}'`), codes.ERR_OLD_KEY_NAME_INVALID)) - } - if (!validateKeyName(newName) || newName === 'self') { - return throwDelayed(errcode(new Error(`Invalid new key name '${newName}'`), codes.ERR_NEW_KEY_NAME_INVALID)) - } - const oldDsname = DsName(oldName) - const newDsname = DsName(newName) - const oldInfoName = DsInfoName(oldName) - const newInfoName = DsInfoName(newName) - - const exists = await self.store.has(newDsname) - if (exists) return throwDelayed(errcode(new Error(`Key '${newName}' already exists`), codes.ERR_KEY_ALREADY_EXISTS)) - - try { - const pem = await self.store.get(oldDsname) - const res = await self.store.get(oldInfoName) - - const keyInfo = JSON.parse(uint8ArrayToString(res)) - keyInfo.name = newName - const batch = self.store.batch() - batch.put(newDsname, pem) - batch.put(newInfoName, uint8ArrayFromString(JSON.stringify(keyInfo))) - batch.delete(oldDsname) - batch.delete(oldInfoName) - await batch.commit() - return keyInfo - } catch (/** @type {any} */ err) { - return throwDelayed(err) - } - } - - /** - * Export an existing key as a PEM encrypted PKCS #8 string - * - * @param {string} name - The local key name; must already exist. - * @param {string} password - The password - * @returns {Promise} - */ - async exportKey (name, password) { - if (!validateKeyName(name)) { - return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME)) - } - if (!password) { - return throwDelayed(errcode(new Error('Password is required'), codes.ERR_PASSWORD_REQUIRED)) - } - - const dsname = DsName(name) - try { - const res = await this.store.get(dsname) - const pem = uint8ArrayToString(res) - /** @type {string} */ - const dek = privates.get(this).dek - const privateKey = await crypto.keys.import(pem, dek) - return privateKey.export(password) - } catch (/** @type {any} */ err) { - return throwDelayed(err) - } - } - - /** - * Import a new key from a PEM encoded PKCS #8 string - * - * @param {string} name - The local key name; must not already exist. - * @param {string} pem - The PEM encoded PKCS #8 string - * @param {string} password - The password. - * @returns {Promise} - */ - async importKey (name, pem, password) { - const self = this - if (!validateKeyName(name) || name === 'self') { - return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME)) - } - if (!pem) { - return throwDelayed(errcode(new Error('PEM encoded key is required'), codes.ERR_PEM_REQUIRED)) - } - const dsname = DsName(name) - const exists = await self.store.has(dsname) - if (exists) return throwDelayed(errcode(new Error(`Key '${name}' already exists`), codes.ERR_KEY_ALREADY_EXISTS)) - - let privateKey - try { - privateKey = await crypto.keys.import(pem, password) - } catch (/** @type {any} */ err) { - return throwDelayed(errcode(new Error('Cannot read the key, most likely the password is wrong'), codes.ERR_CANNOT_READ_KEY)) - } - - let kid - try { - kid = await privateKey.id() - /** @type {string} */ - const dek = privates.get(this).dek - pem = await privateKey.export(dek) - } catch (/** @type {any} */ err) { - return throwDelayed(err) - } - - const keyInfo = { - name: name, - id: kid - } - const batch = self.store.batch() - batch.put(dsname, uint8ArrayFromString(pem)) - batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo))) - await batch.commit() - - return keyInfo - } - - /** - * Import a peer key - * - * @param {string} name - The local key name; must not already exist. - * @param {PeerId} peer - The PEM encoded PKCS #8 string - * @returns {Promise} - */ - async importPeer (name, peer) { - const self = this - if (!validateKeyName(name)) { - return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME)) - } - if (!peer || !peer.privKey) { - return throwDelayed(errcode(new Error('Peer.privKey is required'), codes.ERR_MISSING_PRIVATE_KEY)) - } - - const privateKey = peer.privKey - const dsname = DsName(name) - const exists = await self.store.has(dsname) - if (exists) return throwDelayed(errcode(new Error(`Key '${name}' already exists`), codes.ERR_KEY_ALREADY_EXISTS)) - - try { - const kid = await privateKey.id() - /** @type {string} */ - const dek = privates.get(this).dek - const pem = await privateKey.export(dek) - const keyInfo = { - name: name, - id: kid - } - const batch = self.store.batch() - batch.put(dsname, uint8ArrayFromString(pem)) - batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo))) - await batch.commit() - return keyInfo - } catch (/** @type {any} */ err) { - return throwDelayed(err) - } - } - - /** - * Gets the private key as PEM encoded PKCS #8 string. - * - * @param {string} name - * @returns {Promise} - */ - async _getPrivateKey (name) { - if (!validateKeyName(name)) { - return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME)) - } - - try { - const dsname = DsName(name) - const res = await this.store.get(dsname) - return uint8ArrayToString(res) - } catch (/** @type {any} */ err) { - return throwDelayed(errcode(new Error(`Key '${name}' does not exist. ${err.message}`), codes.ERR_KEY_NOT_FOUND)) - } - } - - /** - * Rotate keychain password and re-encrypt all assosciated keys - * - * @param {string} oldPass - The old local keychain password - * @param {string} newPass - The new local keychain password - */ - async rotateKeychainPass (oldPass, newPass) { - if (typeof oldPass !== 'string') { - return throwDelayed(errcode(new Error(`Invalid old pass type '${typeof oldPass}'`), codes.ERR_INVALID_OLD_PASS_TYPE)) - } - if (typeof newPass !== 'string') { - return throwDelayed(errcode(new Error(`Invalid new pass type '${typeof newPass}'`), codes.ERR_INVALID_NEW_PASS_TYPE)) - } - if (newPass.length < 20) { - return throwDelayed(errcode(new Error(`Invalid pass length ${newPass.length}`), codes.ERR_INVALID_PASS_LENGTH)) - } - log('recreating keychain') - const oldDek = privates.get(this).dek - this.opts.pass = newPass - const newDek = newPass - ? crypto.pbkdf2( - newPass, - this.opts.dek.salt, - this.opts.dek.iterationCount, - this.opts.dek.keyLength, - this.opts.dek.hash) - : '' - privates.set(this, { dek: newDek }) - const keys = await this.listKeys() - for (const key of keys) { - const res = await this.store.get(DsName(key.name)) - const pem = uint8ArrayToString(res) - const privateKey = await crypto.keys.import(pem, oldDek) - const password = newDek.toString() - const keyAsPEM = await privateKey.export(password) - - // Update stored key - const batch = this.store.batch() - const keyInfo = { - name: key.name, - id: key.id - } - batch.put(DsName(key.name), uint8ArrayFromString(keyAsPEM)) - batch.put(DsInfoName(key.name), uint8ArrayFromString(JSON.stringify(keyInfo))) - await batch.commit() - } - log('keychain reconstructed') - } -} - -module.exports = Keychain diff --git a/src/keychain/index.ts b/src/keychain/index.ts new file mode 100644 index 0000000000..b95cfb8adf --- /dev/null +++ b/src/keychain/index.ts @@ -0,0 +1,588 @@ +/* eslint max-nested-callbacks: ["error", 5] */ + +import { logger } from '@libp2p/logger' +import sanitize from 'sanitize-filename' +import mergeOptions from 'merge-options' +import { Key } from 'interface-datastore/key' +import { CMS } from './cms.js' +import errCode from 'err-code' +import { codes } from '../errors.js' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import 'node-forge/lib/sha512.js' +import { generateKeyPair, importKey, unmarshalPrivateKey } from '@libp2p/crypto/keys' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { Components } from '@libp2p/interfaces/components' +import { pbkdf2, randomBytes } from '@libp2p/crypto' + +const log = logger('libp2p:keychain') + +export interface DekOptions { + hash: string + salt: string + iterationCount: number + keyLength: number +} + +export interface KeyChainInit { + pass?: string + dek?: DekOptions +} + +/** + * Information about a key. + */ +export interface KeyInfo { + /** + * The universally unique key id + */ + id: string + + /** + * The local key name. + */ + name: string +} + +const keyPrefix = '/pkcs8/' +const infoPrefix = '/info/' +const privates = new WeakMap() + +// NIST SP 800-132 +const NIST = { + minKeyLength: 112 / 8, + minSaltLength: 128 / 8, + minIterationCount: 1000 +} + +const defaultOptions = { + // See https://cryptosense.com/parametesr-choice-for-pbkdf2/ + dek: { + keyLength: 512 / 8, + iterationCount: 10000, + salt: 'you should override this value with a crypto secure random number', + hash: 'sha2-512' + } +} + +function validateKeyName (name: string) { + if (name == null) { + return false + } + if (typeof name !== 'string') { + return false + } + return name === sanitize(name.trim()) && name.length > 0 +} + +/** + * Throws an error after a delay + * + * This assumes than an error indicates that the keychain is under attack. Delay returning an + * error to make brute force attacks harder. + */ +async function randomDelay () { + const min = 200 + const max = 1000 + const delay = Math.random() * (max - min) + min + + await new Promise(resolve => setTimeout(resolve, delay)) +} + +/** + * Converts a key name into a datastore name + */ +function DsName (name: string) { + return new Key(keyPrefix + name) +} + +/** + * Converts a key name into a datastore info name + */ +function DsInfoName (name: string) { + return new Key(infoPrefix + name) +} + +/** + * Manages the lifecycle of a key. Keys are encrypted at rest using PKCS #8. + * + * A key in the store has two entries + * - '/info/*key-name*', contains the KeyInfo for the key + * - '/pkcs8/*key-name*', contains the PKCS #8 for the key + * + */ +export class KeyChain { + private readonly components: Components + private init: KeyChainInit + + /** + * Creates a new instance of a key chain + */ + constructor (components: Components, init: KeyChainInit) { + this.components = components + this.init = mergeOptions(defaultOptions, init) + + // Enforce NIST SP 800-132 + if (this.init.pass != null && this.init.pass?.length < 20) { + throw new Error('pass must be least 20 characters') + } + if (this.init.dek?.keyLength != null && this.init.dek.keyLength < NIST.minKeyLength) { + throw new Error(`dek.keyLength must be least ${NIST.minKeyLength} bytes`) + } + if (this.init.dek?.salt?.length != null && this.init.dek.salt.length < NIST.minSaltLength) { + throw new Error(`dek.saltLength must be least ${NIST.minSaltLength} bytes`) + } + if (this.init.dek?.iterationCount != null && this.init.dek.iterationCount < NIST.minIterationCount) { + throw new Error(`dek.iterationCount must be least ${NIST.minIterationCount}`) + } + + const dek = this.init.pass != null && this.init.dek?.salt != null + ? pbkdf2( + this.init.pass, + this.init.dek?.salt, + this.init.dek?.iterationCount, + this.init.dek?.keyLength, + this.init.dek?.hash) + : '' + + privates.set(this, { dek }) + } + + /** + * Gets an object that can encrypt/decrypt protected data + * using the Cryptographic Message Syntax (CMS). + * + * CMS describes an encapsulation syntax for data protection. It + * is used to digitally sign, digest, authenticate, or encrypt + * arbitrary message content + */ + get cms () { + const cached = privates.get(this) + + if (cached == null) { + throw errCode(new Error('dek missing'), codes.ERR_INVALID_PARAMETERS) + } + + const dek = cached.dek + + return new CMS(this, dek) + } + + /** + * Generates the options for a keychain. A random salt is produced. + * + * @returns {Object} + */ + static generateOptions (): KeyChainInit { + const options = Object.assign({}, defaultOptions) + const saltLength = Math.ceil(NIST.minSaltLength / 3) * 3 // no base64 padding + options.dek.salt = uint8ArrayToString(randomBytes(saltLength), 'base64') + return options + } + + /** + * Gets an object that can encrypt/decrypt protected data. + * The default options for a keychain. + * + * @returns {Object} + */ + static get options () { + return defaultOptions + } + + /** + * Create a new key. + * + * @param {string} name - The local key name; cannot already exist. + * @param {string} type - One of the key types; 'rsa'. + * @param {number} [size = 2048] - The key size in bits. Used for rsa keys only + */ + async createKey (name: string, type: 'RSA' | 'Ed25519', size = 2048): Promise { + if (!validateKeyName(name) || name === 'self') { + await randomDelay() + throw errCode(new Error('Invalid key name'), codes.ERR_INVALID_KEY_NAME) + } + + if (typeof type !== 'string') { + await randomDelay() + throw errCode(new Error('Invalid key type'), codes.ERR_INVALID_KEY_TYPE) + } + + const dsname = DsName(name) + const exists = await this.components.getDatastore().has(dsname) + if (exists) { + await randomDelay() + throw errCode(new Error('Key name already exists'), codes.ERR_KEY_ALREADY_EXISTS) + } + + switch (type.toLowerCase()) { + case 'rsa': + if (!Number.isSafeInteger(size) || size < 2048) { + await randomDelay() + throw errCode(new Error('Invalid RSA key size'), codes.ERR_INVALID_KEY_SIZE) + } + break + default: + break + } + + let keyInfo + try { + const keypair = await generateKeyPair(type, size) + const kid = await keypair.id() + const cached = privates.get(this) + + if (cached == null) { + throw errCode(new Error('dek missing'), codes.ERR_INVALID_PARAMETERS) + } + + const dek = cached.dek + const pem = await keypair.export(dek) + keyInfo = { + name: name, + id: kid + } + const batch = this.components.getDatastore().batch() + batch.put(dsname, uint8ArrayFromString(pem)) + batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo))) + + await batch.commit() + } catch (err: any) { + await randomDelay() + throw err + } + + return keyInfo + } + + /** + * List all the keys. + * + * @returns {Promise} + */ + async listKeys () { + const query = { + prefix: infoPrefix + } + + const info = [] + for await (const value of this.components.getDatastore().query(query)) { + info.push(JSON.parse(uint8ArrayToString(value.value))) + } + + return info + } + + /** + * Find a key by it's id + */ + async findKeyById (id: string): Promise { + try { + const keys = await this.listKeys() + return keys.find((k) => k.id === id) + } catch (err: any) { + await randomDelay() + throw err + } + } + + /** + * Find a key by it's name. + * + * @param {string} name - The local key name. + * @returns {Promise} + */ + async findKeyByName (name: string): Promise { + if (!validateKeyName(name)) { + await randomDelay() + throw errCode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME) + } + + const dsname = DsInfoName(name) + try { + const res = await this.components.getDatastore().get(dsname) + return JSON.parse(uint8ArrayToString(res)) + } catch (err: any) { + await randomDelay() + log.error(err) + throw errCode(new Error(`Key '${name}' does not exist.`), codes.ERR_KEY_NOT_FOUND) + } + } + + /** + * Remove an existing key. + * + * @param {string} name - The local key name; must already exist. + * @returns {Promise} + */ + async removeKey (name: string) { + if (!validateKeyName(name) || name === 'self') { + await randomDelay() + throw errCode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME) + } + const dsname = DsName(name) + const keyInfo = await this.findKeyByName(name) + const batch = this.components.getDatastore().batch() + batch.delete(dsname) + batch.delete(DsInfoName(name)) + await batch.commit() + return keyInfo + } + + /** + * Rename a key + * + * @param {string} oldName - The old local key name; must already exist. + * @param {string} newName - The new local key name; must not already exist. + * @returns {Promise} + */ + async renameKey (oldName: string, newName: string): Promise { + if (!validateKeyName(oldName) || oldName === 'self') { + await randomDelay() + throw errCode(new Error(`Invalid old key name '${oldName}'`), codes.ERR_OLD_KEY_NAME_INVALID) + } + if (!validateKeyName(newName) || newName === 'self') { + await randomDelay() + throw errCode(new Error(`Invalid new key name '${newName}'`), codes.ERR_NEW_KEY_NAME_INVALID) + } + const oldDsname = DsName(oldName) + const newDsname = DsName(newName) + const oldInfoName = DsInfoName(oldName) + const newInfoName = DsInfoName(newName) + + const exists = await this.components.getDatastore().has(newDsname) + if (exists) { + await randomDelay() + throw errCode(new Error(`Key '${newName}' already exists`), codes.ERR_KEY_ALREADY_EXISTS) + } + + try { + const pem = await this.components.getDatastore().get(oldDsname) + const res = await this.components.getDatastore().get(oldInfoName) + + const keyInfo = JSON.parse(uint8ArrayToString(res)) + keyInfo.name = newName + const batch = this.components.getDatastore().batch() + batch.put(newDsname, pem) + batch.put(newInfoName, uint8ArrayFromString(JSON.stringify(keyInfo))) + batch.delete(oldDsname) + batch.delete(oldInfoName) + await batch.commit() + return keyInfo + } catch (err: any) { + await randomDelay() + throw err + } + } + + /** + * Export an existing key as a PEM encrypted PKCS #8 string + */ + async exportKey (name: string, password: string) { + if (!validateKeyName(name)) { + await randomDelay() + throw errCode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME) + } + if (password == null) { + await randomDelay() + throw errCode(new Error('Password is required'), codes.ERR_PASSWORD_REQUIRED) + } + + const dsname = DsName(name) + try { + const res = await this.components.getDatastore().get(dsname) + const pem = uint8ArrayToString(res) + const cached = privates.get(this) + + if (cached == null) { + throw errCode(new Error('dek missing'), codes.ERR_INVALID_PARAMETERS) + } + + const dek = cached.dek + const privateKey = await importKey(pem, dek) + return await privateKey.export(password) + } catch (err: any) { + await randomDelay() + throw err + } + } + + /** + * Import a new key from a PEM encoded PKCS #8 string + * + * @param {string} name - The local key name; must not already exist. + * @param {string} pem - The PEM encoded PKCS #8 string + * @param {string} password - The password. + * @returns {Promise} + */ + async importKey (name: string, pem: string, password: string): Promise { + if (!validateKeyName(name) || name === 'self') { + await randomDelay() + throw errCode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME) + } + if (pem == null) { + await randomDelay() + throw errCode(new Error('PEM encoded key is required'), codes.ERR_PEM_REQUIRED) + } + const dsname = DsName(name) + const exists = await this.components.getDatastore().has(dsname) + if (exists) { + await randomDelay() + throw errCode(new Error(`Key '${name}' already exists`), codes.ERR_KEY_ALREADY_EXISTS) + } + + let privateKey + try { + privateKey = await importKey(pem, password) + } catch (err: any) { + await randomDelay() + throw errCode(new Error('Cannot read the key, most likely the password is wrong'), codes.ERR_CANNOT_READ_KEY) + } + + let kid + try { + kid = await privateKey.id() + const cached = privates.get(this) + + if (cached == null) { + throw errCode(new Error('dek missing'), codes.ERR_INVALID_PARAMETERS) + } + + const dek = cached.dek + pem = await privateKey.export(dek) + } catch (err: any) { + await randomDelay() + throw err + } + + const keyInfo = { + name: name, + id: kid + } + const batch = this.components.getDatastore().batch() + batch.put(dsname, uint8ArrayFromString(pem)) + batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo))) + await batch.commit() + + return keyInfo + } + + /** + * Import a peer key + */ + async importPeer (name: string, peer: PeerId): Promise { + try { + if (!validateKeyName(name)) { + throw errCode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME) + } + if (peer == null || peer.privateKey == null) { + throw errCode(new Error('Peer.privKey is required'), codes.ERR_MISSING_PRIVATE_KEY) + } + + const privateKey = await unmarshalPrivateKey(peer.privateKey) + + const dsname = DsName(name) + const exists = await this.components.getDatastore().has(dsname) + if (exists) { + await randomDelay() + throw errCode(new Error(`Key '${name}' already exists`), codes.ERR_KEY_ALREADY_EXISTS) + } + + const cached = privates.get(this) + + if (cached == null) { + throw errCode(new Error('dek missing'), codes.ERR_INVALID_PARAMETERS) + } + + const dek = cached.dek + const pem = await privateKey.export(dek) + const keyInfo: KeyInfo = { + name: name, + id: peer.toString() + } + const batch = this.components.getDatastore().batch() + batch.put(dsname, uint8ArrayFromString(pem)) + batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo))) + await batch.commit() + return keyInfo + } catch (err: any) { + await randomDelay() + throw err + } + } + + /** + * Gets the private key as PEM encoded PKCS #8 string + */ + async getPrivateKey (name: string): Promise { + if (!validateKeyName(name)) { + await randomDelay() + throw errCode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME) + } + + try { + const dsname = DsName(name) + const res = await this.components.getDatastore().get(dsname) + return uint8ArrayToString(res) + } catch (err: any) { + await randomDelay() + log.error(err) + throw errCode(new Error(`Key '${name}' does not exist.`), codes.ERR_KEY_NOT_FOUND) + } + } + + /** + * Rotate keychain password and re-encrypt all associated keys + */ + async rotateKeychainPass (oldPass: string, newPass: string) { + if (typeof oldPass !== 'string') { + await randomDelay() + throw errCode(new Error(`Invalid old pass type '${typeof oldPass}'`), codes.ERR_INVALID_OLD_PASS_TYPE) + } + if (typeof newPass !== 'string') { + await randomDelay() + throw errCode(new Error(`Invalid new pass type '${typeof newPass}'`), codes.ERR_INVALID_NEW_PASS_TYPE) + } + if (newPass.length < 20) { + await randomDelay() + throw errCode(new Error(`Invalid pass length ${newPass.length}`), codes.ERR_INVALID_PASS_LENGTH) + } + log('recreating keychain') + const cached = privates.get(this) + + if (cached == null) { + throw errCode(new Error('dek missing'), codes.ERR_INVALID_PARAMETERS) + } + + const oldDek = cached.dek + this.init.pass = newPass + const newDek = newPass != null && this.init.dek?.salt != null + ? pbkdf2( + newPass, + this.init.dek.salt, + this.init.dek?.iterationCount, + this.init.dek?.keyLength, + this.init.dek?.hash) + : '' + privates.set(this, { dek: newDek }) + const keys = await this.listKeys() + for (const key of keys) { + const res = await this.components.getDatastore().get(DsName(key.name)) + const pem = uint8ArrayToString(res) + const privateKey = await importKey(pem, oldDek) + const password = newDek.toString() + const keyAsPEM = await privateKey.export(password) + + // Update stored key + const batch = this.components.getDatastore().batch() + const keyInfo = { + name: key.name, + id: key.id + } + batch.put(DsName(key.name), uint8ArrayFromString(keyAsPEM)) + batch.put(DsInfoName(key.name), uint8ArrayFromString(JSON.stringify(keyInfo))) + await batch.commit() + } + log('keychain reconstructed') + } +} diff --git a/src/keychain/util.js b/src/keychain/util.ts similarity index 78% rename from src/keychain/util.js rename to src/keychain/util.ts index a84c3f1081..7e01542649 100644 --- a/src/keychain/util.js +++ b/src/keychain/util.ts @@ -1,8 +1,7 @@ -// @ts-nocheck -'use strict' +import 'node-forge/lib/x509.js' +// @ts-expect-error types are missing +import forge from 'node-forge/lib/forge.js' -require('node-forge/lib/x509') -const forge = require('node-forge/lib/forge') const pki = forge.pki /** @@ -11,19 +10,15 @@ const pki = forge.pki * The output Uint8Array contains the PKCS #7 message in DER. * * TODO: move to libp2p-crypto package - * - * @param {KeyInfo} key - The id and name of the key - * @param {RsaPrivateKey} privateKey - The naked key - * @returns {Uint8Array} */ -const certificateForKey = (key, privateKey) => { - const publicKey = pki.setRsaPublicKey(privateKey.n, privateKey.e) +export const certificateForKey = (key: any, privateKey: forge.pki.rsa.PrivateKey) => { + const publicKey = pki.rsa.setPublicKey(privateKey.n, privateKey.e) const cert = pki.createCertificate() cert.publicKey = publicKey cert.serialNumber = '01' cert.validity.notBefore = new Date() cert.validity.notAfter = new Date() - cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 10) + cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 10) // eslint-disable-line @typescript-eslint/restrict-plus-operands const attrs = [{ name: 'organizationName', value: 'ipfs' @@ -79,14 +74,9 @@ const certificateForKey = (key, privateKey) => { * @param {Array} array * @param {function(*)} asyncCompare - An async function that returns a boolean */ -async function findAsync (array, asyncCompare) { +export async function findAsync (array: T[], asyncCompare: (val: T) => Promise) { const promises = array.map(asyncCompare) const results = await Promise.all(promises) const index = results.findIndex(result => result) return array[index] } - -module.exports = { - certificateForKey, - findAsync -} diff --git a/src/libp2p.ts b/src/libp2p.ts new file mode 100644 index 0000000000..142031e127 --- /dev/null +++ b/src/libp2p.ts @@ -0,0 +1,501 @@ +import { logger } from '@libp2p/logger' +import { AbortOptions, EventEmitter, Startable, CustomEvent, isStartable } from '@libp2p/interfaces' +import type { Multiaddr } from '@multiformats/multiaddr' +import { MemoryDatastore } from 'datastore-core/memory' +import { DefaultPeerRouting } from './peer-routing.js' +import { CompoundContentRouting } from './content-routing/index.js' +import { getPeer } from './get-peer.js' +import { codes } from './errors.js' +import { DefaultAddressManager } from './address-manager/index.js' +import { DefaultConnectionManager } from './connection-manager/index.js' +import { AutoDialler } from './connection-manager/auto-dialler.js' +import { Circuit } from './circuit/transport.js' +import { Relay } from './circuit/index.js' +import { DefaultDialer } from './dialer/index.js' +import { KeyChain } from './keychain/index.js' +import { DefaultMetrics } from './metrics/index.js' +import { DefaultTransportManager } from './transport-manager.js' +import { DefaultUpgrader } from './upgrader.js' +import { DefaultRegistrar } from './registrar.js' +import { IdentifyService } from './identify/index.js' +import { FetchService } from './fetch/index.js' +import { PingService } from './ping/index.js' +import { NatManager } from './nat-manager.js' +import { PeerRecordUpdater } from './peer-record-updater.js' +import { DHTPeerRouting } from './dht/dht-peer-routing.js' +import { PersistentPeerStore } from '@libp2p/peer-store' +import { DHTContentRouting } from './dht/dht-content-routing.js' +import { AutoDialer } from './dialer/auto-dialer.js' +import { Initializable, Components, isInitializable } from '@libp2p/interfaces/components' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { Connection } from '@libp2p/interfaces/connection' +import type { PeerRouting } from '@libp2p/interfaces/peer-routing' +import type { ContentRouting } from '@libp2p/interfaces/content-routing' +import type { PubSub } from '@libp2p/interfaces/pubsub' +import type { ConnectionManager, StreamHandler } from '@libp2p/interfaces/registrar' +import type { PeerInfo } from '@libp2p/interfaces/peer-info' +import type { Libp2p, Libp2pEvents, Libp2pInit, Libp2pOptions } from './index.js' +import { validateConfig } from './config.js' +import { createEd25519PeerId } from '@libp2p/peer-id-factory' +import type { PeerStore } from '@libp2p/interfaces/peer-store' +import type { DualDHT } from '@libp2p/interfaces/dht' +import { concat as uint8ArrayConcat } from 'uint8arrays/concat' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import errCode from 'err-code' +import { unmarshalPublicKey } from '@libp2p/crypto/keys' + +const log = logger('libp2p') + +export class Libp2pNode extends EventEmitter implements Libp2p { + public peerId: PeerId + public dht?: DualDHT + public pubsub?: PubSub + public identifyService?: IdentifyService + public fetchService: FetchService + public pingService: PingService + public components: Components + public peerStore: PeerStore + public contentRouting: ContentRouting + public peerRouting: PeerRouting + public keychain: KeyChain + public connectionManager: ConnectionManager + + private started: boolean + private readonly services: Startable[] + private readonly initializables: Initializable[] + + constructor (init: Libp2pInit) { + super() + + this.services = [] + this.initializables = [] + this.started = false + this.peerId = init.peerId + this.components = new Components({ + peerId: init.peerId, + datastore: init.datastore ?? new MemoryDatastore() + }) + + // Create Metrics + if (init.metrics.enabled) { + this.components.setMetrics(this.configureComponent(new DefaultMetrics(init.metrics))) + } + + this.components.setConnectionGater(this.configureComponent({ + denyDialPeer: async () => await Promise.resolve(false), + denyDialMultiaddr: async () => await Promise.resolve(false), + denyInboundConnection: async () => await Promise.resolve(false), + denyOutboundConnection: async () => await Promise.resolve(false), + denyInboundEncryptedConnection: async () => await Promise.resolve(false), + denyOutboundEncryptedConnection: async () => await Promise.resolve(false), + denyInboundUpgradedConnection: async () => await Promise.resolve(false), + denyOutboundUpgradedConnection: async () => await Promise.resolve(false), + filterMultiaddrForPeer: async () => await Promise.resolve(true), + ...init.connectionGater + })) + + this.peerStore = this.components.setPeerStore(this.configureComponent(new PersistentPeerStore(this.components, init.peerStore))) + + this.peerStore.addEventListener('peer', evt => { + const { detail: peerData } = evt + + this.dispatchEvent(new CustomEvent('peer:discovery', { detail: peerData })) + }) + + // Set up connection protector if configured + if (init.connectionProtector != null) { + this.components.setConnectionProtector(this.configureComponent(init.connectionProtector)) + } + + // Set up the Upgrader + this.components.setUpgrader(this.configureComponent(new DefaultUpgrader(this.components, { + connectionEncryption: (init.connectionEncryption ?? []).map(component => this.configureComponent(component)), + muxers: (init.streamMuxers ?? []).map(component => this.configureComponent(component)) + }))) + + // Create the Connection Manager + this.connectionManager = this.components.setConnectionManager(this.configureComponent(new DefaultConnectionManager(this.components, init.connectionManager))) + + // Create the Registrar + this.components.setRegistrar(this.configureComponent(new DefaultRegistrar(this.components))) + + // Setup the transport manager + this.components.setTransportManager(this.configureComponent(new DefaultTransportManager(this.components, init.transportManager))) + + // Addresses {listen, announce, noAnnounce} + this.components.setAddressManager(this.configureComponent(new DefaultAddressManager(this.components, init.addresses))) + + // update our peer record when addresses change + this.configureComponent(new PeerRecordUpdater(this.components)) + + this.components.setDialer(this.configureComponent(new DefaultDialer(this.components, init.dialer))) + + this.configureComponent(new AutoDialler(this.components, { + enabled: init.connectionManager.autoDial, + minConnections: init.connectionManager.minConnections, + autoDialInterval: init.connectionManager.autoDialInterval + })) + + // Create keychain + const keychainOpts = KeyChain.generateOptions() + this.keychain = this.configureComponent(new KeyChain(this.components, { + ...keychainOpts, + ...init.keychain + })) + + // Create the Nat Manager + this.services.push(new NatManager(this.components, init.nat)) + + init.transports.forEach((transport) => { + this.components.getTransportManager().add(this.configureComponent(transport)) + }) + + // Attach stream multiplexers + if (init.streamMuxers != null && init.streamMuxers.length > 0) { + // Add the identify service since we can multiplex + this.identifyService = new IdentifyService(this.components, { + protocolPrefix: init.protocolPrefix, + host: { + agentVersion: init.host.agentVersion + } + }) + this.configureComponent(this.identifyService) + } + + // dht provided components (peerRouting, contentRouting, dht) + if (init.dht != null) { + this.dht = this.components.setDHT(this.configureComponent(init.dht)) + } + + // Create pubsub if provided + if (init.pubsub != null) { + this.pubsub = this.components.setPubSub(this.configureComponent(init.pubsub)) + } + + // Attach remaining APIs + // peer and content routing will automatically get modules from _modules and _dht + + const peerRouters: PeerRouting[] = (init.peerRouters ?? []).map(component => this.configureComponent(component)) + + if (this.dht != null) { + // add dht to routers + peerRouters.push(this.configureComponent(new DHTPeerRouting(this.dht))) + } + + this.peerRouting = this.components.setPeerRouting(this.configureComponent(new DefaultPeerRouting(this.components, { + ...init.peerRouting, + routers: peerRouters + }))) + + const contentRouters: ContentRouting[] = (init.contentRouters ?? []).map(component => this.configureComponent(component)) + + if (this.dht != null) { + // add dht to routers + contentRouters.push(this.configureComponent(new DHTContentRouting(this.dht))) + } + + this.contentRouting = this.components.setContentRouting(this.configureComponent(new CompoundContentRouting(this.components, { + routers: contentRouters + }))) + + if (init.relay.enabled) { + this.components.getTransportManager().add(this.configureComponent(new Circuit())) + + this.configureComponent(new Relay(this.components, { + addressSorter: init.dialer.addressSorter, + ...init.relay + })) + } + + this.fetchService = this.configureComponent(new FetchService(this.components, { + protocolPrefix: init.protocolPrefix + })) + + this.pingService = this.configureComponent(new PingService(this.components, { + protocolPrefix: init.protocolPrefix + })) + + const autoDialer = this.configureComponent(new AutoDialer(this.components, { + enabled: init.connectionManager.autoDial !== false, + minConnections: init.connectionManager.minConnections ?? Infinity + })) + + this.addEventListener('peer:discovery', evt => { + if (!this.isStarted()) { + return + } + + autoDialer.handle(evt) + }) + + // Discovery modules + for (const service of init.peerDiscovery ?? []) { + this.configureComponent(service) + + service.addEventListener('peer', (evt) => { + this.onDiscoveryPeer(evt) + }) + } + } + + private configureComponent (component: T): T { + if (isStartable(component)) { + this.services.push(component) + } + + if (isInitializable(component)) { + this.initializables.push(component) + } + + return component + } + + /** + * Starts the libp2p node and all its subsystems + */ + async start () { + if (this.started) { + return + } + + this.started = true + + log('libp2p is starting') + + try { + // Set available components on all modules interested in components + this.initializables.forEach(obj => { + obj.init(this.components) + }) + + await Promise.all( + this.services.map(async service => { + if (service.beforeStart != null) { + await service.beforeStart() + } + }) + ) + + // start any startables + await Promise.all( + this.services.map(service => service.start()) + ) + + await Promise.all( + this.services.map(async service => { + if (service.afterStart != null) { + await service.afterStart() + } + }) + ) + + log('libp2p has started') + + // 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 + await this.components.getPeerStore().forEach(peer => { + this.dispatchEvent(new CustomEvent('peer:discovery', { + detail: { + id: peer.id, + multiaddrs: peer.addresses.map(addr => addr.multiaddr), + protocols: peer.protocols + } + })) + }) + } catch (err: any) { + log.error('An error occurred starting libp2p', err) + await this.stop() + throw err + } + } + + /** + * Stop the libp2p node by closing its listeners and open connections + */ + async stop () { + if (!this.started) { + return + } + + log('libp2p is stopping') + + this.started = false + + await Promise.all( + this.services.map(async service => { + if (service.beforeStop != null) { + await service.beforeStop() + } + }) + ) + + await Promise.all( + this.services.map(servce => servce.stop()) + ) + + await Promise.all( + this.services.map(async service => { + if (service.afterStop != null) { + await service.afterStop() + } + }) + ) + + log('libp2p has stopped') + } + + /** + * Load keychain keys from the datastore. + * Imports the private key as 'self', if needed. + */ + async loadKeychain () { + if (this.keychain == null) { + return + } + + try { + await this.keychain.findKeyByName('self') + } catch (err: any) { + await this.keychain.importPeer('self', this.peerId) + } + } + + isStarted () { + return this.started + } + + getConnections (peerId?: PeerId): Connection[] { + if (peerId == null) { + return this.components.getConnectionManager().getConnectionList() + } + + return this.components.getConnectionManager().getConnections(peerId) + } + + getPeers (): PeerId[] { + return this.components.getConnectionManager().getConnectionList() + .map(conn => conn.remotePeer) + } + + async dial (peer: PeerId | Multiaddr, options: AbortOptions = {}): Promise { + return await this.components.getDialer().dial(peer, options) + } + + async dialProtocol (peer: PeerId | Multiaddr, protocols: string | string[], options: AbortOptions = {}) { + return await this.components.getDialer().dialProtocol(peer, protocols, options) + } + + getMultiaddrs (): Multiaddr[] { + return this.components.getAddressManager().getAddresses() + } + + async hangUp (peer: PeerId | Multiaddr | string): Promise { + const { id } = getPeer(peer) + + const connections = this.components.getConnectionManager().getConnections(id) + + await Promise.all( + connections.map(async connection => { + return await connection.close() + }) + ) + } + + /** + * Get the public key for the given peer id + */ + async getPublicKey (peer: PeerId, options: AbortOptions = {}) { + log('getPublicKey %p', peer) + + const peerInfo = await this.peerStore.get(peer) + + if (peerInfo.pubKey != null) { + return peerInfo.pubKey + } + + if (this.dht == null) { + throw errCode(new Error('Public key was not in the peer store and the DHT is not enabled'), codes.ERR_NO_ROUTERS_AVAILABLE) + } + + const peerKey = uint8ArrayConcat([ + uint8ArrayFromString('/pk/'), + peer.multihash.digest + ]) + + // search the dht + for await (const event of this.dht.get(peerKey, options)) { + if (event.name === 'VALUE') { + const key = unmarshalPublicKey(event.value) + + await this.peerStore.keyBook.set(peer, event.value) + + return key + } + } + + throw errCode(new Error(`Node not responding with its public key: ${peer.toString()}`), codes.ERR_INVALID_RECORD) + } + + async fetch (peer: PeerId | Multiaddr | string, key: string): Promise { + const { id, multiaddrs } = getPeer(peer) + + if (multiaddrs != null) { + await this.components.getPeerStore().addressBook.add(id, multiaddrs) + } + + return await this.fetchService.fetch(id, key) + } + + async ping (peer: PeerId | Multiaddr | string): Promise { + const { id, multiaddrs } = getPeer(peer) + + if (multiaddrs.length > 0) { + await this.components.getPeerStore().addressBook.add(id, multiaddrs) + } + + return await this.pingService.ping(id) + } + + async handle (protocols: string | string[], handler: StreamHandler): Promise { + return await this.components.getRegistrar().handle(protocols, handler) + } + + async unhandle (protocols: string[] | string): Promise { + return await this.components.getRegistrar().unhandle(protocols) + } + + /** + * Called whenever peer discovery services emit `peer` events. + * Known peers may be emitted. + */ + onDiscoveryPeer (evt: CustomEvent) { + const { detail: peer } = evt + + if (peer.id.toString() === this.peerId.toString()) { + log.error(new Error(codes.ERR_DISCOVERED_SELF)) + return + } + + if (peer.multiaddrs.length > 0) { + void this.components.getPeerStore().addressBook.add(peer.id, peer.multiaddrs).catch(err => log.error(err)) + } + + if (peer.protocols.length > 0) { + void this.components.getPeerStore().protoBook.set(peer.id, peer.protocols).catch(err => log.error(err)) + } + + this.dispatchEvent(new CustomEvent('peer:discovery', { detail: peer })) + } +} + +/** + * Returns a new Libp2pNode instance - this exposes more of the internals than the + * libp2p interface and is useful for testing and debugging. + */ +export async function createLibp2pNode (options: Libp2pOptions): Promise { + if (options.peerId == null) { + options.peerId = await createEd25519PeerId() + } + + return new Libp2pNode(validateConfig(options)) +} diff --git a/src/metrics/index.js b/src/metrics/index.js deleted file mode 100644 index 2b65c28974..0000000000 --- a/src/metrics/index.js +++ /dev/null @@ -1,290 +0,0 @@ -// @ts-nocheck -'use strict' - -const mergeOptions = require('merge-options') -const { pipe } = require('it-pipe') -const { tap } = require('streaming-iterables') -const oldPeerLRU = require('./old-peers') -const { METRICS: defaultOptions } = require('../constants') -const Stats = require('./stats') - -const initialCounters = [ - 'dataReceived', - 'dataSent' -] - -const directionToEvent = { - in: 'dataReceived', - out: 'dataSent' -} - -/** - * @typedef {import('peer-id')} PeerId - * @typedef {import('libp2p-interfaces/src/transport/types').MultiaddrConnection} MultiaddrConnection - */ - -/** - * @typedef MetricsOptions - * @property {number} [computeThrottleMaxQueueSize = defaultOptions.computeThrottleMaxQueueSize] - * @property {number} [computeThrottleTimeout = defaultOptions.computeThrottleTimeout] - * @property {number[]} [movingAverageIntervals = defaultOptions.movingAverageIntervals] - * @property {number} [maxOldPeersRetention = defaultOptions.maxOldPeersRetention] - */ - -class Metrics { - /** - * @class - * @param {MetricsOptions} options - */ - constructor (options) { - this._options = mergeOptions(defaultOptions, options) - this._globalStats = new Stats(initialCounters, this._options) - this._peerStats = new Map() - this._protocolStats = new Map() - this._oldPeers = oldPeerLRU(this._options.maxOldPeersRetention) - this._running = false - this._onMessage = this._onMessage.bind(this) - this._systems = new Map() - } - - /** - * Must be called for stats to saved. Any data pushed for tracking - * will be ignored. - */ - start () { - this._running = true - } - - /** - * Stops all averages timers and prevents new data from being tracked. - * Once `stop` is called, `start` must be called to resume stats tracking. - */ - stop () { - this._running = false - this._globalStats.stop() - for (const stats of this._peerStats.values()) { - stats.stop() - } - for (const stats of this._protocolStats.values()) { - stats.stop() - } - } - - /** - * Gets the global `Stats` object - * - * @returns {Stats} - */ - get global () { - return this._globalStats - } - - /** - * Returns a list of `PeerId` strings currently being tracked - * - * @returns {string[]} - */ - get peers () { - return Array.from(this._peerStats.keys()) - } - - /** - * @returns {Map>>} - */ - getComponentMetrics () { - return this._systems - } - - updateComponentMetric ({ system = 'libp2p', component, metric, value }) { - if (!this._systems.has(system)) { - this._systems.set(system, new Map()) - } - - const systemMetrics = this._systems.get(system) - - if (!systemMetrics.has(component)) { - systemMetrics.set(component, new Map()) - } - - const componentMetrics = systemMetrics.get(component) - - componentMetrics.set(metric, value) - } - - /** - * Returns the `Stats` object for the given `PeerId` whether it - * is a live peer, or in the disconnected peer LRU cache. - * - * @param {PeerId} peerId - * @returns {Stats} - */ - forPeer (peerId) { - const idString = peerId.toB58String() - return this._peerStats.get(idString) || this._oldPeers.get(idString) - } - - /** - * Returns a list of all protocol strings currently being tracked. - * - * @returns {string[]} - */ - get protocols () { - return Array.from(this._protocolStats.keys()) - } - - /** - * Returns the `Stats` object for the given `protocol`. - * - * @param {string} protocol - * @returns {Stats} - */ - forProtocol (protocol) { - return this._protocolStats.get(protocol) - } - - /** - * Should be called when all connections to a given peer - * have closed. The `Stats` collection for the peer will - * be stopped and moved to an LRU for temporary retention. - * - * @param {PeerId} peerId - */ - onPeerDisconnected (peerId) { - const idString = peerId.toB58String() - const peerStats = this._peerStats.get(idString) - if (peerStats) { - peerStats.stop() - this._peerStats.delete(idString) - this._oldPeers.set(idString, peerStats) - } - } - - /** - * Takes the metadata for a message and tracks it in the - * appropriate categories. If the protocol is present, protocol - * stats will also be tracked. - * - * @private - * @param {object} params - * @param {PeerId} params.remotePeer - Remote peer - * @param {string} [params.protocol] - Protocol string the stream is running - * @param {string} params.direction - One of ['in','out'] - * @param {number} params.dataLength - Size of the message - * @returns {void} - */ - _onMessage ({ remotePeer, protocol, direction, dataLength }) { - if (!this._running) return - - const key = directionToEvent[direction] - - let peerStats = this.forPeer(remotePeer) - if (!peerStats) { - peerStats = new Stats(initialCounters, this._options) - this._peerStats.set(remotePeer.toB58String(), peerStats) - } - - // Peer and global stats - peerStats.push(key, dataLength) - this._globalStats.push(key, dataLength) - - // Protocol specific stats - if (protocol) { - let protocolStats = this.forProtocol(protocol) - if (!protocolStats) { - protocolStats = new Stats(initialCounters, this._options) - this._protocolStats.set(protocol, protocolStats) - } - protocolStats.push(key, dataLength) - } - } - - /** - * Replaces the `PeerId` string with the given `peerId`. - * If stats are already being tracked for the given `peerId`, the - * placeholder stats will be merged with the existing stats. - * - * @param {PeerId} placeholder - A peerId string - * @param {PeerId} peerId - * @returns {void} - */ - updatePlaceholder (placeholder, peerId) { - if (!this._running) return - const placeholderStats = this.forPeer(placeholder) - const peerIdString = peerId.toB58String() - const existingStats = this.forPeer(peerId) - let mergedStats = placeholderStats - - // If we already have stats, merge the two - if (existingStats) { - // If existing, merge - mergedStats = Metrics.mergeStats(existingStats, mergedStats) - // Attempt to delete from the old peers list just in case it was tracked there - this._oldPeers.delete(peerIdString) - } - - this._peerStats.delete(placeholder.toB58String()) - this._peerStats.set(peerIdString, mergedStats) - mergedStats.start() - } - - /** - * Tracks data running through a given Duplex Iterable `stream`. If - * the `peerId` is not provided, a placeholder string will be created and - * returned. This allows lazy tracking of a peer when the peer is not yet known. - * When the `PeerId` is known, `Metrics.updatePlaceholder` should be called - * with the placeholder string returned from here, and the known `PeerId`. - * - * @param {Object} options - * @param {MultiaddrConnection} 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 {MultiaddrConnection} The peerId string or placeholder string - */ - trackStream ({ stream, remotePeer, protocol }) { - const metrics = this - const _source = stream.source - stream.source = tap(chunk => metrics._onMessage({ - remotePeer, - protocol, - direction: 'in', - dataLength: chunk.length - }))(_source) - - const _sink = stream.sink - stream.sink = source => { - return pipe( - source, - tap(chunk => metrics._onMessage({ - remotePeer, - protocol, - direction: 'out', - dataLength: chunk.length - })), - _sink - ) - } - - return stream - } - - /** - * Merges `other` into `target`. `target` will be modified - * and returned. - * - * @param {Stats} target - * @param {Stats} other - * @returns {Stats} - */ - static mergeStats (target, other) { - target.stop() - other.stop() - - // Merge queues - target._queue = [...target._queue, ...other._queue] - - // TODO: how to merge moving averages? - return target - } -} - -module.exports = Metrics diff --git a/src/metrics/index.ts b/src/metrics/index.ts new file mode 100644 index 0000000000..bf632fa2b2 --- /dev/null +++ b/src/metrics/index.ts @@ -0,0 +1,310 @@ +import { pipe } from 'it-pipe' +import each from 'it-foreach' +import LRU from 'hashlru' +import { METRICS as defaultOptions } from '../constants.js' +import { DefaultStats, StatsInit } from './stats.js' +import type { ComponentMetricsUpdate, Metrics, Stats, TrackStreamOptions } from '@libp2p/interfaces/metrics' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { Startable } from '@libp2p/interfaces' +import type { Duplex } from 'it-stream-types' + +const initialCounters: ['dataReceived', 'dataSent'] = [ + 'dataReceived', + 'dataSent' +] + +const directionToEvent = { + in: 'dataReceived', + out: 'dataSent' +} + +export interface OnMessageOptions { + remotePeer: PeerId + protocol?: string + direction: 'in' | 'out' + dataLength: number +} + +export interface MetricsInit { + enabled: boolean + computeThrottleMaxQueueSize: number + computeThrottleTimeout: number + movingAverageIntervals: number[] + maxOldPeersRetention: number +} + +export class DefaultMetrics implements Metrics, Startable { + public globalStats: DefaultStats + + private readonly enabled: boolean + private readonly peerStats: Map + private readonly protocolStats: Map + private readonly oldPeers: ReturnType + private running: boolean + private readonly systems: Map>> + private readonly statsInit: StatsInit + + constructor (init: MetricsInit) { + this.enabled = init.enabled + this.statsInit = { + ...init, + initialCounters + } + this.globalStats = new DefaultStats(this.statsInit) + this.peerStats = new Map() + this.protocolStats = new Map() + this.oldPeers = LRU(init.maxOldPeersRetention ?? defaultOptions.maxOldPeersRetention) + this.running = false + this._onMessage = this._onMessage.bind(this) + this.systems = new Map() + } + + isStarted () { + return this.running + } + + /** + * Must be called for stats to saved. Any data pushed for tracking + * will be ignored. + */ + async start () { + if (!this.enabled) { + return + } + + this.running = true + } + + /** + * Stops all averages timers and prevents new data from being tracked. + * Once `stop` is called, `start` must be called to resume stats tracking. + */ + async stop () { + if (!this.running) { + return + } + + this.running = false + this.globalStats.stop() + + for (const stats of this.peerStats.values()) { + stats.stop() + } + + for (const stats of this.protocolStats.values()) { + stats.stop() + } + } + + /** + * Gets the global `Stats` object + */ + getGlobal () { + return this.globalStats + } + + /** + * Returns a list of `PeerId` strings currently being tracked + */ + getPeers () { + return Array.from(this.peerStats.keys()) + } + + getComponentMetrics () { + return this.systems + } + + updateComponentMetric (update: ComponentMetricsUpdate) { + const { system = 'libp2p', component, metric, value } = update + + if (!this.systems.has(system)) { + this.systems.set(system, new Map()) + } + + const systemMetrics = this.systems.get(system) + + if (systemMetrics == null) { + throw new Error('Unknown metric system') + } + + if (!systemMetrics.has(component)) { + systemMetrics.set(component, new Map()) + } + + const componentMetrics = systemMetrics.get(component) + + if (componentMetrics == null) { + throw new Error('Unknown metric component') + } + + componentMetrics.set(metric, value) + } + + /** + * Returns the `Stats` object for the given `PeerId` whether it + * is a live peer, or in the disconnected peer LRU cache. + */ + forPeer (peerId: PeerId): Stats | undefined { + const idString = peerId.toString() + return this.peerStats.get(idString) ?? this.oldPeers.get(idString) + } + + /** + * Returns a list of all protocol strings currently being tracked + */ + getProtocols (): string[] { + return Array.from(this.protocolStats.keys()) + } + + /** + * Returns the `Stats` object for the given `protocol` + */ + forProtocol (protocol: string): Stats | undefined { + return this.protocolStats.get(protocol) + } + + /** + * Should be called when all connections to a given peer + * have closed. The `Stats` collection for the peer will + * be stopped and moved to an LRU for temporary retention. + */ + onPeerDisconnected (peerId: PeerId) { + const idString = peerId.toString() + const peerStats = this.peerStats.get(idString) + + if (peerStats != null) { + peerStats.stop() + + this.peerStats.delete(idString) + this.oldPeers.set(idString, peerStats) + } + } + + /** + * Takes the metadata for a message and tracks it in the + * appropriate categories. If the protocol is present, protocol + * stats will also be tracked. + */ + _onMessage (opts: OnMessageOptions) { + if (!this.running) { + return + } + + const { remotePeer, protocol, direction, dataLength } = opts + + const key = directionToEvent[direction] + + let peerStats = this.forPeer(remotePeer) + if (peerStats == null) { + const stats = new DefaultStats(this.statsInit) + this.peerStats.set(remotePeer.toString(), stats) + peerStats = stats + } + + // Peer and global stats + peerStats.push(key, dataLength) + this.globalStats.push(key, dataLength) + + // Protocol specific stats + if (protocol != null) { + let protocolStats = this.forProtocol(protocol) + + if (protocolStats == null) { + const stats = new DefaultStats(this.statsInit) + this.protocolStats.set(protocol, stats) + protocolStats = stats + } + + protocolStats.push(key, dataLength) + } + } + + /** + * Replaces the `PeerId` string with the given `peerId`. + * If stats are already being tracked for the given `peerId`, the + * placeholder stats will be merged with the existing stats. + * + * @param {PeerId} placeholder - A peerId string + * @param {PeerId} peerId + * @returns {void} + */ + updatePlaceholder (placeholder: PeerId, peerId: PeerId) { + if (!this.running) { + return + } + + const placeholderString = placeholder.toString() + const placeholderStats = this.peerStats.get(placeholderString) ?? this.oldPeers.get(placeholderString) + const peerIdString = peerId.toString() + const existingStats = this.peerStats.get(peerIdString) ?? this.oldPeers.get(peerIdString) + let mergedStats = placeholderStats + + // If we already have stats, merge the two + if (existingStats != null) { + // If existing, merge + mergedStats = mergeStats(existingStats, mergedStats) + // Attempt to delete from the old peers list just in case it was tracked there + this.oldPeers.remove(peerIdString) + } + + this.peerStats.delete(placeholder.toString()) + this.peerStats.set(peerIdString, mergedStats) + mergedStats.start() + } + + /** + * Tracks data running through a given Duplex Iterable `stream`. If + * the `peerId` is not provided, a placeholder string will be created and + * returned. This allows lazy tracking of a peer when the peer is not yet known. + * When the `PeerId` is known, `Metrics.updatePlaceholder` should be called + * with the placeholder string returned from here, and the known `PeerId`. + */ + trackStream > (opts: TrackStreamOptions): T { + const { stream, remotePeer, protocol } = opts + + if (!this.running) { + return stream + } + + const source = stream.source + stream.source = each(source, chunk => this._onMessage({ + remotePeer, + protocol, + direction: 'in', + dataLength: chunk.length + })) + + const sink = stream.sink + stream.sink = async source => { + return await pipe( + source, + (source) => each(source, chunk => { + this._onMessage({ + remotePeer, + protocol, + direction: 'out', + dataLength: chunk.length + }) + }), + sink + ) + } + + return stream + } +} + +/** + * Merges `other` into `target`. `target` will be modified + * and returned + */ +function mergeStats (target: DefaultStats, other: DefaultStats) { + target.stop() + other.stop() + + // Merge queues + target.queue = [...target.queue, ...other.queue] + + // TODO: how to merge moving averages? + return target +} diff --git a/src/metrics/moving-average.ts b/src/metrics/moving-average.ts new file mode 100644 index 0000000000..615b62cf24 --- /dev/null +++ b/src/metrics/moving-average.ts @@ -0,0 +1,53 @@ +import type { MovingAverage } from '@libp2p/interfaces/metrics' + +export class DefaultMovingAverage { + public movingAverage: number + public variance: number + public deviation: number + public forecast: number + private readonly timespan: number + private previousTime?: number + + constructor (timespan: number) { + if (typeof timespan !== 'number') { + throw new Error('must provide a timespan to the moving average constructor') + } + + if (timespan <= 0) { + throw new Error('must provide a timespan > 0 to the moving average constructor') + } + + this.timespan = timespan + this.movingAverage = 0 + this.variance = 0 + this.deviation = 0 + this.forecast = 0 + } + + alpha (t: number, pt: number) { + return 1 - (Math.exp(-(t - pt) / this.timespan)) + } + + push (time: number, value: number) { + if (this.previousTime != null) { + // calculate moving average + const a = this.alpha(time, this.previousTime) + const diff = value - this.movingAverage + const incr = a * diff + this.movingAverage = a * value + (1 - a) * this.movingAverage + // calculate variance & deviation + this.variance = (1 - a) * (this.variance + diff * incr) + this.deviation = Math.sqrt(this.variance) + // calculate forecast + this.forecast = this.movingAverage + a * diff + } else { + this.movingAverage = value + } + + this.previousTime = time + } +} + +export function createMovingAverage (timespan: number): MovingAverage { + return new DefaultMovingAverage(timespan) +} diff --git a/src/metrics/old-peers.js b/src/metrics/old-peers.js deleted file mode 100644 index 753bdf5fa1..0000000000 --- a/src/metrics/old-peers.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict' - -const LRU = require('hashlru') - -/** - * Creates and returns a Least Recently Used Cache - * - * @param {number} maxSize - * @returns {any} - */ -module.exports = (maxSize) => { - // @ts-ignore LRU expression is not callable - const patched = LRU(maxSize) - patched.delete = patched.remove - return patched -} diff --git a/src/metrics/stats.js b/src/metrics/stats.js deleted file mode 100644 index 4246588f21..0000000000 --- a/src/metrics/stats.js +++ /dev/null @@ -1,270 +0,0 @@ -// @ts-nocheck -'use strict' - -const { EventEmitter } = require('events') -const { BigNumber: Big } = require('bignumber.js') -const MovingAverage = require('@vascosantos/moving-average') -const retimer = require('retimer') - -/** - * @typedef {import('@vascosantos/moving-average').IMovingAverage} IMovingAverage - * @typedef {import('bignumber.js').BigNumber} Big - */ - -class Stats extends EventEmitter { - /** - * A queue based manager for stat processing - * - * @class - * @param {string[]} initialCounters - * @param {any} options - */ - constructor (initialCounters, options) { - super() - - this._options = options - this._queue = [] - - /** @type {{ dataReceived: Big, dataSent: Big }} */ - this._stats = { - dataReceived: Big(0), - dataSent: Big(0) - } - - this._frequencyLastTime = Date.now() - this._frequencyAccumulators = {} - - /** @type {{ dataReceived: IMovingAverage[], dataSent: IMovingAverage[] }} */ - this._movingAverages = {} - - this._update = this._update.bind(this) - - const intervals = this._options.movingAverageIntervals - - for (let i = 0; i < initialCounters.length; i++) { - const key = initialCounters[i] - this._stats[key] = Big(0) - this._movingAverages[key] = {} - for (let k = 0; k < intervals.length; k++) { - const interval = intervals[k] - const ma = this._movingAverages[key][interval] = MovingAverage(interval) - ma.push(this._frequencyLastTime, 0) - } - } - } - - /** - * Initializes the internal timer if there are items in the queue. This - * should only need to be called if `Stats.stop` was previously called, as - * `Stats.push` will also start the processing. - * - * @returns {void} - */ - start () { - if (this._queue.length) { - this._resetComputeTimeout() - } - } - - /** - * Stops processing and computing of stats by clearing the internal - * timer. - * - * @returns {void} - */ - stop () { - if (this._timeout) { - this._timeout.clear() - this._timeout = null - } - } - - /** - * Returns a clone of the current stats. - */ - get snapshot () { - return Object.assign({}, this._stats) - } - - /** - * Returns a clone of the internal movingAverages - */ - get movingAverages () { - return Object.assign({}, this._movingAverages) - } - - /** - * Returns a plain JSON object of the stats - * - * @returns {*} - */ - toJSON () { - const snapshot = this.snapshot - const movingAverages = this.movingAverages - const data = { - dataReceived: snapshot.dataReceived.toString(), - dataSent: snapshot.dataSent.toString(), - movingAverages: {} - } - - const counters = Object.keys(movingAverages) - for (const key of counters) { - data.movingAverages[key] = {} - for (const interval of Object.keys(movingAverages[key])) { - data.movingAverages[key][interval] = movingAverages[key][interval].movingAverage() - } - } - - return data - } - - /** - * Pushes the given operation data to the queue, along with the - * current Timestamp, then resets the update timer. - * - * @param {string} counter - * @param {number} inc - * @returns {void} - */ - push (counter, inc) { - this._queue.push([counter, inc, Date.now()]) - this._resetComputeTimeout() - } - - /** - * Resets the timeout for triggering updates. - * - * @private - * @returns {void} - */ - _resetComputeTimeout () { - this._timeout = retimer(this._update, this._nextTimeout()) - } - - /** - * Calculates and returns the timeout for the next update based on - * the urgency of the update. - * - * @private - * @returns {number} - */ - _nextTimeout () { - // calculate the need for an update, depending on the queue length - const urgency = this._queue.length / this._options.computeThrottleMaxQueueSize - const timeout = Math.max(this._options.computeThrottleTimeout * (1 - urgency), 0) - return timeout - } - - /** - * If there are items in the queue, they will will be processed and - * the frequency for all items will be updated based on the Timestamp - * of the last item in the queue. The `update` event will also be emitted - * with the latest stats. - * - * If there are no items in the queue, no action is taken. - * - * @private - * @returns {void} - */ - _update () { - this._timeout = null - if (this._queue.length) { - let last - for (last of this._queue) { - this._applyOp(last) - } - this._queue = [] - - this._updateFrequency(last[2]) // contains timestamp of last op - - this.emit('update', this._stats) - } - } - - /** - * For each key in the stats, the frequency and moving averages - * will be updated via Stats._updateFrequencyFor based on the time - * difference between calls to this method. - * - * @private - * @param {Timestamp} latestTime - * @returns {void} - */ - _updateFrequency (latestTime) { - const timeDiff = latestTime - this._frequencyLastTime - - Object.keys(this._stats).forEach((key) => { - this._updateFrequencyFor(key, timeDiff, latestTime) - }) - - this._frequencyLastTime = latestTime - } - - /** - * Updates the `movingAverages` for the given `key` and also - * resets the `frequencyAccumulator` for the `key`. - * - * @private - * @param {string} key - * @param {number} timeDiffMS - Time in milliseconds - * @param {Timestamp} latestTime - Time in ticks - * @returns {void} - */ - _updateFrequencyFor (key, timeDiffMS, latestTime) { - const count = this._frequencyAccumulators[key] || 0 - this._frequencyAccumulators[key] = 0 - // if `timeDiff` is zero, `hz` becomes Infinity, so we fallback to 1ms - const safeTimeDiff = timeDiffMS || 1 - const hz = (count / safeTimeDiff) * 1000 - - let movingAverages = this._movingAverages[key] - if (!movingAverages) { - movingAverages = this._movingAverages[key] = {} - } - - const intervals = this._options.movingAverageIntervals - - for (let i = 0; i < intervals.length; i++) { - const movingAverageInterval = intervals[i] - let movingAverage = movingAverages[movingAverageInterval] - if (!movingAverage) { - movingAverage = movingAverages[movingAverageInterval] = MovingAverage(movingAverageInterval) - } - movingAverage.push(latestTime, hz) - } - } - - /** - * For the given operation, `op`, the stats and `frequencyAccumulator` - * will be updated or initialized if they don't already exist. - * - * @private - * @param {{string, number}[]} op - * @throws {InvalidNumber} - * @returns {void} - */ - _applyOp (op) { - const key = op[0] - const inc = op[1] - - if (typeof inc !== 'number') { - throw new Error(`invalid increment number: ${inc}`) - } - - let n - - if (!Object.prototype.hasOwnProperty.call(this._stats, key)) { - n = this._stats[key] = Big(0) - } else { - n = this._stats[key] - } - this._stats[key] = n.plus(inc) - - if (!this._frequencyAccumulators[key]) { - this._frequencyAccumulators[key] = 0 - } - this._frequencyAccumulators[key] += inc - } -} - -module.exports = Stats diff --git a/src/metrics/stats.ts b/src/metrics/stats.ts new file mode 100644 index 0000000000..56b3d4ca79 --- /dev/null +++ b/src/metrics/stats.ts @@ -0,0 +1,243 @@ +import { CustomEvent, EventEmitter } from '@libp2p/interfaces' +import { createMovingAverage } from './moving-average.js' +// @ts-expect-error no types +import retimer from 'retimer' +import type { MovingAverages, Stats } from '@libp2p/interfaces/metrics' + +export interface StatsEvents { + 'update': CustomEvent +} + +export interface StatsInit { + enabled: boolean + initialCounters: ['dataReceived', 'dataSent'] + movingAverageIntervals: number[] + computeThrottleMaxQueueSize: number + computeThrottleTimeout: number +} + +export interface TransferStats { + dataReceived: BigInt + dataSent: BigInt +} + +export class DefaultStats extends EventEmitter implements Stats { + private readonly enabled: boolean + public queue: Array<[string, number, number]> + private stats: TransferStats + private frequencyLastTime: number + private frequencyAccumulators: Record + private movingAverages: MovingAverages + private timeout?: any + private readonly computeThrottleMaxQueueSize: number + private readonly computeThrottleTimeout: number + private readonly movingAverageIntervals: number[] + + /** + * A queue based manager for stat processing + */ + constructor (init: StatsInit) { + super() + + this.enabled = init.enabled + this.queue = [] + this.stats = { + dataReceived: 0n, + dataSent: 0n + } + this.frequencyLastTime = Date.now() + this.frequencyAccumulators = {} + this.movingAverages = { + dataReceived: [], + dataSent: [] + } + this.computeThrottleMaxQueueSize = init.computeThrottleMaxQueueSize + this.computeThrottleTimeout = init.computeThrottleTimeout + + this._update = this._update.bind(this) + + this.movingAverageIntervals = init.movingAverageIntervals + + for (let i = 0; i < init.initialCounters.length; i++) { + const key = init.initialCounters[i] + this.stats[key] = 0n + this.movingAverages[key] = [] + + for (let k = 0; k < this.movingAverageIntervals.length; k++) { + const interval = this.movingAverageIntervals[k] + const ma = this.movingAverages[key][interval] = createMovingAverage(interval) + ma.push(this.frequencyLastTime, 0) + } + } + } + + /** + * Initializes the internal timer if there are items in the queue. This + * should only need to be called if `Stats.stop` was previously called, as + * `Stats.push` will also start the processing + */ + start () { + if (!this.enabled) { + return + } + + if (this.queue.length > 0) { + this._resetComputeTimeout() + } + } + + /** + * Stops processing and computing of stats by clearing the internal + * timer + */ + stop () { + if (this.timeout != null) { + this.timeout.clear() + this.timeout = null + } + } + + /** + * Returns a clone of the current stats. + */ + getSnapshot () { + return Object.assign({}, this.stats) + } + + /** + * Returns a clone of the internal movingAverages + */ + getMovingAverages (): MovingAverages { + return Object.assign({}, this.movingAverages) + } + + /** + * Pushes the given operation data to the queue, along with the + * current Timestamp, then resets the update timer. + */ + push (counter: string, inc: number) { + this.queue.push([counter, inc, Date.now()]) + this._resetComputeTimeout() + } + + /** + * Resets the timeout for triggering updates. + */ + _resetComputeTimeout () { + this.timeout = retimer(this._update, this._nextTimeout()) + } + + /** + * Calculates and returns the timeout for the next update based on + * the urgency of the update. + */ + _nextTimeout () { + // calculate the need for an update, depending on the queue length + const urgency = this.queue.length / this.computeThrottleMaxQueueSize + const timeout = Math.max(this.computeThrottleTimeout * (1 - urgency), 0) + return timeout + } + + /** + * If there are items in the queue, they will will be processed and + * the frequency for all items will be updated based on the Timestamp + * of the last item in the queue. The `update` event will also be emitted + * with the latest stats. + * + * If there are no items in the queue, no action is taken. + */ + _update () { + this.timeout = null + if (this.queue.length > 0) { + let last: [string, number, number] = ['', 0, 0] + + for (last of this.queue) { + this._applyOp(last) + } + + this.queue = [] + + if (last.length > 2 && last[0] !== '') { + this._updateFrequency(last[2]) // contains timestamp of last op + } + + this.dispatchEvent(new CustomEvent('update', { + detail: this.stats + })) + } + } + + /** + * For each key in the stats, the frequency and moving averages + * will be updated via Stats._updateFrequencyFor based on the time + * difference between calls to this method. + */ + _updateFrequency (latestTime: number) { + const timeDiff = latestTime - this.frequencyLastTime + + this._updateFrequencyFor('dataReceived', timeDiff, latestTime) + this._updateFrequencyFor('dataSent', timeDiff, latestTime) + + this.frequencyLastTime = latestTime + } + + /** + * Updates the `movingAverages` for the given `key` and also + * resets the `frequencyAccumulator` for the `key`. + */ + _updateFrequencyFor (key: 'dataReceived' | 'dataSent', timeDiffMS: number, latestTime: number) { + const count = this.frequencyAccumulators[key] ?? 0 + this.frequencyAccumulators[key] = 0 + // if `timeDiff` is zero, `hz` becomes Infinity, so we fallback to 1ms + const safeTimeDiff = timeDiffMS ?? 1 + const hz = (count / safeTimeDiff) * 1000 + + let movingAverages = this.movingAverages[key] + if (movingAverages == null) { + movingAverages = this.movingAverages[key] = [] + } + + const intervals = this.movingAverageIntervals + + for (let i = 0; i < intervals.length; i++) { + const movingAverageInterval = intervals[i] + let movingAverage = movingAverages[movingAverageInterval] + if (movingAverage == null) { + movingAverage = movingAverages[movingAverageInterval] = createMovingAverage(movingAverageInterval) + } + movingAverage.push(latestTime, hz) + } + } + + /** + * For the given operation, `op`, the stats and `frequencyAccumulator` + * will be updated or initialized if they don't already exist. + */ + _applyOp (op: [string, number, number]) { + const key = op[0] + const inc = op[1] + + if (typeof inc !== 'number') { + throw new Error('invalid increment number') + } + + let n: bigint + + if (!Object.prototype.hasOwnProperty.call(this.stats, key)) { + // @ts-expect-error cannot index type with key + n = this.stats[key] = 0n + } else { + // @ts-expect-error cannot index type with key + n = this.stats[key] + } + + // @ts-expect-error cannot index type with key + this.stats[key] = n + BigInt(inc) + + if (this.frequencyAccumulators[key] == null) { + this.frequencyAccumulators[key] = 0 + } + + this.frequencyAccumulators[key] += inc + } +} diff --git a/src/metrics/tracked-map.js b/src/metrics/tracked-map.js deleted file mode 100644 index beb0fa7280..0000000000 --- a/src/metrics/tracked-map.js +++ /dev/null @@ -1,94 +0,0 @@ -'use strict' - -/** - * @template K - * @template V - */ -class TrackedMap extends Map { - /** - * @param {object} options - * @param {string} options.system - * @param {string} options.component - * @param {string} options.metric - * @param {import('.')} options.metrics - */ - constructor (options) { - super() - - const { system, component, metric, metrics } = options - this._system = system - this._component = component - this._metric = metric - this._metrics = metrics - - this._metrics.updateComponentMetric({ - system: this._system, - component: this._component, - metric: this._metric, - value: this.size - }) - } - - /** - * @param {K} key - * @param {V} value - */ - set (key, value) { - super.set(key, value) - this._metrics.updateComponentMetric({ - system: this._system, - component: this._component, - metric: this._metric, - value: this.size - }) - return this - } - - /** - * @param {K} key - */ - delete (key) { - const deleted = super.delete(key) - this._metrics.updateComponentMetric({ - system: this._system, - component: this._component, - metric: this._metric, - value: this.size - }) - return deleted - } - - clear () { - super.clear() - - this._metrics.updateComponentMetric({ - system: this._system, - component: this._component, - metric: this._metric, - value: this.size - }) - } -} - -/** - * @template K - * @template V - * @param {object} options - * @param {string} [options.system] - * @param {string} options.component - * @param {string} options.metric - * @param {import('.')} [options.metrics] - * @returns {Map} - */ -module.exports = ({ system = 'libp2p', component, metric, metrics }) => { - /** @type {Map} */ - let map - - if (metrics) { - map = new TrackedMap({ system, component, metric, metrics }) - } else { - map = new Map() - } - - return map -} diff --git a/src/nat-manager.js b/src/nat-manager.js deleted file mode 100644 index cd8bf5aaf5..0000000000 --- a/src/nat-manager.js +++ /dev/null @@ -1,197 +0,0 @@ -'use strict' - -// @ts-ignore nat-api does not export types -const NatAPI = require('nat-api') -const debug = require('debug') -const { promisify } = require('es6-promisify') -const { Multiaddr } = require('multiaddr') -const log = Object.assign(debug('libp2p:nat'), { - error: debug('libp2p:nat:err') -}) -const { isBrowser } = require('wherearewe') -const retry = require('p-retry') -const isPrivateIp = require('private-ip') -const pkg = require('../package.json') -const errcode = require('err-code') -const { - codes: { ERR_INVALID_PARAMETERS } -} = require('./errors') -const isLoopback = require('libp2p-utils/src/multiaddr/is-loopback') - -const DEFAULT_TTL = 7200 - -/** - * @typedef {import('peer-id')} PeerId - * @typedef {import('./transport-manager')} TransportManager - * @typedef {import('./address-manager')} AddressManager - */ - -/** - * @typedef {Object} NatManagerProperties - * @property {PeerId} peerId - The peer ID of the current node - * @property {TransportManager} transportManager - A transport manager - * @property {AddressManager} addressManager - An address manager - * - * @typedef {Object} NatManagerOptions - * @property {boolean} enabled - Whether to enable the NAT manager - * @property {string} [externalIp] - Pass a value to use instead of auto-detection - * @property {string} [description] - A string value to use for the port mapping description on the gateway - * @property {number} [ttl = DEFAULT_TTL] - How long UPnP port mappings should last for in seconds (minimum 1200) - * @property {boolean} [keepAlive] - Whether to automatically refresh UPnP port mappings when their TTL is reached - * @property {string} [gateway] - Pass a value to use instead of auto-detection - * @property {object} [pmp] - PMP options - * @property {boolean} [pmp.enabled] - Whether to enable PMP as well as UPnP - */ - -function highPort (min = 1024, max = 65535) { - return Math.floor(Math.random() * (max - min + 1) + min) -} - -class NatManager { - /** - * @class - * @param {NatManagerProperties & NatManagerOptions} options - */ - constructor ({ peerId, addressManager, transportManager, ...options }) { - this._peerId = peerId - this._addressManager = addressManager - this._transportManager = transportManager - - this._enabled = options.enabled - this._externalIp = options.externalIp - this._options = { - description: options.description || `${pkg.name}@${pkg.version} ${this._peerId}`, - ttl: options.ttl || DEFAULT_TTL, - autoUpdate: options.keepAlive || true, - gateway: options.gateway, - enablePMP: Boolean(options.pmp && options.pmp.enabled) - } - - if (this._options.ttl < DEFAULT_TTL) { - throw errcode(new Error(`NatManager ttl should be at least ${DEFAULT_TTL} seconds`), ERR_INVALID_PARAMETERS) - } - } - - /** - * Starts the NAT manager - */ - start () { - if (isBrowser || !this._enabled) { - return - } - - // done async to not slow down startup - this._start().catch((err) => { - // hole punching errors are non-fatal - log.error(err) - }) - } - - async _start () { - const addrs = this._transportManager.getAddrs() - - for (const addr of addrs) { - // try to open uPnP ports for each thin waist address - const { family, host, port, transport } = addr.toOptions() - - if (!addr.isThinWaistAddress() || transport !== 'tcp') { - // only bare tcp addresses - // eslint-disable-next-line no-continue - continue - } - - if (isLoopback(addr)) { - // eslint-disable-next-line no-continue - continue - } - - if (family !== 4) { - // ignore ipv6 - // eslint-disable-next-line no-continue - continue - } - - const client = this._getClient() - const publicIp = this._externalIp || await client.externalIp() - - // @ts-expect-error types are wrong - if (isPrivateIp(publicIp)) { - throw new Error(`${publicIp} is private - please set config.nat.externalIp to an externally routable IP or ensure you are not behind a double NAT`) - } - - const publicPort = highPort() - - log(`opening uPnP connection from ${publicIp}:${publicPort} to ${host}:${port}`) - - await client.map({ - publicPort, - privatePort: port, - protocol: transport.toUpperCase() - }) - - this._addressManager.addObservedAddr(Multiaddr.fromNodeAddress({ - family: 4, - address: publicIp, - port: publicPort - }, transport)) - } - } - - _getClient () { - if (this._client) { - return this._client - } - - const client = new NatAPI(this._options) - - /** @type {(...any: any) => any} */ - const map = promisify(client.map.bind(client)) - /** @type {(...any: any) => any} */ - const destroy = promisify(client.destroy.bind(client)) - /** @type {(...any: any) => any} */ - const externalIp = promisify(client.externalIp.bind(client)) - - // these are all network operations so add a retry - this._client = { - /** - * @param {...any} args - * @returns {Promise} - */ - map: (...args) => retry(() => map(...args), { onFailedAttempt: log.error, unref: true }), - - /** - * @param {...any} args - * @returns {Promise} - */ - destroy: (...args) => retry(() => destroy(...args), { onFailedAttempt: log.error, unref: true }), - - /** - * @param {...any} args - * @returns {Promise} - */ - externalIp: (...args) => retry(() => externalIp(...args), { onFailedAttempt: log.error, unref: true }) - } - - return this._client - } - - /** - * Stops the NAT manager - * - * @async - */ - async stop () { - if (isBrowser || !this._client) { - return - } - - try { - await this._client.destroy() - this._client = null - } catch (/** @type {any} */ err) { - log.error(err) - } - } -} - -module.exports = NatManager diff --git a/src/nat-manager.ts b/src/nat-manager.ts new file mode 100644 index 0000000000..978efa647b --- /dev/null +++ b/src/nat-manager.ts @@ -0,0 +1,194 @@ +import { upnpNat, NatAPI } from '@achingbrain/nat-port-mapper' +import { logger } from '@libp2p/logger' +import { Multiaddr } from '@multiformats/multiaddr' +import { isBrowser } from 'wherearewe' +import isPrivateIp from 'private-ip' +import * as pkg from './version.js' +import errCode from 'err-code' +import { codes } from './errors.js' +import { isLoopback } from '@libp2p/utils/multiaddr/is-loopback' +import type { Startable } from '@libp2p/interfaces' +import type { Components } from '@libp2p/interfaces/components' + +const log = logger('libp2p:nat') +const DEFAULT_TTL = 7200 + +function highPort (min = 1024, max = 65535) { + return Math.floor(Math.random() * (max - min + 1) + min) +} + +export interface PMPOptions { + /** + * Whether to enable PMP as well as UPnP + */ + enabled?: boolean +} + +export interface NatManagerInit { + /** + * Whether to enable the NAT manager + */ + enabled: boolean + + /** + * Pass a value to use instead of auto-detection + */ + externalAddress?: string + + /** + * Pass a value to use instead of auto-detection + */ + localAddress?: string + + /** + * A string value to use for the port mapping description on the gateway + */ + description?: string + + /** + * How long UPnP port mappings should last for in seconds (minimum 1200) + */ + ttl?: number + + /** + * Whether to automatically refresh UPnP port mappings when their TTL is reached + */ + keepAlive: boolean + + /** + * Pass a value to use instead of auto-detection + */ + gateway?: string +} + +export class NatManager implements Startable { + private readonly components: Components + private readonly enabled: boolean + private readonly externalAddress?: string + private readonly localAddress?: string + private readonly description: string + private readonly ttl: number + private readonly keepAlive: boolean + private readonly gateway?: string + private started: boolean + private client?: NatAPI + + constructor (components: Components, init: NatManagerInit) { + this.components = components + + this.started = false + this.enabled = init.enabled + this.externalAddress = init.externalAddress + this.localAddress = init.localAddress + this.description = init.description ?? `${pkg.name}@${pkg.version} ${this.components.getPeerId().toString()}` + this.ttl = init.ttl ?? DEFAULT_TTL + this.keepAlive = init.keepAlive ?? true + this.gateway = init.gateway + + if (this.ttl < DEFAULT_TTL) { + throw errCode(new Error(`NatManager ttl should be at least ${DEFAULT_TTL} seconds`), codes.ERR_INVALID_PARAMETERS) + } + } + + isStarted () { + return this.started + } + + /** + * Starts the NAT manager + */ + start () { + if (isBrowser || !this.enabled || this.started) { + return + } + + this.started = true + + // done async to not slow down startup + this._start().catch((err) => { + // hole punching errors are non-fatal + log.error(err) + }) + } + + async _start () { + const addrs = this.components.getTransportManager().getAddrs() + + for (const addr of addrs) { + // try to open uPnP ports for each thin waist address + const { family, host, port, transport } = addr.toOptions() + + if (!addr.isThinWaistAddress() || transport !== 'tcp') { + // only bare tcp addresses + // eslint-disable-next-line no-continue + continue + } + + if (isLoopback(addr)) { + // eslint-disable-next-line no-continue + continue + } + + if (family !== 4) { + // ignore ipv6 + // eslint-disable-next-line no-continue + continue + } + + const client = await this._getClient() + const publicIp = this.externalAddress ?? await client.externalIp() + + if (isPrivateIp(publicIp)) { + throw new Error(`${publicIp} is private - please set config.nat.externalIp to an externally routable IP or ensure you are not behind a double NAT`) + } + + const publicPort = highPort() + + log(`opening uPnP connection from ${publicIp}:${publicPort} to ${host}:${port}`) + + await client.map({ + publicPort, + localPort: port, + localAddress: this.localAddress, + protocol: transport.toUpperCase() === 'TCP' ? 'TCP' : 'UDP' + }) + + this.components.getAddressManager().addObservedAddr(Multiaddr.fromNodeAddress({ + family: 4, + address: publicIp, + port: publicPort + }, transport)) + } + } + + async _getClient () { + if (this.client != null) { + return this.client + } + + this.client = await upnpNat({ + description: this.description, + ttl: this.ttl, + keepAlive: this.keepAlive, + gateway: this.gateway + }) + + return this.client + } + + /** + * Stops the NAT manager + */ + async stop () { + if (isBrowser || this.client == null) { + return + } + + try { + await this.client.close() + this.client = undefined + } catch (err: any) { + log.error(err) + } + } +} diff --git a/src/peer-record-updater.ts b/src/peer-record-updater.ts new file mode 100644 index 0000000000..340508a675 --- /dev/null +++ b/src/peer-record-updater.ts @@ -0,0 +1,55 @@ +import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record' +import type { Components } from '@libp2p/interfaces/components' +import type { Startable } from '@libp2p/interfaces' +import { logger } from '@libp2p/logger' +import { protocols } from '@multiformats/multiaddr' + +const log = logger('libp2p:peer-record-updater') + +export class PeerRecordUpdater implements Startable { + private readonly components: Components + private started: boolean + + constructor (components: Components) { + this.components = components + this.started = false + this.update = this.update.bind(this) + } + + isStarted () { + return this.started + } + + async start () { + this.started = true + this.components.getTransportManager().addEventListener('listener:listening', this.update) + this.components.getTransportManager().addEventListener('listener:close', this.update) + this.components.getAddressManager().addEventListener('change:addresses', this.update) + } + + async stop () { + this.started = false + this.components.getTransportManager().removeEventListener('listener:listening', this.update) + this.components.getTransportManager().removeEventListener('listener:close', this.update) + this.components.getAddressManager().removeEventListener('change:addresses', this.update) + } + + /** + * Create (or update if existing) self peer record and store it in the AddressBook. + */ + update () { + Promise.resolve() + .then(async () => { + const peerRecord = new PeerRecord({ + peerId: this.components.getPeerId(), + multiaddrs: this.components.getAddressManager().getAddresses().map(ma => ma.decapsulateCode(protocols('p2p').code)) + }) + + const envelope = await RecordEnvelope.seal(peerRecord, this.components.getPeerId()) + await this.components.getPeerStore().addressBook.consumePeerRecord(envelope) + }) + .catch(err => { + log.error('Could not update self peer record: %o', err) + }) + } +} diff --git a/src/peer-routing.js b/src/peer-routing.js deleted file mode 100644 index b525920f9a..0000000000 --- a/src/peer-routing.js +++ /dev/null @@ -1,176 +0,0 @@ -'use strict' - -const debug = require('debug') -const log = Object.assign(debug('libp2p:peer-routing'), { - error: debug('libp2p:peer-routing:err') -}) -const errCode = require('err-code') -const errors = require('./errors') -const { - storeAddresses, - uniquePeers, - requirePeers -} = require('./content-routing/utils') -const { TimeoutController } = require('timeout-abort-controller') - -const merge = require('it-merge') -const { pipe } = require('it-pipe') -const first = require('it-first') -const drain = require('it-drain') -const filter = require('it-filter') -const { - setDelayedInterval, - clearDelayedInterval -// @ts-ignore module with no types -} = require('set-delayed-interval') -const { DHTPeerRouting } = require('./dht/dht-peer-routing') -// @ts-expect-error setMaxListeners is missing from the types -const { setMaxListeners } = require('events') - -/** - * @typedef {import('peer-id')} PeerId - * @typedef {import('multiaddr').Multiaddr} Multiaddr - * @typedef {import('libp2p-interfaces/src/peer-routing/types').PeerRouting} PeerRoutingModule - */ - -/** - * @typedef {Object} RefreshManagerOptions - * @property {boolean} [enabled = true] - Whether to enable the Refresh manager - * @property {number} [bootDelay = 6e5] - Boot delay to start the Refresh Manager (in ms) - * @property {number} [interval = 10e3] - Interval between each Refresh Manager run (in ms) - * @property {number} [timeout = 10e3] - How long to let each refresh run (in ms) - * - * @typedef {Object} PeerRoutingOptions - * @property {RefreshManagerOptions} [refreshManager] - */ - -class PeerRouting { - /** - * @class - * @param {import('./')} libp2p - */ - constructor (libp2p) { - this._peerId = libp2p.peerId - this._peerStore = libp2p.peerStore - /** @type {PeerRoutingModule[]} */ - this._routers = libp2p._modules.peerRouting || [] - - // If we have the dht, add it to the available peer routers - if (libp2p._dht && libp2p._config.dht.enabled) { - this._routers.push(new DHTPeerRouting(libp2p._dht)) - } - - this._refreshManagerOptions = libp2p._options.peerRouting.refreshManager - - this._findClosestPeersTask = this._findClosestPeersTask.bind(this) - } - - /** - * Start peer routing service. - */ - start () { - if (!this._routers.length || this._timeoutId || !this._refreshManagerOptions.enabled) { - return - } - - this._timeoutId = setDelayedInterval( - this._findClosestPeersTask, this._refreshManagerOptions.interval, this._refreshManagerOptions.bootDelay - ) - } - - /** - * Recurrent task to find closest peers and add their addresses to the Address Book. - */ - async _findClosestPeersTask () { - try { - // nb getClosestPeers adds the addresses to the address book - await drain(this.getClosestPeers(this._peerId.id, { timeout: this._refreshManagerOptions.timeout || 10e3 })) - } catch (/** @type {any} */ err) { - log.error(err) - } - } - - /** - * Stop peer routing service. - */ - stop () { - clearDelayedInterval(this._timeoutId) - } - - /** - * Iterates over all peer routers in parallel to find the given peer. - * - * @param {PeerId} id - The id of the peer to find - * @param {object} [options] - * @param {number} [options.timeout] - How long the query should run - * @returns {Promise<{ id: PeerId, multiaddrs: Multiaddr[] }>} - */ - async findPeer (id, options) { // eslint-disable-line require-await - if (!this._routers.length) { - throw errCode(new Error('No peer routers available'), errors.codes.ERR_NO_ROUTERS_AVAILABLE) - } - - if (id.toB58String() === this._peerId.toB58String()) { - throw errCode(new Error('Should not try to find self'), errors.codes.ERR_FIND_SELF) - } - - const output = await pipe( - merge( - ...this._routers.map(router => (async function * () { - try { - yield await router.findPeer(id, options) - } catch (err) { - log.error(err) - } - })()) - ), - (source) => filter(source, Boolean), - (source) => storeAddresses(source, this._peerStore), - (source) => first(source) - ) - - if (output) { - return output - } - - throw errCode(new Error(errors.messages.NOT_FOUND), errors.codes.ERR_NOT_FOUND) - } - - /** - * Attempt to find the closest peers on the network to the given key. - * - * @param {Uint8Array} key - A CID like key - * @param {Object} [options] - * @param {number} [options.timeout=30e3] - How long the query can take - * @param {AbortSignal} [options.signal] - An AbortSignal to abort the request - * @returns {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} - */ - async * getClosestPeers (key, options = { timeout: 30e3 }) { - if (!this._routers.length) { - throw errCode(new Error('No peer routers available'), errors.codes.ERR_NO_ROUTERS_AVAILABLE) - } - - if (options.timeout) { - const controller = new TimeoutController(options.timeout) - // this controller will potentially be used while dialing lots of - // peers so prevent MaxListenersExceededWarning appearing in the console - try { - // fails on node < 15.4 - setMaxListeners && setMaxListeners(Infinity, controller.signal) - } catch {} - - options.signal = controller.signal - } - - yield * pipe( - merge( - ...this._routers.map(router => router.getClosestPeers(key, options)) - ), - (source) => storeAddresses(source, this._peerStore), - (source) => uniquePeers(source), - (source) => requirePeers(source) - ) - } -} - -module.exports = PeerRouting diff --git a/src/peer-routing.ts b/src/peer-routing.ts new file mode 100644 index 0000000000..4e95ff5cca --- /dev/null +++ b/src/peer-routing.ts @@ -0,0 +1,185 @@ +import { logger } from '@libp2p/logger' +import errCode from 'err-code' +import { codes, messages } from './errors.js' +import { + storeAddresses, + uniquePeers, + requirePeers +} from './content-routing/utils.js' +import { TimeoutController } from 'timeout-abort-controller' +import merge from 'it-merge' +import { pipe } from 'it-pipe' +import first from 'it-first' +import drain from 'it-drain' +import filter from 'it-filter' +import { + setDelayedInterval, + clearDelayedInterval +// @ts-expect-error module with no types +} from 'set-delayed-interval' +// @ts-expect-error setMaxListeners is missing from the node 16 types +import { setMaxListeners } from 'events' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerRouting } from '@libp2p/interfaces/peer-routing' +import type { AbortOptions, Startable } from '@libp2p/interfaces' +import type { PeerInfo } from '@libp2p/interfaces/peer-info' +import type { Components } from '@libp2p/interfaces/components' + +const log = logger('libp2p:peer-routing') + +export interface RefreshManagerInit { + /** + * Whether to enable the Refresh manager + */ + enabled?: boolean + + /** + * Boot delay to start the Refresh Manager (in ms) + */ + bootDelay?: number + + /** + * Interval between each Refresh Manager run (in ms) + */ + interval?: number + + /** + * How long to let each refresh run (in ms) + */ + timeout?: number +} + +export interface PeerRoutingInit { + routers: PeerRouting[] + refreshManager?: RefreshManagerInit +} + +export class DefaultPeerRouting implements PeerRouting, Startable { + private readonly components: Components + private readonly routers: PeerRouting[] + private readonly refreshManagerInit: RefreshManagerInit + private timeoutId?: ReturnType + private started: boolean + private abortController?: TimeoutController + + constructor (components: Components, init: PeerRoutingInit) { + this.components = components + this.routers = init.routers + this.refreshManagerInit = init.refreshManager ?? {} + this.started = false + + this._findClosestPeersTask = this._findClosestPeersTask.bind(this) + } + + isStarted () { + return this.started + } + + /** + * Start peer routing service. + */ + async start () { + if (this.started || this.routers.length === 0 || this.timeoutId != null || this.refreshManagerInit.enabled === false) { + return + } + + this.timeoutId = setDelayedInterval( + this._findClosestPeersTask, this.refreshManagerInit.interval, this.refreshManagerInit.bootDelay + ) + + this.started = true + } + + /** + * Recurrent task to find closest peers and add their addresses to the Address Book. + */ + async _findClosestPeersTask () { + if (this.abortController != null) { + // we are already running the query + return + } + + try { + this.abortController = new TimeoutController(this.refreshManagerInit.timeout ?? 10e3) + + // this controller may be used while dialing lots of peers so prevent MaxListenersExceededWarning + // appearing in the console + try { + // fails on node < 15.4 + setMaxListeners?.(Infinity, this.abortController.signal) + } catch {} + + // nb getClosestPeers adds the addresses to the address book + await drain(this.getClosestPeers(this.components.getPeerId().toBytes(), { signal: this.abortController.signal })) + } catch (err: any) { + log.error(err) + } finally { + this.abortController?.clear() + this.abortController = undefined + } + } + + /** + * Stop peer routing service. + */ + async stop () { + clearDelayedInterval(this.timeoutId) + + // abort query if it is in-flight + this.abortController?.abort() + + this.started = false + } + + /** + * Iterates over all peer routers in parallel to find the given peer + */ + async findPeer (id: PeerId, options?: AbortOptions): Promise { + if (this.routers.length === 0) { + throw errCode(new Error('No peer routers available'), codes.ERR_NO_ROUTERS_AVAILABLE) + } + + if (id.toString() === this.components.getPeerId().toString()) { + throw errCode(new Error('Should not try to find self'), codes.ERR_FIND_SELF) + } + + const output = await pipe( + merge( + ...this.routers.map(router => (async function * () { + try { + yield await router.findPeer(id, options) + } catch (err) { + log.error(err) + } + })()) + ), + (source) => filter(source, Boolean), + (source) => storeAddresses(source, this.components.getPeerStore()), + async (source) => await first(source) + ) + + if (output != null) { + return output + } + + throw errCode(new Error(messages.NOT_FOUND), codes.ERR_NOT_FOUND) + } + + /** + * Attempt to find the closest peers on the network to the given key + */ + async * getClosestPeers (key: Uint8Array, options?: AbortOptions): AsyncIterable { + if (this.routers.length === 0) { + throw errCode(new Error('No peer routers available'), codes.ERR_NO_ROUTERS_AVAILABLE) + } + + yield * pipe( + merge( + ...this.routers.map(router => router.getClosestPeers(key, options)) + ), + (source) => storeAddresses(source, this.components.getPeerStore()), + (source) => uniquePeers(source), + (source) => requirePeers(source) + ) + } +} diff --git a/src/peer-store/README.md b/src/peer-store/README.md deleted file mode 100644 index b95cdaf0c8..0000000000 --- a/src/peer-store/README.md +++ /dev/null @@ -1,145 +0,0 @@ -# PeerStore - -Libp2p's PeerStore is responsible for keeping an updated register with the relevant information of the known peers. It should be the single source of truth for all peer data, where a subsystem can learn about peers' data and where someone can listen for updates. The PeerStore comprises four main components: `addressBook`, `keyBook`, `protocolBook` and `metadataBook`. - -The PeerStore manages the high level operations on its inner books. Moreover, the PeerStore should be responsible for notifying interested parties of relevant events, through its Event Emitter. - -## Submitting records to the PeerStore - -Several libp2p subsystems will perform operations that might gather relevant information about peers. - -### Identify -- The Identify protocol automatically runs on every connection when multiplexing is enabled. The protocol will put the multiaddrs and protocols provided by the peer to the PeerStore. -- In the background, the Identify Service is also waiting for protocol change notifications of peers via the IdentifyPush protocol. Peers may leverage the `identify-push` message to communicate protocol changes to all connected peers, so that their PeerStore can be updated with the updated protocols. -- While it is currently not supported in js-libp2p, future iterations may also support the [IdentifyDelta protocol](https://github.com/libp2p/specs/pull/176). -- Taking into account that the Identify protocol records are directly from the peer, they should be considered the source of truth and weighted accordingly. - -### Peer Discovery -- Libp2p discovery protocols aim to discover new peers in the network. In a typical discovery protocol, addresses of the peer are discovered along with its peer id. Once this happens, a libp2p discovery protocol should emit a `peer` event with the information of the discovered peer and this information will be added to the PeerStore by libp2p. - -### Dialer -- Libp2p API supports dialing a peer given a `multiaddr`, and no prior knowledge of the peer. If the node is able to establish a connection with the peer, it and its multiaddr is added to the PeerStore. -- When a connection is being upgraded, more precisely after its encryption, or even in a discovery protocol, a libp2p node can get to know other parties public keys. In this scenario, libp2p will add the peer's public key to its `KeyBook`. - -### DHT -- On some DHT operations, such as finding providers for a given CID, nodes may exchange peer data as part of the query. This passive peer discovery should result in the DHT emitting the `peer` event in the same way [Peer Discovery](#peerdiscovery) does. - -## Retrieving records from the PeerStore - -When data in the PeerStore is updated the PeerStore will emit events based on the changes, to allow applications and other subsystems to take action on those changes. Any subsystem interested in these notifications should subscribe the [`PeerStore events`][peer-store-events]. - -### Peer -- Each time a new peer is discovered, the PeerStore should emit a [`peer` event][peer-store-events], so that interested parties can leverage this peer and establish a connection with it. - -### Protocols -- When the known protocols of a peer change, the PeerStore emits a [`change:protocols` event][peer-store-events]. - -### Multiaddrs -- When the known listening `multiaddrs` of a peer change, the PeerStore emits a [`change:multiaddrs` event][peer-store-events]. - -## PeerStore implementation - -The PeerStore wraps four main components: `addressBook`, `keyBook`, `protocolBook` and `metadataBook`. Moreover, it provides a high level API for those components, as well as data events. - -### Components - -#### Address Book - -The `addressBook` keeps the known multiaddrs of a peer. The multiaddrs of each peer may change over time and the Address Book must account for this. - -`Map` - -A `peerId.toB58String()` identifier mapping to a `Address` object, which should have the following structure: - -```js -{ - multiaddr: -} -``` - -#### Key Book - -The `keyBook` tracks the public keys of the peers by keeping their [`PeerId`][peer-id]. - -`Map>` - -A `peerId.toB58String()` identifier mapping to a `Set` of protocol identifier strings. - -#### Metadata Book - -The `metadataBook` keeps track of the known metadata of a peer. Its metadata is stored in a key value fashion, where a key identifier (`string`) represents a metadata value (`Uint8Array`). - -`Map>` - -A `peerId.toB58String()` identifier mapping to the peer metadata Map. - -### API - -For the complete API documentation, you should check the [API.md](../../doc/API.md). - -Access to its underlying books: - -- `peerStore.addressBook.*` -- `peerStore.keyBook.*` -- `peerStore.metadataBook.*` -- `peerStore.protoBook.*` - -### Events - -- `peer` - emitted when a new peer is added. -- `change:multiaadrs` - emitted when a known peer has a different set of multiaddrs. -- `change:protocols` - emitted when a known peer supports a different set of protocols. -- `change:pubkey` - emitted when a peer's public key is known. -- `change:metadata` - emitted when known metadata of a peer changes. - -## Data Persistence - -The data stored in the PeerStore can be persisted if configured appropriately. Keeping a record of the peers already discovered by the peer, as well as their known data aims to improve the efficiency of peers joining the network after being offline. - -The libp2p node will need to receive a [datastore](https://github.com/ipfs/interface-datastore), in order to persist this data across restarts. A [datastore](https://github.com/ipfs/interface-datastore) stores its data in a key-value fashion. As a result, we need coherent keys so that we do not overwrite data. - -The PeerStore should not continuously update the datastore whenever data is changed. Instead, it should only store new data after reaching a certain threshold of "dirty" peers, as well as when the node is stopped, in order to batch writes to the datastore. - -The peer id will be appended to the datastore key for each data namespace. The namespaces were defined as follows: - -**AddressBook** - -All the known peer addresses are stored with a key pattern as follows: - -`/peers/addrs/` - -**ProtoBook** - -All the known peer protocols are stored with a key pattern as follows: - -`/peers/protos/` - -**KeyBook** - -All public keys are stored under the following pattern: - -` /peers/keys/` - -**MetadataBook** - -Metadata is stored under the following key pattern: - -`/peers/metadata//` - -## Future Considerations - -- If multiaddr TTLs are added, the PeerStore may schedule jobs to delete all addresses that exceed the TTL to prevent AddressBook bloating -- Further API methods will probably need to be added in the context of multiaddr validity and confidence. -- When improving libp2p configuration for specific runtimes, we should take into account the PeerStore recommended datastore. -- When improving libp2p configuration, we should think about a possible way of allowing the configuration of Bootstrap to be influenced by the persisted peers, as a way to decrease the load on Bootstrap nodes. - -[peer-id]: https://github.com/libp2p/js-peer-id -[peer-store-events]: ../../doc/API.md#libp2ppeerstore diff --git a/src/peer-store/address-book.js b/src/peer-store/address-book.js deleted file mode 100644 index 391f960888..0000000000 --- a/src/peer-store/address-book.js +++ /dev/null @@ -1,382 +0,0 @@ -'use strict' - -const debug = require('debug') -const errcode = require('err-code') -const { Multiaddr } = require('multiaddr') -const PeerId = require('peer-id') -const { codes } = require('../errors') -const PeerRecord = require('../record/peer-record') -const Envelope = require('../record/envelope') -const { pipe } = require('it-pipe') -const all = require('it-all') -const filter = require('it-filter') -const map = require('it-map') -const each = require('it-foreach') - -/** - * @typedef {import('./types').PeerStore} PeerStore - * @typedef {import('./types').Address} Address - * @typedef {import('./types').AddressBook} AddressBook - */ - -const log = Object.assign(debug('libp2p:peer-store:address-book'), { - error: debug('libp2p:peer-store:address-book:err') -}) - -const EVENT_NAME = 'change:multiaddrs' - -/** - * @implements {AddressBook} - */ -class PeerStoreAddressBook { - /** - * @param {PeerStore["emit"]} emit - * @param {import('./types').Store} store - * @param {(peerId: PeerId, multiaddr: Multiaddr) => Promise} addressFilter - */ - constructor (emit, store, addressFilter) { - this._emit = emit - this._store = store - this._addressFilter = addressFilter - } - - /** - * 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 - */ - async consumePeerRecord (envelope) { - log('consumePeerRecord await write lock') - const release = await this._store.lock.writeLock() - log('consumePeerRecord got write lock') - - let peerId - let updatedPeer - - try { - let peerRecord - try { - peerRecord = PeerRecord.createFromProtobuf(envelope.payload) - } catch (/** @type {any} */ err) { - log.error('invalid peer record received') - return false - } - - peerId = peerRecord.peerId - const multiaddrs = peerRecord.multiaddrs - - // Verify peerId - if (!peerId.equals(envelope.peerId)) { - log('signing key does not match PeerId in the PeerRecord') - return false - } - - // ensure the record has multiaddrs - if (!multiaddrs || !multiaddrs.length) { - return false - } - - if (await this._store.has(peerId)) { - const peer = await this._store.load(peerId) - - if (peer.peerRecordEnvelope) { - const storedEnvelope = await Envelope.createFromProtobuf(peer.peerRecordEnvelope) - const storedRecord = PeerRecord.createFromProtobuf(storedEnvelope.payload) - - // ensure seq is greater than, or equal to, the last received - if (storedRecord.seqNumber >= peerRecord.seqNumber) { - return false - } - } - } - - // Replace unsigned addresses by the new ones from the record - // TODO: Once we have ttls for the addresses, we should merge these in - updatedPeer = await this._store.patchOrCreate(peerId, { - addresses: await filterMultiaddrs(peerId, multiaddrs, this._addressFilter, true), - peerRecordEnvelope: envelope.marshal() - }) - - log(`stored provided peer record for ${peerRecord.peerId.toB58String()}`) - } finally { - log('consumePeerRecord release write lock') - release() - } - - this._emit(EVENT_NAME, { peerId, multiaddrs: updatedPeer.addresses.map(({ multiaddr }) => multiaddr) }) - - return true - } - - /** - * @param {PeerId} peerId - */ - async getRawEnvelope (peerId) { - log('getRawEnvelope await read lock') - const release = await this._store.lock.readLock() - log('getRawEnvelope got read lock') - - try { - const peer = await this._store.load(peerId) - - return peer.peerRecordEnvelope - } catch (/** @type {any} */ err) { - if (err.code !== codes.ERR_NOT_FOUND) { - throw err - } - } finally { - log('getRawEnvelope release read lock') - release() - } - } - - /** - * Get an Envelope containing a PeerRecord for the given peer. - * Returns undefined if no record exists. - * - * @param {PeerId} peerId - */ - async getPeerRecord (peerId) { - const raw = await this.getRawEnvelope(peerId) - - if (!raw) { - return undefined - } - - return Envelope.createFromProtobuf(raw) - } - - /** - * @param {PeerId} peerId - */ - async get (peerId) { - 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'), codes.ERR_INVALID_PARAMETERS) - } - - log('get wait for read lock') - const release = await this._store.lock.readLock() - log('get got read lock') - - try { - const peer = await this._store.load(peerId) - - return peer.addresses - } catch (/** @type {any} */ err) { - if (err.code !== codes.ERR_NOT_FOUND) { - throw err - } - } finally { - log('get release read lock') - release() - } - - return [] - } - - /** - * @param {PeerId} peerId - * @param {Multiaddr[]} multiaddrs - */ - async set (peerId, multiaddrs) { - if (!PeerId.isPeerId(peerId)) { - log.error('peerId must be an instance of peer-id to store data') - throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) - } - - if (!Array.isArray(multiaddrs)) { - log.error('multiaddrs must be an array of Multiaddrs') - throw errcode(new Error('multiaddrs must be an array of Multiaddrs'), codes.ERR_INVALID_PARAMETERS) - } - - log('set await write lock') - const release = await this._store.lock.writeLock() - log('set got write lock') - - let hasPeer = false - let updatedPeer - - try { - const addresses = await filterMultiaddrs(peerId, multiaddrs, this._addressFilter) - - // No valid addresses found - if (!addresses.length) { - return - } - - try { - const peer = await this._store.load(peerId) - hasPeer = true - - if (new Set([ - ...addresses.map(({ multiaddr }) => multiaddr.toString()), - ...peer.addresses.map(({ multiaddr }) => multiaddr.toString()) - ]).size === peer.addresses.length && addresses.length === peer.addresses.length) { - // not changing anything, no need to update - return - } - } catch (/** @type {any} */ err) { - if (err.code !== codes.ERR_NOT_FOUND) { - throw err - } - } - - updatedPeer = await this._store.patchOrCreate(peerId, { addresses }) - - log(`set multiaddrs for ${peerId.toB58String()}`) - } finally { - log('set release write lock') - release() - } - - this._emit(EVENT_NAME, { peerId, multiaddrs: updatedPeer.addresses.map(addr => addr.multiaddr) }) - - // Notify the existence of a new peer - if (!hasPeer) { - this._emit('peer', peerId) - } - } - - /** - * @param {PeerId} peerId - * @param {Multiaddr[]} multiaddrs - */ - async add (peerId, multiaddrs) { - if (!PeerId.isPeerId(peerId)) { - log.error('peerId must be an instance of peer-id to store data') - throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) - } - - if (!Array.isArray(multiaddrs)) { - log.error('multiaddrs must be an array of Multiaddrs') - throw errcode(new Error('multiaddrs must be an array of Multiaddrs'), codes.ERR_INVALID_PARAMETERS) - } - - log('add await write lock') - const release = await this._store.lock.writeLock() - log('add got write lock') - - let hasPeer - let updatedPeer - - try { - const addresses = await filterMultiaddrs(peerId, multiaddrs, this._addressFilter) - - // No valid addresses found - if (!addresses.length) { - return - } - - try { - const peer = await this._store.load(peerId) - hasPeer = true - - if (new Set([ - ...addresses.map(({ multiaddr }) => multiaddr.toString()), - ...peer.addresses.map(({ multiaddr }) => multiaddr.toString()) - ]).size === peer.addresses.length) { - return - } - } catch (/** @type {any} */ err) { - if (err.code !== codes.ERR_NOT_FOUND) { - throw err - } - } - - updatedPeer = await this._store.mergeOrCreate(peerId, { addresses }) - - log(`added multiaddrs for ${peerId.toB58String()}`) - } finally { - log('set release write lock') - release() - } - - this._emit(EVENT_NAME, { peerId, multiaddrs: updatedPeer.addresses.map(addr => addr.multiaddr) }) - - // Notify the existence of a new peer - if (!hasPeer) { - this._emit('peer', peerId) - } - } - - /** - * @param {PeerId} peerId - */ - async delete (peerId) { - 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'), codes.ERR_INVALID_PARAMETERS) - } - - log('delete await write lock') - const release = await this._store.lock.writeLock() - log('delete got write lock') - - let has - - try { - has = await this._store.has(peerId) - - await this._store.patchOrCreate(peerId, { - addresses: [] - }) - } finally { - log('delete release write lock') - release() - } - - if (has) { - this._emit(EVENT_NAME, { peerId, multiaddrs: [] }) - } - } - - /** - * @param {PeerId} peerId - * @param {(addresses: Address[]) => Address[]} [addressSorter] - */ - async getMultiaddrsForPeer (peerId, addressSorter = (ms) => ms) { - const addresses = await this.get(peerId) - - return addressSorter( - addresses - ).map((address) => { - const multiaddr = address.multiaddr - - const idString = multiaddr.getPeerId() - if (idString && idString === peerId.toB58String()) return multiaddr - - return multiaddr.encapsulate(`/p2p/${peerId.toB58String()}`) - }) - } -} - -/** - * @param {PeerId} peerId - * @param {Multiaddr[]} multiaddrs - * @param {(peerId: PeerId, multiaddr: Multiaddr) => Promise} addressFilter - * @param {boolean} isCertified - */ -function filterMultiaddrs (peerId, multiaddrs, addressFilter, isCertified = false) { - return pipe( - multiaddrs, - (source) => each(source, (multiaddr) => { - if (!Multiaddr.isMultiaddr(multiaddr)) { - log.error('multiaddr must be an instance of Multiaddr') - throw errcode(new Error('multiaddr must be an instance of Multiaddr'), codes.ERR_INVALID_PARAMETERS) - } - }), - (source) => filter(source, (multiaddr) => addressFilter(peerId, multiaddr)), - (source) => map(source, (multiaddr) => { - return { - multiaddr: new Multiaddr(multiaddr.toString()), - isCertified - } - }), - (source) => all(source) - ) -} - -module.exports = PeerStoreAddressBook diff --git a/src/peer-store/index.js b/src/peer-store/index.js deleted file mode 100644 index 2dceac374a..0000000000 --- a/src/peer-store/index.js +++ /dev/null @@ -1,121 +0,0 @@ -'use strict' - -const debug = require('debug') -const { EventEmitter } = require('events') -const AddressBook = require('./address-book') -const KeyBook = require('./key-book') -const MetadataBook = require('./metadata-book') -const ProtoBook = require('./proto-book') -const Store = require('./store') - -/** - * @typedef {import('./types').PeerStore} PeerStore - * @typedef {import('./types').Peer} Peer - * @typedef {import('peer-id')} PeerId - * @typedef {import('multiaddr').Multiaddr} Multiaddr - */ - -const log = Object.assign(debug('libp2p:peer-store'), { - error: debug('libp2p:peer-store:err') -}) - -/** - * An implementation of PeerStore that stores data in a Datastore - * - * @implements {PeerStore} - */ -class DefaultPeerStore extends EventEmitter { - /** - * @param {object} properties - * @param {PeerId} properties.peerId - * @param {import('interface-datastore').Datastore} properties.datastore - * @param {(peerId: PeerId, multiaddr: Multiaddr) => Promise} properties.addressFilter - */ - constructor ({ peerId, datastore, addressFilter }) { - super() - - this._peerId = peerId - this._store = new Store(datastore) - - this.addressBook = new AddressBook(this.emit.bind(this), this._store, addressFilter) - this.keyBook = new KeyBook(this.emit.bind(this), this._store) - this.metadataBook = new MetadataBook(this.emit.bind(this), this._store) - this.protoBook = new ProtoBook(this.emit.bind(this), this._store) - } - - async * getPeers () { - log('getPeers await read lock') - const release = await this._store.lock.readLock() - log('getPeers got read lock') - - try { - for await (const peer of this._store.all()) { - if (peer.id.toB58String() === this._peerId.toB58String()) { - // Remove self peer if present - continue - } - - yield peer - } - } finally { - log('getPeers release read lock') - release() - } - } - - /** - * Delete the information of the given peer in every book - * - * @param {PeerId} peerId - */ - async delete (peerId) { - log('delete await write lock') - const release = await this._store.lock.writeLock() - log('delete got write lock') - - try { - await this._store.delete(peerId) - } finally { - log('delete release write lock') - release() - } - } - - /** - * Get the stored information of a given peer - * - * @param {PeerId} peerId - */ - async get (peerId) { - log('get await read lock') - const release = await this._store.lock.readLock() - log('get got read lock') - - try { - return this._store.load(peerId) - } finally { - log('get release read lock') - release() - } - } - - /** - * Returns true if we have a record of the peer - * - * @param {PeerId} peerId - */ - async has (peerId) { - log('has await read lock') - const release = await this._store.lock.readLock() - log('has got read lock') - - try { - return this._store.has(peerId) - } finally { - log('has release read lock') - release() - } - } -} - -module.exports = DefaultPeerStore diff --git a/src/peer-store/key-book.js b/src/peer-store/key-book.js deleted file mode 100644 index dfd4c1511e..0000000000 --- a/src/peer-store/key-book.js +++ /dev/null @@ -1,141 +0,0 @@ -'use strict' - -const debug = require('debug') -const errcode = require('err-code') -const { codes } = require('../errors') -const PeerId = require('peer-id') -const { equals: uint8arrayEquals } = require('uint8arrays/equals') - -/** - * @typedef {import('./types').PeerStore} PeerStore - * @typedef {import('./types').KeyBook} KeyBook - * @typedef {import('libp2p-interfaces/src/keys/types').PublicKey} PublicKey - */ - -const log = Object.assign(debug('libp2p:peer-store:key-book'), { - error: debug('libp2p:peer-store:key-book:err') -}) - -const EVENT_NAME = 'change:pubkey' - -/** - * @implements {KeyBook} - */ -class PeerStoreKeyBook { - /** - * The KeyBook is responsible for keeping the known public keys of a peer. - * - * @param {PeerStore["emit"]} emit - * @param {import('./types').Store} store - */ - constructor (emit, store) { - this._emit = emit - this._store = store - } - - /** - * Set the Peer public key - * - * @param {PeerId} peerId - * @param {PublicKey} publicKey - */ - async set (peerId, publicKey) { - if (!PeerId.isPeerId(peerId)) { - log.error('peerId must be an instance of peer-id to store data') - throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) - } - - if (!publicKey) { - log.error('publicKey must be an instance of PublicKey to store data') - throw errcode(new Error('publicKey must be an instance of PublicKey'), codes.ERR_INVALID_PARAMETERS) - } - - log('set await write lock') - const release = await this._store.lock.writeLock() - log('set got write lock') - - let updatedKey = false - - try { - try { - const existing = await this._store.load(peerId) - - if (existing.pubKey && uint8arrayEquals(existing.pubKey.bytes, publicKey.bytes)) { - return - } - } catch (/** @type {any} */ err) { - if (err.code !== codes.ERR_NOT_FOUND) { - throw err - } - } - - await this._store.patchOrCreate(peerId, { - pubKey: publicKey - }) - updatedKey = true - } finally { - log('set release write lock') - release() - } - - if (updatedKey) { - this._emit(EVENT_NAME, { peerId, pubKey: publicKey }) - } - } - - /** - * Get Public key of the given PeerId, if stored - * - * @param {PeerId} peerId - */ - async get (peerId) { - 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'), codes.ERR_INVALID_PARAMETERS) - } - - log('get await write lock') - const release = await this._store.lock.readLock() - log('get got write lock') - - try { - const peer = await this._store.load(peerId) - - return peer.pubKey - } catch (/** @type {any} */ err) { - if (err.code !== codes.ERR_NOT_FOUND) { - throw err - } - } finally { - log('get release write lock') - release() - } - } - - /** - * @param {PeerId} peerId - */ - async delete (peerId) { - 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'), codes.ERR_INVALID_PARAMETERS) - } - - log('delete await write lock') - const release = await this._store.lock.writeLock() - log('delete got write lock') - - try { - await this._store.patchOrCreate(peerId, { - pubKey: undefined - }) - } finally { - log('delete release write lock') - release() - } - - this._emit(EVENT_NAME, { peerId, pubKey: undefined }) - } -} - -module.exports = PeerStoreKeyBook diff --git a/src/peer-store/metadata-book.js b/src/peer-store/metadata-book.js deleted file mode 100644 index 7176a78afb..0000000000 --- a/src/peer-store/metadata-book.js +++ /dev/null @@ -1,250 +0,0 @@ -'use strict' - -const debug = require('debug') -const errcode = require('err-code') -const { codes } = require('../errors') -const PeerId = require('peer-id') -const { equals: uint8ArrayEquals } = require('uint8arrays/equals') - -const log = Object.assign(debug('libp2p:peer-store:metadata-book'), { - error: debug('libp2p:peer-store:metadata-book:err') -}) - -/** - * @typedef {import('./types').PeerStore} PeerStore - * @typedef {import('./types').MetadataBook} MetadataBook - */ - -const EVENT_NAME = 'change:metadata' - -/** - * @implements {MetadataBook} - */ -class PeerStoreMetadataBook { - /** - * The MetadataBook is responsible for keeping the known supported - * protocols of a peer - * - * @param {PeerStore["emit"]} emit - * @param {import('./types').Store} store - */ - constructor (emit, store) { - this._emit = emit - this._store = store - } - - /** - * Get the known data of a provided peer - * - * @param {PeerId} peerId - */ - async get (peerId) { - 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'), codes.ERR_INVALID_PARAMETERS) - } - - log('get await read lock') - const release = await this._store.lock.readLock() - log('get got read lock') - - try { - const peer = await this._store.load(peerId) - - return peer.metadata - } catch (/** @type {any} */ err) { - if (err.code !== codes.ERR_NOT_FOUND) { - throw err - } - } finally { - log('get release read lock') - release() - } - - return new Map() - } - - /** - * Get specific metadata value, if it exists - * - * @param {PeerId} peerId - * @param {string} key - */ - async getValue (peerId, key) { - 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'), codes.ERR_INVALID_PARAMETERS) - } - - log('getValue await read lock') - const release = await this._store.lock.readLock() - log('getValue got read lock') - - try { - const peer = await this._store.load(peerId) - - return peer.metadata.get(key) - } catch (/** @type {any} */ err) { - if (err.code !== codes.ERR_NOT_FOUND) { - throw err - } - } finally { - log('getValue release write lock') - release() - } - } - - /** - * @param {PeerId} peerId - * @param {Map} metadata - */ - async set (peerId, metadata) { - 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'), codes.ERR_INVALID_PARAMETERS) - } - - if (!metadata || !(metadata instanceof Map)) { - log.error('valid metadata must be provided to store data') - throw errcode(new Error('valid metadata must be provided'), codes.ERR_INVALID_PARAMETERS) - } - - log('set await write lock') - const release = await this._store.lock.writeLock() - log('set got write lock') - - try { - await this._store.mergeOrCreate(peerId, { - metadata - }) - } finally { - log('set release write lock') - release() - } - - this._emit(EVENT_NAME, { peerId, metadata }) - } - - /** - * Set metadata key and value of a provided peer - * - * @param {PeerId} peerId - * @param {string} key - metadata key - * @param {Uint8Array} value - metadata value - */ - async setValue (peerId, key, value) { - if (!PeerId.isPeerId(peerId)) { - log.error('peerId must be an instance of peer-id to store data') - throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) - } - - if (typeof key !== 'string' || !(value instanceof Uint8Array)) { - log.error('valid key and value must be provided to store data') - throw errcode(new Error('valid key and value must be provided'), codes.ERR_INVALID_PARAMETERS) - } - - log('setValue await write lock') - const release = await this._store.lock.writeLock() - log('setValue got write lock') - - let updatedPeer - - try { - try { - const existingPeer = await this._store.load(peerId) - const existingValue = existingPeer.metadata.get(key) - - if (existingValue != null && uint8ArrayEquals(value, existingValue)) { - return - } - } catch (/** @type {any} */ err) { - if (err.code !== codes.ERR_NOT_FOUND) { - throw err - } - } - - updatedPeer = await this._store.mergeOrCreate(peerId, { - metadata: new Map([[key, value]]) - }) - } finally { - log('setValue release write lock') - release() - } - - this._emit(EVENT_NAME, { peerId, metadata: updatedPeer.metadata }) - } - - /** - * @param {PeerId} peerId - */ - async delete (peerId) { - 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'), codes.ERR_INVALID_PARAMETERS) - } - - log('delete await write lock') - const release = await this._store.lock.writeLock() - log('delete got write lock') - - let has - - try { - has = await this._store.has(peerId) - - if (has) { - await this._store.patch(peerId, { - metadata: new Map() - }) - } - } finally { - log('delete release write lock') - release() - } - - if (has) { - this._emit(EVENT_NAME, { peerId, metadata: new Map() }) - } - } - - /** - * @param {PeerId} peerId - * @param {string} key - */ - async deleteValue (peerId, key) { - 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'), codes.ERR_INVALID_PARAMETERS) - } - - log('deleteValue await write lock') - const release = await this._store.lock.writeLock() - log('deleteValue got write lock') - - let metadata - - try { - const peer = await this._store.load(peerId) - metadata = peer.metadata - - metadata.delete(key) - - await this._store.patch(peerId, { - metadata - }) - } catch (/** @type {any} **/ err) { - if (err.code !== codes.ERR_NOT_FOUND) { - throw err - } - } finally { - log('deleteValue release write lock') - release() - } - - if (metadata) { - this._emit(EVENT_NAME, { peerId, metadata }) - } - } -} - -module.exports = PeerStoreMetadataBook diff --git a/src/peer-store/pb/peer.d.ts b/src/peer-store/pb/peer.d.ts deleted file mode 100644 index e6ccdfe6c1..0000000000 --- a/src/peer-store/pb/peer.d.ts +++ /dev/null @@ -1,222 +0,0 @@ -import * as $protobuf from "protobufjs"; -/** Properties of a Peer. */ -export interface IPeer { - - /** Peer addresses */ - addresses?: (IAddress[]|null); - - /** Peer protocols */ - protocols?: (string[]|null); - - /** Peer metadata */ - metadata?: (IMetadata[]|null); - - /** Peer pubKey */ - pubKey?: (Uint8Array|null); - - /** Peer peerRecordEnvelope */ - peerRecordEnvelope?: (Uint8Array|null); -} - -/** Represents a Peer. */ -export class Peer implements IPeer { - - /** - * Constructs a new Peer. - * @param [p] Properties to set - */ - constructor(p?: IPeer); - - /** Peer addresses. */ - public addresses: IAddress[]; - - /** Peer protocols. */ - public protocols: string[]; - - /** Peer metadata. */ - public metadata: IMetadata[]; - - /** Peer pubKey. */ - public pubKey?: (Uint8Array|null); - - /** Peer peerRecordEnvelope. */ - public peerRecordEnvelope?: (Uint8Array|null); - - /** Peer _pubKey. */ - public _pubKey?: "pubKey"; - - /** Peer _peerRecordEnvelope. */ - public _peerRecordEnvelope?: "peerRecordEnvelope"; - - /** - * Encodes the specified Peer message. Does not implicitly {@link Peer.verify|verify} messages. - * @param m Peer message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: IPeer, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a Peer message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns Peer - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Peer; - - /** - * Creates a Peer message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns Peer - */ - public static fromObject(d: { [k: string]: any }): Peer; - - /** - * Creates a plain object from a Peer message. Also converts values to other types if specified. - * @param m Peer - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: Peer, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this Peer to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; -} - -/** Properties of an Address. */ -export interface IAddress { - - /** Address multiaddr */ - multiaddr?: (Uint8Array|null); - - /** Address isCertified */ - isCertified?: (boolean|null); -} - -/** Represents an Address. */ -export class Address implements IAddress { - - /** - * Constructs a new Address. - * @param [p] Properties to set - */ - constructor(p?: IAddress); - - /** Address multiaddr. */ - public multiaddr: Uint8Array; - - /** Address isCertified. */ - public isCertified?: (boolean|null); - - /** Address _isCertified. */ - public _isCertified?: "isCertified"; - - /** - * Encodes the specified Address message. Does not implicitly {@link Address.verify|verify} messages. - * @param m Address message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: IAddress, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes an Address message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns Address - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Address; - - /** - * Creates an Address message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns Address - */ - public static fromObject(d: { [k: string]: any }): Address; - - /** - * Creates a plain object from an Address message. Also converts values to other types if specified. - * @param m Address - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: Address, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this Address to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; -} - -/** Properties of a Metadata. */ -export interface IMetadata { - - /** Metadata key */ - key?: (string|null); - - /** Metadata value */ - value?: (Uint8Array|null); -} - -/** Represents a Metadata. */ -export class Metadata implements IMetadata { - - /** - * Constructs a new Metadata. - * @param [p] Properties to set - */ - constructor(p?: IMetadata); - - /** Metadata key. */ - public key: string; - - /** Metadata value. */ - public value: Uint8Array; - - /** - * Encodes the specified Metadata message. Does not implicitly {@link Metadata.verify|verify} messages. - * @param m Metadata message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: IMetadata, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a Metadata message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns Metadata - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Metadata; - - /** - * Creates a Metadata message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns Metadata - */ - public static fromObject(d: { [k: string]: any }): Metadata; - - /** - * Creates a plain object from a Metadata message. Also converts values to other types if specified. - * @param m Metadata - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: Metadata, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this Metadata to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; -} diff --git a/src/peer-store/pb/peer.js b/src/peer-store/pb/peer.js deleted file mode 100644 index 866911d8a4..0000000000 --- a/src/peer-store/pb/peer.js +++ /dev/null @@ -1,643 +0,0 @@ -/*eslint-disable*/ -"use strict"; - -var $protobuf = require("protobufjs/minimal"); - -// Common aliases -var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; - -// Exported root namespace -var $root = $protobuf.roots["libp2p-peer"] || ($protobuf.roots["libp2p-peer"] = {}); - -$root.Peer = (function() { - - /** - * Properties of a Peer. - * @exports IPeer - * @interface IPeer - * @property {Array.|null} [addresses] Peer addresses - * @property {Array.|null} [protocols] Peer protocols - * @property {Array.|null} [metadata] Peer metadata - * @property {Uint8Array|null} [pubKey] Peer pubKey - * @property {Uint8Array|null} [peerRecordEnvelope] Peer peerRecordEnvelope - */ - - /** - * Constructs a new Peer. - * @exports Peer - * @classdesc Represents a Peer. - * @implements IPeer - * @constructor - * @param {IPeer=} [p] Properties to set - */ - function Peer(p) { - this.addresses = []; - this.protocols = []; - this.metadata = []; - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * Peer addresses. - * @member {Array.} addresses - * @memberof Peer - * @instance - */ - Peer.prototype.addresses = $util.emptyArray; - - /** - * Peer protocols. - * @member {Array.} protocols - * @memberof Peer - * @instance - */ - Peer.prototype.protocols = $util.emptyArray; - - /** - * Peer metadata. - * @member {Array.} metadata - * @memberof Peer - * @instance - */ - Peer.prototype.metadata = $util.emptyArray; - - /** - * Peer pubKey. - * @member {Uint8Array|null|undefined} pubKey - * @memberof Peer - * @instance - */ - Peer.prototype.pubKey = null; - - /** - * Peer peerRecordEnvelope. - * @member {Uint8Array|null|undefined} peerRecordEnvelope - * @memberof Peer - * @instance - */ - Peer.prototype.peerRecordEnvelope = null; - - // OneOf field names bound to virtual getters and setters - var $oneOfFields; - - /** - * Peer _pubKey. - * @member {"pubKey"|undefined} _pubKey - * @memberof Peer - * @instance - */ - Object.defineProperty(Peer.prototype, "_pubKey", { - get: $util.oneOfGetter($oneOfFields = ["pubKey"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Peer _peerRecordEnvelope. - * @member {"peerRecordEnvelope"|undefined} _peerRecordEnvelope - * @memberof Peer - * @instance - */ - Object.defineProperty(Peer.prototype, "_peerRecordEnvelope", { - get: $util.oneOfGetter($oneOfFields = ["peerRecordEnvelope"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Encodes the specified Peer message. Does not implicitly {@link Peer.verify|verify} messages. - * @function encode - * @memberof Peer - * @static - * @param {IPeer} m Peer message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Peer.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.addresses != null && m.addresses.length) { - for (var i = 0; i < m.addresses.length; ++i) - $root.Address.encode(m.addresses[i], w.uint32(10).fork()).ldelim(); - } - if (m.protocols != null && m.protocols.length) { - for (var i = 0; i < m.protocols.length; ++i) - w.uint32(18).string(m.protocols[i]); - } - if (m.metadata != null && m.metadata.length) { - for (var i = 0; i < m.metadata.length; ++i) - $root.Metadata.encode(m.metadata[i], w.uint32(26).fork()).ldelim(); - } - if (m.pubKey != null && Object.hasOwnProperty.call(m, "pubKey")) - w.uint32(34).bytes(m.pubKey); - if (m.peerRecordEnvelope != null && Object.hasOwnProperty.call(m, "peerRecordEnvelope")) - w.uint32(42).bytes(m.peerRecordEnvelope); - return w; - }; - - /** - * Decodes a Peer message from the specified reader or buffer. - * @function decode - * @memberof Peer - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {Peer} Peer - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Peer.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.Peer(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - if (!(m.addresses && m.addresses.length)) - m.addresses = []; - m.addresses.push($root.Address.decode(r, r.uint32())); - break; - case 2: - if (!(m.protocols && m.protocols.length)) - m.protocols = []; - m.protocols.push(r.string()); - break; - case 3: - if (!(m.metadata && m.metadata.length)) - m.metadata = []; - m.metadata.push($root.Metadata.decode(r, r.uint32())); - break; - case 4: - m.pubKey = r.bytes(); - break; - case 5: - m.peerRecordEnvelope = r.bytes(); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a Peer message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof Peer - * @static - * @param {Object.} d Plain object - * @returns {Peer} Peer - */ - Peer.fromObject = function fromObject(d) { - if (d instanceof $root.Peer) - return d; - var m = new $root.Peer(); - if (d.addresses) { - if (!Array.isArray(d.addresses)) - throw TypeError(".Peer.addresses: array expected"); - m.addresses = []; - for (var i = 0; i < d.addresses.length; ++i) { - if (typeof d.addresses[i] !== "object") - throw TypeError(".Peer.addresses: object expected"); - m.addresses[i] = $root.Address.fromObject(d.addresses[i]); - } - } - if (d.protocols) { - if (!Array.isArray(d.protocols)) - throw TypeError(".Peer.protocols: array expected"); - m.protocols = []; - for (var i = 0; i < d.protocols.length; ++i) { - m.protocols[i] = String(d.protocols[i]); - } - } - if (d.metadata) { - if (!Array.isArray(d.metadata)) - throw TypeError(".Peer.metadata: array expected"); - m.metadata = []; - for (var i = 0; i < d.metadata.length; ++i) { - if (typeof d.metadata[i] !== "object") - throw TypeError(".Peer.metadata: object expected"); - m.metadata[i] = $root.Metadata.fromObject(d.metadata[i]); - } - } - if (d.pubKey != null) { - if (typeof d.pubKey === "string") - $util.base64.decode(d.pubKey, m.pubKey = $util.newBuffer($util.base64.length(d.pubKey)), 0); - else if (d.pubKey.length) - m.pubKey = d.pubKey; - } - if (d.peerRecordEnvelope != null) { - if (typeof d.peerRecordEnvelope === "string") - $util.base64.decode(d.peerRecordEnvelope, m.peerRecordEnvelope = $util.newBuffer($util.base64.length(d.peerRecordEnvelope)), 0); - else if (d.peerRecordEnvelope.length) - m.peerRecordEnvelope = d.peerRecordEnvelope; - } - return m; - }; - - /** - * Creates a plain object from a Peer message. Also converts values to other types if specified. - * @function toObject - * @memberof Peer - * @static - * @param {Peer} m Peer - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - Peer.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.arrays || o.defaults) { - d.addresses = []; - d.protocols = []; - d.metadata = []; - } - if (m.addresses && m.addresses.length) { - d.addresses = []; - for (var j = 0; j < m.addresses.length; ++j) { - d.addresses[j] = $root.Address.toObject(m.addresses[j], o); - } - } - if (m.protocols && m.protocols.length) { - d.protocols = []; - for (var j = 0; j < m.protocols.length; ++j) { - d.protocols[j] = m.protocols[j]; - } - } - if (m.metadata && m.metadata.length) { - d.metadata = []; - for (var j = 0; j < m.metadata.length; ++j) { - d.metadata[j] = $root.Metadata.toObject(m.metadata[j], o); - } - } - if (m.pubKey != null && m.hasOwnProperty("pubKey")) { - d.pubKey = o.bytes === String ? $util.base64.encode(m.pubKey, 0, m.pubKey.length) : o.bytes === Array ? Array.prototype.slice.call(m.pubKey) : m.pubKey; - if (o.oneofs) - d._pubKey = "pubKey"; - } - if (m.peerRecordEnvelope != null && m.hasOwnProperty("peerRecordEnvelope")) { - d.peerRecordEnvelope = o.bytes === String ? $util.base64.encode(m.peerRecordEnvelope, 0, m.peerRecordEnvelope.length) : o.bytes === Array ? Array.prototype.slice.call(m.peerRecordEnvelope) : m.peerRecordEnvelope; - if (o.oneofs) - d._peerRecordEnvelope = "peerRecordEnvelope"; - } - return d; - }; - - /** - * Converts this Peer to JSON. - * @function toJSON - * @memberof Peer - * @instance - * @returns {Object.} JSON object - */ - Peer.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return Peer; -})(); - -$root.Address = (function() { - - /** - * Properties of an Address. - * @exports IAddress - * @interface IAddress - * @property {Uint8Array|null} [multiaddr] Address multiaddr - * @property {boolean|null} [isCertified] Address isCertified - */ - - /** - * Constructs a new Address. - * @exports Address - * @classdesc Represents an Address. - * @implements IAddress - * @constructor - * @param {IAddress=} [p] Properties to set - */ - function Address(p) { - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * Address multiaddr. - * @member {Uint8Array} multiaddr - * @memberof Address - * @instance - */ - Address.prototype.multiaddr = $util.newBuffer([]); - - /** - * Address isCertified. - * @member {boolean|null|undefined} isCertified - * @memberof Address - * @instance - */ - Address.prototype.isCertified = null; - - // OneOf field names bound to virtual getters and setters - var $oneOfFields; - - /** - * Address _isCertified. - * @member {"isCertified"|undefined} _isCertified - * @memberof Address - * @instance - */ - Object.defineProperty(Address.prototype, "_isCertified", { - get: $util.oneOfGetter($oneOfFields = ["isCertified"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Encodes the specified Address message. Does not implicitly {@link Address.verify|verify} messages. - * @function encode - * @memberof Address - * @static - * @param {IAddress} m Address message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Address.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.multiaddr != null && Object.hasOwnProperty.call(m, "multiaddr")) - w.uint32(10).bytes(m.multiaddr); - if (m.isCertified != null && Object.hasOwnProperty.call(m, "isCertified")) - w.uint32(16).bool(m.isCertified); - return w; - }; - - /** - * Decodes an Address message from the specified reader or buffer. - * @function decode - * @memberof Address - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {Address} Address - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Address.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.Address(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.multiaddr = r.bytes(); - break; - case 2: - m.isCertified = r.bool(); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates an Address message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof Address - * @static - * @param {Object.} d Plain object - * @returns {Address} Address - */ - Address.fromObject = function fromObject(d) { - if (d instanceof $root.Address) - return d; - var m = new $root.Address(); - if (d.multiaddr != null) { - if (typeof d.multiaddr === "string") - $util.base64.decode(d.multiaddr, m.multiaddr = $util.newBuffer($util.base64.length(d.multiaddr)), 0); - else if (d.multiaddr.length) - m.multiaddr = d.multiaddr; - } - if (d.isCertified != null) { - m.isCertified = Boolean(d.isCertified); - } - return m; - }; - - /** - * Creates a plain object from an Address message. Also converts values to other types if specified. - * @function toObject - * @memberof Address - * @static - * @param {Address} m Address - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - Address.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.defaults) { - if (o.bytes === String) - d.multiaddr = ""; - else { - d.multiaddr = []; - if (o.bytes !== Array) - d.multiaddr = $util.newBuffer(d.multiaddr); - } - } - if (m.multiaddr != null && m.hasOwnProperty("multiaddr")) { - d.multiaddr = o.bytes === String ? $util.base64.encode(m.multiaddr, 0, m.multiaddr.length) : o.bytes === Array ? Array.prototype.slice.call(m.multiaddr) : m.multiaddr; - } - if (m.isCertified != null && m.hasOwnProperty("isCertified")) { - d.isCertified = m.isCertified; - if (o.oneofs) - d._isCertified = "isCertified"; - } - return d; - }; - - /** - * Converts this Address to JSON. - * @function toJSON - * @memberof Address - * @instance - * @returns {Object.} JSON object - */ - Address.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return Address; -})(); - -$root.Metadata = (function() { - - /** - * Properties of a Metadata. - * @exports IMetadata - * @interface IMetadata - * @property {string|null} [key] Metadata key - * @property {Uint8Array|null} [value] Metadata value - */ - - /** - * Constructs a new Metadata. - * @exports Metadata - * @classdesc Represents a Metadata. - * @implements IMetadata - * @constructor - * @param {IMetadata=} [p] Properties to set - */ - function Metadata(p) { - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * Metadata key. - * @member {string} key - * @memberof Metadata - * @instance - */ - Metadata.prototype.key = ""; - - /** - * Metadata value. - * @member {Uint8Array} value - * @memberof Metadata - * @instance - */ - Metadata.prototype.value = $util.newBuffer([]); - - /** - * Encodes the specified Metadata message. Does not implicitly {@link Metadata.verify|verify} messages. - * @function encode - * @memberof Metadata - * @static - * @param {IMetadata} m Metadata message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Metadata.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.key != null && Object.hasOwnProperty.call(m, "key")) - w.uint32(10).string(m.key); - if (m.value != null && Object.hasOwnProperty.call(m, "value")) - w.uint32(18).bytes(m.value); - return w; - }; - - /** - * Decodes a Metadata message from the specified reader or buffer. - * @function decode - * @memberof Metadata - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {Metadata} Metadata - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Metadata.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.Metadata(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.key = r.string(); - break; - case 2: - m.value = r.bytes(); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a Metadata message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof Metadata - * @static - * @param {Object.} d Plain object - * @returns {Metadata} Metadata - */ - Metadata.fromObject = function fromObject(d) { - if (d instanceof $root.Metadata) - return d; - var m = new $root.Metadata(); - if (d.key != null) { - m.key = String(d.key); - } - if (d.value != null) { - if (typeof d.value === "string") - $util.base64.decode(d.value, m.value = $util.newBuffer($util.base64.length(d.value)), 0); - else if (d.value.length) - m.value = d.value; - } - return m; - }; - - /** - * Creates a plain object from a Metadata message. Also converts values to other types if specified. - * @function toObject - * @memberof Metadata - * @static - * @param {Metadata} m Metadata - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - Metadata.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.defaults) { - d.key = ""; - if (o.bytes === String) - d.value = ""; - else { - d.value = []; - if (o.bytes !== Array) - d.value = $util.newBuffer(d.value); - } - } - if (m.key != null && m.hasOwnProperty("key")) { - d.key = m.key; - } - if (m.value != null && m.hasOwnProperty("value")) { - d.value = o.bytes === String ? $util.base64.encode(m.value, 0, m.value.length) : o.bytes === Array ? Array.prototype.slice.call(m.value) : m.value; - } - return d; - }; - - /** - * Converts this Metadata to JSON. - * @function toJSON - * @memberof Metadata - * @instance - * @returns {Object.} JSON object - */ - Metadata.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return Metadata; -})(); - -module.exports = $root; diff --git a/src/peer-store/pb/peer.proto b/src/peer-store/pb/peer.proto deleted file mode 100644 index 1c9cc166c4..0000000000 --- a/src/peer-store/pb/peer.proto +++ /dev/null @@ -1,31 +0,0 @@ -syntax = "proto3"; - -message Peer { - // Multiaddrs we know about - repeated Address addresses = 1; - - // The protocols the peer supports - repeated string protocols = 2; - - // Any peer metadata - repeated Metadata metadata = 3; - - // The public key of the peer - optional bytes pub_key = 4; - - // The most recently received signed PeerRecord - optional bytes peer_record_envelope = 5; -} - -// Address represents a single multiaddr -message Address { - bytes multiaddr = 1; - - // Flag to indicate if the address comes from a certified source - optional bool isCertified = 2; -} - -message Metadata { - string key = 1; - bytes value = 2; -} diff --git a/src/peer-store/proto-book.js b/src/peer-store/proto-book.js deleted file mode 100644 index 686957ec7a..0000000000 --- a/src/peer-store/proto-book.js +++ /dev/null @@ -1,237 +0,0 @@ -'use strict' - -const debug = require('debug') -const errcode = require('err-code') -const { codes } = require('../errors') -const PeerId = require('peer-id') - -/** - * @typedef {import('./types').PeerStore} PeerStore - * @typedef {import('./types').ProtoBook} ProtoBook - */ - -const log = Object.assign(debug('libp2p:peer-store:proto-book'), { - error: debug('libp2p:peer-store:proto-book:err') -}) - -const EVENT_NAME = 'change:protocols' - -/** - * @implements {ProtoBook} - */ -class PersistentProtoBook { - /** - * @param {PeerStore["emit"]} emit - * @param {import('./types').Store} store - */ - constructor (emit, store) { - this._emit = emit - this._store = store - } - - /** - * @param {PeerId} peerId - */ - async get (peerId) { - log('get wait for read lock') - const release = await this._store.lock.readLock() - log('get got read lock') - - try { - const peer = await this._store.load(peerId) - - return peer.protocols - } catch (/** @type {any} */ err) { - if (err.code !== codes.ERR_NOT_FOUND) { - throw err - } - } finally { - log('get release read lock') - release() - } - - return [] - } - - /** - * @param {PeerId} peerId - * @param {string[]} protocols - */ - async set (peerId, protocols) { - if (!PeerId.isPeerId(peerId)) { - log.error('peerId must be an instance of peer-id to store data') - throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) - } - - if (!Array.isArray(protocols)) { - log.error('protocols must be provided to store data') - throw errcode(new Error('protocols must be provided'), codes.ERR_INVALID_PARAMETERS) - } - - log('set await write lock') - const release = await this._store.lock.writeLock() - log('set got write lock') - - let updatedPeer - - try { - try { - const peer = await this._store.load(peerId) - - if (new Set([ - ...protocols - ]).size === peer.protocols.length) { - return - } - } catch (/** @type {any} */ err) { - if (err.code !== codes.ERR_NOT_FOUND) { - throw err - } - } - - updatedPeer = await this._store.patchOrCreate(peerId, { - protocols - }) - - log(`stored provided protocols for ${peerId.toB58String()}`) - } finally { - log('set release write lock') - release() - } - - this._emit(EVENT_NAME, { peerId, protocols: updatedPeer.protocols }) - } - - /** - * @param {PeerId} peerId - * @param {string[]} protocols - */ - async add (peerId, protocols) { - if (!PeerId.isPeerId(peerId)) { - log.error('peerId must be an instance of peer-id to store data') - throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) - } - - if (!Array.isArray(protocols)) { - log.error('protocols must be provided to store data') - throw errcode(new Error('protocols must be provided'), codes.ERR_INVALID_PARAMETERS) - } - - log('add await write lock') - const release = await this._store.lock.writeLock() - log('add got write lock') - - let updatedPeer - - try { - try { - const peer = await this._store.load(peerId) - - if (new Set([ - ...peer.protocols, - ...protocols - ]).size === peer.protocols.length) { - return - } - } catch (/** @type {any} */ err) { - if (err.code !== codes.ERR_NOT_FOUND) { - throw err - } - } - - updatedPeer = await this._store.mergeOrCreate(peerId, { - protocols - }) - - log(`added provided protocols for ${peerId.toB58String()}`) - } finally { - log('add release write lock') - release() - } - - this._emit(EVENT_NAME, { peerId, protocols: updatedPeer.protocols }) - } - - /** - * @param {PeerId} peerId - * @param {string[]} protocols - */ - async 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'), codes.ERR_INVALID_PARAMETERS) - } - - if (!Array.isArray(protocols)) { - log.error('protocols must be provided to store data') - throw errcode(new Error('protocols must be provided'), codes.ERR_INVALID_PARAMETERS) - } - - log('remove await write lock') - const release = await this._store.lock.writeLock() - log('remove got write lock') - - let updatedPeer - - try { - try { - const peer = await this._store.load(peerId) - const protocolSet = new Set(peer.protocols) - - for (const protocol of protocols) { - protocolSet.delete(protocol) - } - - if (peer.protocols.length === protocolSet.size) { - return - } - - protocols = Array.from(protocolSet) - } catch (/** @type {any} */ err) { - if (err.code !== codes.ERR_NOT_FOUND) { - throw err - } - } - - updatedPeer = await this._store.patchOrCreate(peerId, { - protocols - }) - } finally { - log('remove release write lock') - release() - } - - this._emit(EVENT_NAME, { peerId, protocols: updatedPeer.protocols }) - } - - /** - * @param {PeerId} peerId - */ - async delete (peerId) { - log('delete await write lock') - const release = await this._store.lock.writeLock() - log('delete got write lock') - let has - - try { - has = await this._store.has(peerId) - - await this._store.patchOrCreate(peerId, { - protocols: [] - }) - } catch (/** @type {any} */ err) { - if (err.code !== codes.ERR_NOT_FOUND) { - throw err - } - } finally { - log('delete release write lock') - release() - } - - if (has) { - this._emit(EVENT_NAME, { peerId, protocols: [] }) - } - } -} - -module.exports = PersistentProtoBook diff --git a/src/peer-store/store.js b/src/peer-store/store.js deleted file mode 100644 index 34dcce366b..0000000000 --- a/src/peer-store/store.js +++ /dev/null @@ -1,263 +0,0 @@ -'use strict' - -const debug = require('debug') -const PeerId = require('peer-id') -const errcode = require('err-code') -const { codes } = require('../errors') -const { Key } = require('interface-datastore/key') -const { base32 } = require('multiformats/bases/base32') -const { keys: { unmarshalPublicKey, marshalPublicKey } } = require('libp2p-crypto') -const { Multiaddr } = require('multiaddr') -const { Peer: PeerPB } = require('./pb/peer') -// @ts-expect-error no types -const mortice = require('mortice') -const { equals: uint8arrayEquals } = require('uint8arrays/equals') - -const log = Object.assign(debug('libp2p:peer-store:store'), { - error: debug('libp2p:peer-store:store:err') -}) - -/** - * @typedef {import('./types').PeerStore} PeerStore - * @typedef {import('./types').EventName} EventName - * @typedef {import('./types').Peer} Peer - */ - -const NAMESPACE_COMMON = '/peers/' - -class PersistentStore { - /** - * @param {import('interface-datastore').Datastore} datastore - */ - constructor (datastore) { - this._datastore = datastore - this.lock = mortice('peer-store', { - singleProcess: true - }) - } - - /** - * @param {PeerId} peerId - * @returns {Key} - */ - _peerIdToDatastoreKey (peerId) { - 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'), codes.ERR_INVALID_PARAMETERS) - } - - const b32key = peerId.toString() - return new Key(`${NAMESPACE_COMMON}${b32key}`) - } - - /** - * @param {PeerId} peerId - */ - async has (peerId) { - return this._datastore.has(this._peerIdToDatastoreKey(peerId)) - } - - /** - * @param {PeerId} peerId - */ - async delete (peerId) { - await this._datastore.delete(this._peerIdToDatastoreKey(peerId)) - } - - /** - * @param {PeerId} peerId - * @returns {Promise} peer - */ - async load (peerId) { - const buf = await this._datastore.get(this._peerIdToDatastoreKey(peerId)) - const peer = PeerPB.decode(buf) - const pubKey = peer.pubKey ? unmarshalPublicKey(peer.pubKey) : peerId.pubKey - const metadata = new Map() - - for (const meta of peer.metadata) { - metadata.set(meta.key, meta.value) - } - - return { - ...peer, - id: peerId, - pubKey, - addresses: peer.addresses.map(({ multiaddr, isCertified }) => ({ - multiaddr: new Multiaddr(multiaddr), - isCertified: isCertified || false - })), - metadata, - peerRecordEnvelope: peer.peerRecordEnvelope || undefined - } - } - - /** - * @param {Peer} peer - */ - async save (peer) { - if (peer.pubKey != null && peer.id.pubKey != null && !uint8arrayEquals(peer.pubKey.bytes, peer.id.pubKey.bytes)) { - log.error('peer publicKey bytes do not match peer id publicKey bytes') - throw errcode(new Error('publicKey bytes do not match peer id publicKey bytes'), codes.ERR_INVALID_PARAMETERS) - } - - // dedupe addresses - const addressSet = new Set() - - const buf = PeerPB.encode({ - addresses: peer.addresses - .filter(address => { - if (addressSet.has(address.multiaddr.toString())) { - return false - } - - addressSet.add(address.multiaddr.toString()) - return true - }) - .sort((a, b) => { - return a.multiaddr.toString().localeCompare(b.multiaddr.toString()) - }) - .map(({ multiaddr, isCertified }) => ({ - multiaddr: multiaddr.bytes, - isCertified - })), - protocols: peer.protocols.sort(), - pubKey: peer.pubKey ? marshalPublicKey(peer.pubKey) : undefined, - metadata: [...peer.metadata.keys()].sort().map(key => ({ key, value: peer.metadata.get(key) })), - peerRecordEnvelope: peer.peerRecordEnvelope - }).finish() - - await this._datastore.put(this._peerIdToDatastoreKey(peer.id), buf) - - return this.load(peer.id) - } - - /** - * @param {PeerId} peerId - * @param {Partial} data - */ - async patch (peerId, data) { - const peer = await this.load(peerId) - - return await this._patch(peerId, data, peer) - } - - /** - * @param {PeerId} peerId - * @param {Partial} data - */ - async patchOrCreate (peerId, data) { - /** @type {Peer} */ - let peer - - try { - peer = await this.load(peerId) - } catch (/** @type {any} */ err) { - if (err.code !== codes.ERR_NOT_FOUND) { - throw err - } - - peer = { id: peerId, addresses: [], protocols: [], metadata: new Map() } - } - - return await this._patch(peerId, data, peer) - } - - /** - * @param {PeerId} peerId - * @param {Partial} data - * @param {Peer} peer - */ - async _patch (peerId, data, peer) { - return await this.save({ - ...peer, - ...data, - id: peerId - }) - } - - /** - * @param {PeerId} peerId - * @param {Partial} data - */ - async merge (peerId, data) { - const peer = await this.load(peerId) - - return this._merge(peerId, data, peer) - } - - /** - * @param {PeerId} peerId - * @param {Partial} data - */ - async mergeOrCreate (peerId, data) { - /** @type {Peer} */ - let peer - - try { - peer = await this.load(peerId) - } catch (/** @type {any} */ err) { - if (err.code !== codes.ERR_NOT_FOUND) { - throw err - } - - peer = { id: peerId, addresses: [], protocols: [], metadata: new Map() } - } - - return await this._merge(peerId, data, peer) - } - - /** - * @param {PeerId} peerId - * @param {Partial} data - * @param {Peer} peer - */ - async _merge (peerId, data, peer) { - // if the peer has certified addresses, use those in - // favour of the supplied versions - /** @type {Map} */ - const addresses = new Map() - - ;(data.addresses || []).forEach(addr => { - addresses.set(addr.multiaddr.toString(), addr.isCertified) - }) - - peer.addresses.forEach(({ multiaddr, isCertified }) => { - const addrStr = multiaddr.toString() - addresses.set(addrStr, Boolean(addresses.get(addrStr) || isCertified)) - }) - - return await this.save({ - id: peerId, - addresses: Array.from(addresses.entries()).map(([addrStr, isCertified]) => { - return { - multiaddr: new Multiaddr(addrStr), - isCertified - } - }), - protocols: Array.from(new Set([ - ...(peer.protocols || []), - ...(data.protocols || []) - ])), - metadata: new Map([ - ...(peer.metadata ? peer.metadata.entries() : []), - ...(data.metadata ? data.metadata.entries() : []) - ]), - pubKey: data.pubKey || (peer != null ? peer.pubKey : undefined), - peerRecordEnvelope: data.peerRecordEnvelope || (peer != null ? peer.peerRecordEnvelope : undefined) - }) - } - - async * all () { - for await (const key of this._datastore.queryKeys({ - prefix: NAMESPACE_COMMON - })) { - // /peers/${peer-id-as-libp2p-key-cid-string-in-base-32} - const base32Str = key.toString().split('/')[2] - const buf = base32.decode(base32Str) - - yield this.load(PeerId.createFromBytes(buf)) - } - } -} - -module.exports = PersistentStore diff --git a/src/peer-store/types.ts b/src/peer-store/types.ts deleted file mode 100644 index 0ae16b460d..0000000000 --- a/src/peer-store/types.ts +++ /dev/null @@ -1,245 +0,0 @@ -import type PeerId from 'peer-id' -import type { Multiaddr } from 'multiaddr' -import type Envelope from '../record/envelope' -import type { PublicKey } from 'libp2p-interfaces/src/keys/types' - -export interface Address { - /** - * Peer multiaddr - */ - multiaddr: Multiaddr - - /** - * Obtained from a signed peer record - */ - isCertified: boolean -} - -export interface Peer { - /** - * Peer's peer-id instance - */ - id: PeerId - - /** - * Peer's addresses containing its multiaddrs and metadata - */ - addresses: Address[] - - /** - * Peer's supported protocols - */ - protocols: string[] - - /** - * Peer's metadata map - */ - metadata: Map - - /** - * May be set if the key that this Peer has is an RSA key - */ - pubKey?: PublicKey - - /** - * The last peer record envelope received - */ - peerRecordEnvelope?: Uint8Array -} - -export interface CertifiedRecord { - raw: Uint8Array - seqNumber: number -} - -export interface AddressBookEntry { - addresses: Address[] - record: CertifiedRecord -} - -export interface Book { - /** - * Get the known data of a peer - */ - get: (peerId: PeerId) => Promise - - /** - * Set the known data of a peer - */ - set: (peerId: PeerId, data: Type) => Promise - - /** - * Remove the known data of a peer - */ - delete: (peerId: PeerId) => Promise -} - -/** - * AddressBook containing a map of peerIdStr to Address. - */ -export interface AddressBook { - /** - * 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 - */ - consumePeerRecord: (envelope: Envelope) => Promise - - /** - * Get the raw Envelope for a peer. Returns - * undefined if no Envelope is found - */ - getRawEnvelope: (peerId: PeerId) => Promise - - /** - * Get an Envelope containing a PeerRecord for the given peer. - * Returns undefined if no record exists. - */ - getPeerRecord: (peerId: PeerId) => Promise - - /** - * Add known addresses of a provided peer. - * If the peer is not known, it is set with the given addresses. - */ - add: (peerId: PeerId, multiaddrs: Multiaddr[]) => Promise - - /** - * Set the known addresses of a peer - */ - set: (peerId: PeerId, data: Multiaddr[]) => Promise - - /** - * Return the known addresses of a peer - */ - get: (peerId: PeerId) => Promise - - /** - * Get the known multiaddrs for a given peer. All returned multiaddrs - * will include the encapsulated `PeerId` of the peer. - */ - getMultiaddrsForPeer: (peerId: PeerId, addressSorter?: (ms: Address[]) => Address[]) => Promise -} - -/** - * KeyBook containing a map of peerIdStr to their PeerId with public keys. - */ -export interface KeyBook { - /** - * Get the known data of a peer - */ - get: (peerId: PeerId) => Promise - - /** - * Set the known data of a peer - */ - set: (peerId: PeerId, data: PublicKey) => Promise - - /** - * Remove the known data of a peer - */ - delete: (peerId: PeerId) => Promise -} - -/** - * MetadataBook containing a map of peerIdStr to their metadata Map. - */ -export interface MetadataBook extends Book> { - /** - * Set a specific metadata value - */ - setValue: (peerId: PeerId, key: string, value: Uint8Array) => Promise - - /** - * Get specific metadata value, if it exists - */ - getValue: (peerId: PeerId, key: string) => Promise - - /** - * Deletes the provided peer metadata key from the book - */ - deleteValue: (peerId: PeerId, key: string) => Promise -} - -/** - * ProtoBook containing a map of peerIdStr to supported protocols. - */ -export interface ProtoBook extends Book { - /** - * Adds known protocols of a provided peer. - * If the peer was not known before, it will be added. - */ - add: (peerId: PeerId, protocols: string[]) => Promise - - /** - * Removes known protocols of a provided peer. - * If the protocols did not exist before, nothing will be done. - */ - remove: (peerId: PeerId, protocols: string[]) => Promise -} - -export interface PeerProtocolsChangeEvent { - peerId: PeerId - protocols: string[] -} - -export interface PeerMultiaddrsChangeEvent { - peerId: PeerId - multiaddrs: Multiaddr[] -} - -export interface PeerPublicKeyChangeEvent { - peerId: PeerId - pubKey?: PublicKey -} - -export interface PeerMetadataChangeEvent { - peerId: PeerId - metadata: Map -} - -export type EventName = 'peer' | 'change:protocols' | 'change:multiaddrs' | 'change:pubkey' | 'change:metadata' - -export interface PeerStoreEvents { - 'peer': (event: PeerId) => void - 'change:protocols': (event: PeerProtocolsChangeEvent) => void - 'change:multiaddrs': (event: PeerMultiaddrsChangeEvent) => void - 'change:pubkey': (event: PeerPublicKeyChangeEvent) => void - 'change:metadata': (event: PeerMetadataChangeEvent) => void -} - -export interface PeerStore { - addressBook: AddressBook - keyBook: KeyBook - metadataBook: MetadataBook - protoBook: ProtoBook - - getPeers: () => AsyncIterable - delete: (peerId: PeerId) => Promise - has: (peerId: PeerId) => Promise - get: (peerId: PeerId) => Promise - on: ( - event: U, listener: PeerStoreEvents[U] - ) => this - once: ( - event: U, listener: PeerStoreEvents[U] - ) => this - emit: ( - event: U, ...args: Parameters - ) => boolean -} - -export interface Store { - has: (peerId: PeerId) => Promise - save: (peer: Peer) => Promise - load: (peerId: PeerId) => Promise - merge: (peerId: PeerId, data: Partial) => Promise - mergeOrCreate: (peerId: PeerId, data: Partial) => Promise - patch: (peerId: PeerId, data: Partial) => Promise - patchOrCreate: (peerId: PeerId, data: Partial) => Promise - all: () => AsyncIterable - - lock: { - readLock: () => Promise<() => void> - writeLock: () => Promise<() => void> - } -} diff --git a/src/ping/README.md b/src/ping/README.md index 079e956237..6dccd4be3c 100644 --- a/src/ping/README.md +++ b/src/ping/README.md @@ -8,7 +8,7 @@ libp2p-ping JavaScript Implementation ## Usage ```javascript -var Ping = require('libp2p/src/ping') +import Ping from 'libp2p/src/ping' Ping.mount(libp2p) // Enable this peer to echo Ping requests diff --git a/src/ping/constants.js b/src/ping/constants.js deleted file mode 100644 index 8ddd596d1e..0000000000 --- a/src/ping/constants.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict' - -module.exports = { - PROTOCOL: '/ipfs/ping/1.0.0', // deprecated - PING_LENGTH: 32, - PROTOCOL_VERSION: '1.0.0', - PROTOCOL_NAME: 'ping' -} diff --git a/src/ping/constants.ts b/src/ping/constants.ts new file mode 100644 index 0000000000..9411e75c6f --- /dev/null +++ b/src/ping/constants.ts @@ -0,0 +1,5 @@ + +export const PROTOCOL = '/ipfs/ping/1.0.0' +export const PING_LENGTH = 32 +export const PROTOCOL_VERSION = '1.0.0' +export const PROTOCOL_NAME = 'ping' diff --git a/src/ping/index.js b/src/ping/index.js deleted file mode 100644 index 46f87e3a6d..0000000000 --- a/src/ping/index.js +++ /dev/null @@ -1,84 +0,0 @@ -'use strict' - -const debug = require('debug') -const log = Object.assign(debug('libp2p:ping'), { - error: debug('libp2p:ping:err') -}) -const errCode = require('err-code') -const { codes } = require('../errors') -const crypto = require('libp2p-crypto') -const { pipe } = require('it-pipe') -// @ts-ignore it-buffer has no types exported -const { toBuffer } = require('it-buffer') -const { collect, take } = require('streaming-iterables') -const { equals } = require('uint8arrays/equals') - -const { PROTOCOL_NAME, PING_LENGTH, PROTOCOL_VERSION } = require('./constants') - -/** - * @typedef {import('../')} Libp2p - * @typedef {import('multiaddr').Multiaddr} Multiaddr - * @typedef {import('peer-id')} PeerId - * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream - */ - -class PingService { - /** - * @param {import('../')} libp2p - */ - static getProtocolStr (libp2p) { - return `/${libp2p._config.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}` - } - - /** - * @param {Libp2p} libp2p - */ - constructor (libp2p) { - this._libp2p = libp2p - } - - /** - * A handler to register with Libp2p to process ping messages - * - * @param {Object} options - * @param {MuxedStream} options.stream - */ - handleMessage ({ stream }) { - return pipe(stream, stream) - } - - /** - * Ping a given peer and wait for its response, getting the operation latency. - * - * @param {PeerId|Multiaddr} peer - * @returns {Promise} - */ - async ping (peer) { - const protocol = `/${this._libp2p._config.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}` - // @ts-ignore multiaddr might not have toB58String - log('dialing %s to %s', protocol, peer.toB58String ? peer.toB58String() : peer) - - const connection = await this._libp2p.dial(peer) - const { stream } = await connection.newStream(protocol) - - const start = Date.now() - const data = crypto.randomBytes(PING_LENGTH) - - const [result] = await pipe( - [data], - stream, - (/** @type {MuxedStream} */ stream) => take(1, stream), - toBuffer, - collect - ) - const end = Date.now() - - if (!equals(data, result)) { - throw errCode(new Error('Received wrong ping ack'), codes.ERR_WRONG_PING_ACK) - } - - return end - start - } -} - -module.exports = PingService diff --git a/src/ping/index.ts b/src/ping/index.ts new file mode 100644 index 0000000000..745d1dc404 --- /dev/null +++ b/src/ping/index.ts @@ -0,0 +1,83 @@ +import { logger } from '@libp2p/logger' +import errCode from 'err-code' +import { codes } from '../errors.js' +import { randomBytes } from '@libp2p/crypto' +import { pipe } from 'it-pipe' +import first from 'it-first' +import { equals as uint8ArrayEquals } from 'uint8arrays/equals' +import { PROTOCOL_NAME, PING_LENGTH, PROTOCOL_VERSION } from './constants.js' +import type { IncomingStreamData } from '@libp2p/interfaces/registrar' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { Startable } from '@libp2p/interfaces' +import type { Components } from '@libp2p/interfaces/components' + +const log = logger('libp2p:ping') + +export interface PingServiceInit { + protocolPrefix: string +} + +export class PingService implements Startable { + private readonly components: Components + private readonly protocol: string + private started: boolean + + constructor (components: Components, init: PingServiceInit) { + this.components = components + this.started = false + this.protocol = `/${init.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}` + } + + async start () { + await this.components.getRegistrar().handle(this.protocol, this.handleMessage) + this.started = true + } + + async stop () { + await this.components.getRegistrar().unhandle(this.protocol) + this.started = false + } + + isStarted () { + return this.started + } + + /** + * A handler to register with Libp2p to process ping messages + */ + handleMessage (data: IncomingStreamData) { + const { stream } = data + + void pipe(stream, stream) + .catch(err => { + log.error(err) + }) + } + + /** + * Ping a given peer and wait for its response, getting the operation latency. + * + * @param {PeerId|Multiaddr} peer + * @returns {Promise} + */ + async ping (peer: PeerId): Promise { + log('dialing %s to %p', this.protocol, peer) + + const { stream } = await this.components.getDialer().dialProtocol(peer, this.protocol) + const start = Date.now() + const data = randomBytes(PING_LENGTH) + + const result = await pipe( + [data], + stream, + async (source) => await first(source) + ) + const end = Date.now() + + if (result == null || !uint8ArrayEquals(data, result)) { + throw errCode(new Error('Received wrong ping ack'), codes.ERR_WRONG_PING_ACK) + } + + return end - start + } +} diff --git a/src/ping/util.js b/src/ping/util.js deleted file mode 100644 index e942420a5d..0000000000 --- a/src/ping/util.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict' - -const crypto = require('libp2p-crypto') -const constants = require('./constants') - -/** - * @param {number} length - */ -function rnd (length) { - if (!length) { - length = constants.PING_LENGTH - } - return crypto.randomBytes(length) -} - -module.exports = { - rnd -} diff --git a/src/pnet/README.md b/src/pnet/README.md index d3cce111cc..effcb189e2 100644 --- a/src/pnet/README.md +++ b/src/pnet/README.md @@ -20,7 +20,7 @@ js-libp2p-pnet ## Usage ```js -const Protector = require('libp2p-pnet') +const Protector from 'libp2p-pnet') const protector = new Protector(swarmKeyBuffer) const privateConnection = protector.protect(myPublicConnection, (err) => { }) ``` @@ -63,7 +63,7 @@ node -e "require('libp2p/src/pnet').generate(process.stdout)" > swarm.key #### Programmatically ```js -const writeKey = require('libp2p/src/pnet').generate +const writeKey from 'libp2p/src/pnet').generate const swarmKey = new Uint8Array(95) writeKey(swarmKey) fs.writeFileSync('swarm.key', swarmKey) diff --git a/src/pnet/crypto.js b/src/pnet/crypto.js deleted file mode 100644 index eb9cb22316..0000000000 --- a/src/pnet/crypto.js +++ /dev/null @@ -1,84 +0,0 @@ -'use strict' - -const debug = require('debug') -const log = Object.assign(debug('libp2p:pnet'), { - trace: debug('libp2p:pnet:trace'), - error: debug('libp2p:pnet:err') -}) - -const Errors = require('./errors') -// @ts-ignore xsalsa20 has no types exported -const xsalsa20 = require('xsalsa20') -const KEY_LENGTH = require('./key-generator').KEY_LENGTH -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') - -/** - * 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 - * @returns {*} a through iterable - */ -module.exports.createBoxStream = (nonce, psk) => { - const xor = xsalsa20(nonce, psk) - - return (/** @type {AsyncIterable} */ source) => (async function * () { - for await (const chunk of source) { - yield Uint8Array.from(xor.update(chunk.slice())) - } - })() -} - -/** - * 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 - * @returns {*} a through iterable - */ -module.exports.createUnboxStream = (nonce, psk) => { - return (/** @type {AsyncIterable} */ source) => (async function * () { - const xor = xsalsa20(nonce, psk) - log.trace('Decryption enabled') - - for await (const chunk of source) { - yield Uint8Array.from(xor.update(chunk.slice())) - } - })() -} - -/** - * Decode the version 1 psk from the given Uint8Array - * - * @param {Uint8Array} pskBuffer - * @throws {INVALID_PSK} - * @returns {{ tag?: string, codecName?: string, psk: Uint8Array }} The PSK metadata (tag, codecName, psk) - */ -module.exports.decodeV1PSK = (pskBuffer) => { - try { - // This should pull from multibase/multicodec to allow for - // more encoding flexibility. Ideally we'd consume the codecs - // from the buffer line by line to evaluate the next line - // programmatically instead of making assumptions about the - // encodings of each line. - const metadata = uint8ArrayToString(pskBuffer).split(/(?:\r\n|\r|\n)/g) - const pskTag = metadata.shift() - const codec = metadata.shift() - const pskString = metadata.shift() - const psk = pskString && uint8ArrayFromString(pskString, 'base16') - - if (!psk || psk.byteLength !== KEY_LENGTH) { - throw new Error(Errors.INVALID_PSK) - } - - return { - tag: pskTag, - codecName: codec, - psk: psk - } - } catch (/** @type {any} */ err) { - log.error(err) - throw new Error(Errors.INVALID_PSK) - } -} diff --git a/src/pnet/crypto.ts b/src/pnet/crypto.ts new file mode 100644 index 0000000000..ffc6b17d5d --- /dev/null +++ b/src/pnet/crypto.ts @@ -0,0 +1,67 @@ +import { logger } from '@libp2p/logger' +import * as Errors from './errors.js' +import xsalsa20 from 'xsalsa20' +import { KEY_LENGTH } from './key-generator.js' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import type { Source } from 'it-stream-types' + +const log = logger('libp2p:pnet') + +/** + * Creates a stream iterable to encrypt messages in a private network + */ +export function createBoxStream (nonce: Uint8Array, psk: Uint8Array) { + const xor = xsalsa20(nonce, psk) + + return (source: Source) => (async function * () { + for await (const chunk of source) { + yield Uint8Array.from(xor.update(chunk.slice())) + } + })() +} + +/** + * Creates a stream iterable to decrypt messages in a private network + */ +export function createUnboxStream (nonce: Uint8Array, psk: Uint8Array) { + return (source: Source) => (async function * () { + const xor = xsalsa20(nonce, psk) + log.trace('Decryption enabled') + + for await (const chunk of source) { + yield Uint8Array.from(xor.update(chunk.slice())) + } + })() +} + +/** + * Decode the version 1 psk from the given Uint8Array + */ +export function decodeV1PSK (pskBuffer: Uint8Array) { + try { + // This should pull from multibase/multicodec to allow for + // more encoding flexibility. Ideally we'd consume the codecs + // from the buffer line by line to evaluate the next line + // programmatically instead of making assumptions about the + // encodings of each line. + const metadata = uint8ArrayToString(pskBuffer).split(/(?:\r\n|\r|\n)/g) + const pskTag = metadata.shift() + const codec = metadata.shift() + const pskString = metadata.shift() + const psk = uint8ArrayFromString(pskString ?? '', 'base16') + + if (psk.byteLength !== KEY_LENGTH) { + throw new Error(Errors.INVALID_PSK) + } + + return { + tag: pskTag, + codecName: codec, + psk: psk + } + } catch (err: any) { + log.error(err) + throw new Error(Errors.INVALID_PSK) + } +} diff --git a/src/pnet/errors.js b/src/pnet/errors.js deleted file mode 100644 index 57c44228ce..0000000000 --- a/src/pnet/errors.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict' - -module.exports.INVALID_PEER = 'Not a valid peer connection' -module.exports.INVALID_PSK = 'Your private shared key is invalid' -module.exports.NO_LOCAL_ID = 'No local private key provided' -module.exports.NO_HANDSHAKE_CONNECTION = 'No connection for the handshake provided' -module.exports.STREAM_ENDED = 'Stream ended prematurely' diff --git a/src/pnet/errors.ts b/src/pnet/errors.ts new file mode 100644 index 0000000000..b09f68a2a1 --- /dev/null +++ b/src/pnet/errors.ts @@ -0,0 +1,5 @@ +export const INVALID_PEER = 'Not a valid peer connection' +export const INVALID_PSK = 'Your private shared key is invalid' +export const NO_LOCAL_ID = 'No local private key provided' +export const NO_HANDSHAKE_CONNECTION = 'No connection for the handshake provided' +export const STREAM_ENDED = 'Stream ended prematurely' diff --git a/src/pnet/index.js b/src/pnet/index.js deleted file mode 100644 index c54e1f9c4d..0000000000 --- a/src/pnet/index.js +++ /dev/null @@ -1,86 +0,0 @@ -'use strict' - -const debug = require('debug') -const log = Object.assign(debug('libp2p:pnet'), { - error: debug('libp2p:pnet:err') -}) -const { pipe } = require('it-pipe') -const errcode = require('err-code') -// @ts-ignore it-pair has no types exported -const duplexPair = require('it-pair/duplex') -const crypto = require('libp2p-crypto') -const Errors = require('./errors') -const { - codes: { ERR_INVALID_PARAMETERS } -} = require('../errors') -const { - createBoxStream, - createUnboxStream, - decodeV1PSK -} = require('./crypto') -// @ts-ignore it-handshake has no types exported -const handshake = require('it-handshake') -const { NONCE_LENGTH } = require('./key-generator') - -/** - * @typedef {import('libp2p-interfaces/src/transport/types').MultiaddrConnection} MultiaddrConnection - */ - -class Protector { - /** - * Takes a Private Shared Key (psk) and provides a `protect` method - * for wrapping existing connections in a private encryption stream. - * - * @param {Uint8Array} keyBuffer - The private shared key buffer - * @class - */ - constructor (keyBuffer) { - const decodedPSK = decodeV1PSK(keyBuffer) - this.psk = decodedPSK.psk - this.tag = decodedPSK.tag - } - - /** - * Takes a given Connection and creates a private encryption stream - * between its two peers from the PSK the Protector instance was - * created with. - * - * @param {MultiaddrConnection} connection - The connection to protect - * @returns {Promise} A protected duplex iterable - */ - async protect (connection) { - if (!connection) { - throw errcode(new Error(Errors.NO_HANDSHAKE_CONNECTION), ERR_INVALID_PARAMETERS) - } - - // Exchange nonces - log('protecting the connection') - const localNonce = crypto.randomBytes(NONCE_LENGTH) - - const shake = handshake(connection) - shake.write(localNonce) - - const result = await shake.reader.next(NONCE_LENGTH) - const remoteNonce = result.value.slice() - shake.rest() - - // Create the boxing/unboxing pipe - log('exchanged nonces') - const [internal, external] = duplexPair() - pipe( - external, - // Encrypt all outbound traffic - createBoxStream(localNonce, this.psk), - shake.stream, - // Decrypt all inbound traffic - createUnboxStream(remoteNonce, this.psk), - external - ).catch(log.error) - - return internal - } -} - -module.exports = Protector -module.exports.errors = Errors -module.exports.generate = require('./key-generator') diff --git a/src/pnet/index.ts b/src/pnet/index.ts new file mode 100644 index 0000000000..868b43084d --- /dev/null +++ b/src/pnet/index.ts @@ -0,0 +1,95 @@ +import { logger } from '@libp2p/logger' +import { pipe } from 'it-pipe' +import errCode from 'err-code' +import { duplexPair } from 'it-pair/duplex' +import { randomBytes } from '@libp2p/crypto' +import * as Errors from './errors.js' +import { codes } from '../errors.js' +import { + createBoxStream, + createUnboxStream, + decodeV1PSK +} from './crypto.js' +import { handshake } from 'it-handshake' +import { NONCE_LENGTH } from './key-generator.js' +import type { MultiaddrConnection } from '@libp2p/interfaces/transport' +import type { ConnectionProtector } from '@libp2p/interfaces/connection' + +const log = logger('libp2p:pnet') + +export interface ProtectorInit { + enabled?: boolean + psk: Uint8Array +} + +export class PreSharedKeyConnectionProtector implements ConnectionProtector { + public tag: string + private readonly psk: Uint8Array + private readonly enabled: boolean + + /** + * Takes a Private Shared Key (psk) and provides a `protect` method + * for wrapping existing connections in a private encryption stream. + */ + constructor (init: ProtectorInit) { + this.enabled = init.enabled !== false + + if (this.enabled) { + const decodedPSK = decodeV1PSK(init.psk) + this.psk = decodedPSK.psk + this.tag = decodedPSK.tag ?? '' + } else { + this.psk = new Uint8Array() + this.tag = '' + } + } + + /** + * Takes a given Connection and creates a private encryption stream + * between its two peers from the PSK the Protector instance was + * created with. + */ + async protect (connection: MultiaddrConnection): Promise { + if (!this.enabled) { + return connection + } + + if (connection == null) { + throw errCode(new Error(Errors.NO_HANDSHAKE_CONNECTION), codes.ERR_INVALID_PARAMETERS) + } + + // Exchange nonces + log('protecting the connection') + const localNonce = randomBytes(NONCE_LENGTH) + + const shake = handshake(connection) + shake.write(localNonce) + + const result = await shake.reader.next(NONCE_LENGTH) + + if (result.value == null) { + throw errCode(new Error(Errors.STREAM_ENDED), codes.ERR_INVALID_PARAMETERS) + } + + const remoteNonce = result.value.slice() + shake.rest() + + // Create the boxing/unboxing pipe + log('exchanged nonces') + const [internal, external] = duplexPair() + pipe( + external, + // Encrypt all outbound traffic + createBoxStream(localNonce, this.psk), + shake.stream, + // Decrypt all inbound traffic + createUnboxStream(remoteNonce, this.psk), + external + ).catch(log.error) + + return { + ...connection, + ...internal + } + } +} diff --git a/src/pnet/key-generator.js b/src/pnet/key-generator.js deleted file mode 100644 index ad94b14083..0000000000 --- a/src/pnet/key-generator.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict' - -const crypto = require('libp2p-crypto') -const KEY_LENGTH = 32 -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') -const { fromString: 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 - * @returns {void} - */ -function generate (bytes) { - const psk = uint8ArrayToString(crypto.randomBytes(KEY_LENGTH), 'base16') - const key = uint8ArrayFromString('/key/swarm/psk/1.0.0/\n/base16/\n' + psk) - - bytes.set(key) -} - -module.exports = generate -module.exports.NONCE_LENGTH = 24 -module.exports.KEY_LENGTH = KEY_LENGTH - -try { - // @ts-ignore This condition will always return 'false' since the types 'Module | undefined' - if (require.main === module) { - // @ts-ignore - generate(process.stdout) - } -} catch (/** @type {any} */ error) { - -} diff --git a/src/pnet/key-generator.ts b/src/pnet/key-generator.ts new file mode 100644 index 0000000000..af3b46c1a6 --- /dev/null +++ b/src/pnet/key-generator.ts @@ -0,0 +1,28 @@ +import { randomBytes } from '@libp2p/crypto' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { fromString as uint8ArrayFromString } from '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 + * @returns {void} + */ +export function generate (bytes: Uint8Array) { + const psk = uint8ArrayToString(randomBytes(KEY_LENGTH), 'base16') + const key = uint8ArrayFromString('/key/swarm/psk/1.0.0/\n/base16/\n' + psk) + + bytes.set(key) +} + +export const NONCE_LENGTH = 24 +export const KEY_LENGTH = 32 + +try { + if (require.main === module) { + // @ts-expect-error + generate(process.stdout) + } +} catch (error: any) { + +} diff --git a/src/pubsub-adapter.js b/src/pubsub-adapter.js deleted file mode 100644 index 8ccbf90f48..0000000000 --- a/src/pubsub-adapter.js +++ /dev/null @@ -1,61 +0,0 @@ -'use strict' - -// Pubsub adapter to keep API with handlers while not removed. -/** - * @typedef {import('libp2p-interfaces/src/pubsub').InMessage} InMessage - * @typedef {import('libp2p-interfaces/src/pubsub')} PubsubRouter - */ - -/** - * @param {{new(...args: any[]): PubsubRouter}} PubsubRouter - * @param {import('.')} libp2p - * @param {{ enabled: boolean; } & import(".").PubsubLocalOptions & import("libp2p-interfaces/src/pubsub").PubsubOptions} options - */ -function pubsubAdapter (PubsubRouter, libp2p, options) { - /** @type {PubsubRouter & { _subscribeAdapter: PubsubRouter['subscribe'], _unsubscribeAdapter: PubsubRouter['unsubscribe'] }} */ - // @ts-ignore we set the extra _subscribeAdapter and _unsubscribeAdapter properties afterwards - const pubsub = new PubsubRouter(libp2p, options) - pubsub._subscribeAdapter = pubsub.subscribe - pubsub._unsubscribeAdapter = pubsub.unsubscribe - - /** - * Subscribes to a given topic. - * - * @override - * @param {string} topic - * @param {(msg: InMessage) => void} [handler] - * @returns {void} - */ - function subscribe (topic, handler) { - // Bind provided handler - handler && pubsub.on(topic, handler) - pubsub._subscribeAdapter(topic) - } - - /** - * Unsubscribe from the given topic. - * - * @override - * @param {string} topic - * @param {(msg: InMessage) => void} [handler] - * @returns {void} - */ - function unsubscribe (topic, handler) { - if (!handler) { - pubsub.removeAllListeners(topic) - } else { - pubsub.removeListener(topic, handler) - } - - if (pubsub.listenerCount(topic) === 0) { - pubsub._unsubscribeAdapter(topic) - } - } - - pubsub.subscribe = subscribe - pubsub.unsubscribe = unsubscribe - - return pubsub -} - -module.exports = pubsubAdapter diff --git a/src/record/README.md b/src/record/README.md deleted file mode 100644 index 761cd23477..0000000000 --- a/src/record/README.md +++ /dev/null @@ -1,130 +0,0 @@ -# Libp2p Records - -Libp2p nodes need to store data in a public location (e.g. a DHT), or rely on potentially untrustworthy intermediaries to relay information over its lifetime. Accordingly, libp2p nodes need to be able to verify that the data came from a specific peer and that it hasn't been tampered with. - -## Envelope - -Libp2p provides an all-purpose data container called **envelope**. It was created to enable the distribution of verifiable records, which we can prove originated from the addressed peer itself. The envelope includes a signature of the data, so that its authenticity is verified. - -This envelope stores a marshaled record implementing the [interface-record](https://github.com/libp2p/js-libp2p-interfaces/tree/master/src/record). These Records are designed to be serialized to bytes and placed inside of the envelopes before being shared with other peers. - -You can read further about the envelope in [libp2p/specs#217](https://github.com/libp2p/specs/pull/217). - -### Usage - -- create an envelope with an instance of an [interface-record](https://github.com/libp2p/js-libp2p-interfaces/tree/master/src/record) implementation and prepare it for being exchanged: - -```js -// interface-record implementation example with the "libp2p-example" namespace -const Record = require('libp2p-interfaces/src/record') -const { fromString } = require('uint8arrays/from-string') - -class ExampleRecord extends Record { - constructor () { - super ('libp2p-example', fromString('0302', 'hex')) - } - - marshal () {} - - equals (other) {} -} - -ExampleRecord.createFromProtobuf = () => {} -``` - -```js -const Envelope = require('libp2p/src/record/envelop') -const ExampleRecord = require('./example-record') - -const rec = new ExampleRecord() -const e = await Envelope.seal(rec, peerId) -const wireData = e.marshal() -``` - -- consume a received envelope (`wireData`) and transform it back to a record: - -```js -const Envelope = require('libp2p/src/record/envelop') -const ExampleRecord = require('./example-record') - -const domain = 'libp2p-example' -let e - -try { - e = await Envelope.openAndCertify(wireData, domain) -} catch (err) {} - -const rec = ExampleRecord.createFromProtobuf(e.payload) -``` - -## Peer Record - -All libp2p nodes keep a `PeerStore`, that among other information stores a set of known addresses for each peer, which can come from a variety of sources. - -Libp2p peer records were created to enable the distribution of verifiable address records, which we can prove originated from the addressed peer itself. With such guarantees, libp2p is able to prioritize addresses based on their authenticity, with the most strict strategy being to only dial certified addresses (no strategies have been implemented at the time of writing). - -A peer record contains the peers' publicly reachable listen addresses, and may be extended in the future to contain additional metadata relevant to routing. It also contains a `seqNumber` field, a timestamp per the spec, so that we can verify the most recent record. - -You can read further about the Peer Record in [libp2p/specs#217](https://github.com/libp2p/specs/pull/217). - -### Usage - -- create a new Peer Record - -```js -const PeerRecord = require('libp2p/src/record/peer-record') - -const pr = new PeerRecord({ - peerId: node.peerId, - multiaddrs: node.multiaddrs -}) -``` - -- create a Peer Record from a protobuf - -```js -const PeerRecord = require('libp2p/src/record/peer-record') - -const pr = PeerRecord.createFromProtobuf(data) -``` - -### Libp2p Flows - -#### Self Record - -Once a libp2p node has started and is listening on a set of multiaddrs, its own peer record can be created. - -The identify service is responsible for creating the self record when the identify protocol kicks in for the first time. This record will be stored for future needs of the identify protocol when connecting with other peers. - -#### Self record Updates - -**_NOT_YET_IMPLEMENTED_** - -While creating peer records is fairly trivial, addresses are not static and might be modified at arbitrary times. This can happen via an Address Manager API, or even through AutoRelay/AutoNAT. - -When a libp2p node changes its listen addresses, the identify service will be informed. Once that happens, the identify service creates a new self record and stores it. With the new record, the identify push/delta protocol will be used to communicate this change to the connected peers. - -#### Subsystem receiving a record - -Considering that a node can discover other peers' addresses from a variety of sources, Libp2p Peerstore can differentiate the addresses that were obtained through a signed peer record. - -Once a record is received and its signature properly validated, its envelope is stored in the AddressBook in its byte representation. The `seqNumber` remains unmarshalled so that we can quickly compare it against incoming records to determine the most recent record. - -The AddressBook Addresses will be updated with the content of the envelope with a certified property. This allows other subsystems to identify the known certified addresses of a peer. - -#### Subsystem providing a record - -Libp2p subsystems that exchange other peers information will provide the envelope that they received by those peers. As a result, other peers can verify if the envelope was really created by the addressed peer. - -When a subsystem wants to provide a record, it will get it from the AddressBook, if it exists. Other subsystems are also able to provide the self record, since it is also stored in the AddressBook. - -### Future Work - -- Persistence only considering certified addresses? -- Peers may not know their own addresses. It's often impossible to automatically infer one's own public address, and peers may need to rely on third party peers to inform them of their observed public addresses. -- A peer may inadvertently or maliciously sign an address that they do not control. In other words, a signature isn't a guarantee that a given address is valid. -- Some addresses may be ambiguous. For example, addresses on a private subnet are valid within that subnet but are useless on the public internet. -- Once all these pieces are in place, we will also need a way to prioritize addresses based on their authenticity, that is, the dialer can prioritize self-certified addresses over addresses from an unknown origin. - - Modular dialer? (taken from go PR notes) - - With the modular dialer, users should easily be able to configure precedence. With dialer v1, anything we do to prioritise dials is gonna be spaghetti and adhoc. With the modular dialer, you’d be able to specify the order of dials when instantiating the pipeline. - - Multiple parallel dials. We already have the issue where new addresses aren't added to existing dials. diff --git a/src/record/envelope/envelope.d.ts b/src/record/envelope/envelope.d.ts deleted file mode 100644 index 440590c14b..0000000000 --- a/src/record/envelope/envelope.d.ts +++ /dev/null @@ -1,77 +0,0 @@ -import * as $protobuf from "protobufjs"; -/** Properties of an Envelope. */ -export interface IEnvelope { - - /** Envelope publicKey */ - publicKey?: (Uint8Array|null); - - /** Envelope payloadType */ - payloadType?: (Uint8Array|null); - - /** Envelope payload */ - payload?: (Uint8Array|null); - - /** Envelope signature */ - signature?: (Uint8Array|null); -} - -/** Represents an Envelope. */ -export class Envelope implements IEnvelope { - - /** - * Constructs a new Envelope. - * @param [p] Properties to set - */ - constructor(p?: IEnvelope); - - /** Envelope publicKey. */ - public publicKey: Uint8Array; - - /** Envelope payloadType. */ - public payloadType: Uint8Array; - - /** Envelope payload. */ - public payload: Uint8Array; - - /** Envelope signature. */ - public signature: Uint8Array; - - /** - * Encodes the specified Envelope message. Does not implicitly {@link Envelope.verify|verify} messages. - * @param m Envelope message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: IEnvelope, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes an Envelope message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns Envelope - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Envelope; - - /** - * Creates an Envelope message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns Envelope - */ - public static fromObject(d: { [k: string]: any }): Envelope; - - /** - * Creates a plain object from an Envelope message. Also converts values to other types if specified. - * @param m Envelope - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: Envelope, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this Envelope to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; -} diff --git a/src/record/envelope/envelope.js b/src/record/envelope/envelope.js deleted file mode 100644 index 8741154eac..0000000000 --- a/src/record/envelope/envelope.js +++ /dev/null @@ -1,243 +0,0 @@ -/*eslint-disable*/ -"use strict"; - -var $protobuf = require("protobufjs/minimal"); - -// Common aliases -var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; - -// Exported root namespace -var $root = $protobuf.roots["libp2p-envelope"] || ($protobuf.roots["libp2p-envelope"] = {}); - -$root.Envelope = (function() { - - /** - * Properties of an Envelope. - * @exports IEnvelope - * @interface IEnvelope - * @property {Uint8Array|null} [publicKey] Envelope publicKey - * @property {Uint8Array|null} [payloadType] Envelope payloadType - * @property {Uint8Array|null} [payload] Envelope payload - * @property {Uint8Array|null} [signature] Envelope signature - */ - - /** - * Constructs a new Envelope. - * @exports Envelope - * @classdesc Represents an Envelope. - * @implements IEnvelope - * @constructor - * @param {IEnvelope=} [p] Properties to set - */ - function Envelope(p) { - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * Envelope publicKey. - * @member {Uint8Array} publicKey - * @memberof Envelope - * @instance - */ - Envelope.prototype.publicKey = $util.newBuffer([]); - - /** - * Envelope payloadType. - * @member {Uint8Array} payloadType - * @memberof Envelope - * @instance - */ - Envelope.prototype.payloadType = $util.newBuffer([]); - - /** - * Envelope payload. - * @member {Uint8Array} payload - * @memberof Envelope - * @instance - */ - Envelope.prototype.payload = $util.newBuffer([]); - - /** - * Envelope signature. - * @member {Uint8Array} signature - * @memberof Envelope - * @instance - */ - Envelope.prototype.signature = $util.newBuffer([]); - - /** - * Encodes the specified Envelope message. Does not implicitly {@link Envelope.verify|verify} messages. - * @function encode - * @memberof Envelope - * @static - * @param {IEnvelope} m Envelope message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Envelope.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.publicKey != null && Object.hasOwnProperty.call(m, "publicKey")) - w.uint32(10).bytes(m.publicKey); - if (m.payloadType != null && Object.hasOwnProperty.call(m, "payloadType")) - w.uint32(18).bytes(m.payloadType); - if (m.payload != null && Object.hasOwnProperty.call(m, "payload")) - w.uint32(26).bytes(m.payload); - if (m.signature != null && Object.hasOwnProperty.call(m, "signature")) - w.uint32(42).bytes(m.signature); - return w; - }; - - /** - * Decodes an Envelope message from the specified reader or buffer. - * @function decode - * @memberof Envelope - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {Envelope} Envelope - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Envelope.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.Envelope(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.publicKey = r.bytes(); - break; - case 2: - m.payloadType = r.bytes(); - break; - case 3: - m.payload = r.bytes(); - break; - case 5: - m.signature = r.bytes(); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates an Envelope message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof Envelope - * @static - * @param {Object.} d Plain object - * @returns {Envelope} Envelope - */ - Envelope.fromObject = function fromObject(d) { - if (d instanceof $root.Envelope) - return d; - var m = new $root.Envelope(); - if (d.publicKey != null) { - if (typeof d.publicKey === "string") - $util.base64.decode(d.publicKey, m.publicKey = $util.newBuffer($util.base64.length(d.publicKey)), 0); - else if (d.publicKey.length) - m.publicKey = d.publicKey; - } - if (d.payloadType != null) { - if (typeof d.payloadType === "string") - $util.base64.decode(d.payloadType, m.payloadType = $util.newBuffer($util.base64.length(d.payloadType)), 0); - else if (d.payloadType.length) - m.payloadType = d.payloadType; - } - if (d.payload != null) { - if (typeof d.payload === "string") - $util.base64.decode(d.payload, m.payload = $util.newBuffer($util.base64.length(d.payload)), 0); - else if (d.payload.length) - m.payload = d.payload; - } - if (d.signature != null) { - if (typeof d.signature === "string") - $util.base64.decode(d.signature, m.signature = $util.newBuffer($util.base64.length(d.signature)), 0); - else if (d.signature.length) - m.signature = d.signature; - } - return m; - }; - - /** - * Creates a plain object from an Envelope message. Also converts values to other types if specified. - * @function toObject - * @memberof Envelope - * @static - * @param {Envelope} m Envelope - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - Envelope.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.defaults) { - if (o.bytes === String) - d.publicKey = ""; - else { - d.publicKey = []; - if (o.bytes !== Array) - d.publicKey = $util.newBuffer(d.publicKey); - } - if (o.bytes === String) - d.payloadType = ""; - else { - d.payloadType = []; - if (o.bytes !== Array) - d.payloadType = $util.newBuffer(d.payloadType); - } - if (o.bytes === String) - d.payload = ""; - else { - d.payload = []; - if (o.bytes !== Array) - d.payload = $util.newBuffer(d.payload); - } - if (o.bytes === String) - d.signature = ""; - else { - d.signature = []; - if (o.bytes !== Array) - d.signature = $util.newBuffer(d.signature); - } - } - if (m.publicKey != null && m.hasOwnProperty("publicKey")) { - d.publicKey = o.bytes === String ? $util.base64.encode(m.publicKey, 0, m.publicKey.length) : o.bytes === Array ? Array.prototype.slice.call(m.publicKey) : m.publicKey; - } - if (m.payloadType != null && m.hasOwnProperty("payloadType")) { - d.payloadType = o.bytes === String ? $util.base64.encode(m.payloadType, 0, m.payloadType.length) : o.bytes === Array ? Array.prototype.slice.call(m.payloadType) : m.payloadType; - } - if (m.payload != null && m.hasOwnProperty("payload")) { - d.payload = o.bytes === String ? $util.base64.encode(m.payload, 0, m.payload.length) : o.bytes === Array ? Array.prototype.slice.call(m.payload) : m.payload; - } - if (m.signature != null && m.hasOwnProperty("signature")) { - d.signature = o.bytes === String ? $util.base64.encode(m.signature, 0, m.signature.length) : o.bytes === Array ? Array.prototype.slice.call(m.signature) : m.signature; - } - return d; - }; - - /** - * Converts this Envelope to JSON. - * @function toJSON - * @memberof Envelope - * @instance - * @returns {Object.} JSON object - */ - Envelope.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return Envelope; -})(); - -module.exports = $root; diff --git a/src/record/envelope/envelope.proto b/src/record/envelope/envelope.proto deleted file mode 100644 index 5b80cf504c..0000000000 --- a/src/record/envelope/envelope.proto +++ /dev/null @@ -1,19 +0,0 @@ -syntax = "proto3"; - -message Envelope { - // public_key is the public key of the keypair the enclosed payload was - // signed with. - bytes public_key = 1; - - // payload_type encodes the type of payload, so that it can be deserialized - // deterministically. - bytes payload_type = 2; - - // payload is the actual payload carried inside this envelope. - bytes payload = 3; - - // signature is the signature produced by the private key corresponding to - // the enclosed public key, over the payload, prefixing a domain string for - // additional security. - bytes signature = 5; -} \ No newline at end of file diff --git a/src/record/envelope/index.js b/src/record/envelope/index.js deleted file mode 100644 index 4fadb4f0ba..0000000000 --- a/src/record/envelope/index.js +++ /dev/null @@ -1,183 +0,0 @@ -'use strict' - -const errCode = require('err-code') -const { concat: uint8arraysConcat } = require('uint8arrays/concat') -const { fromString: uint8arraysFromString } = require('uint8arrays/from-string') -// @ts-ignore libp2p-crypto does not support types -const cryptoKeys = require('libp2p-crypto/src/keys') -const PeerId = require('peer-id') -const varint = require('varint') -const { equals: uint8arraysEquals } = require('uint8arrays/equals') - -const { codes } = require('../../errors') -const { Envelope: Protobuf } = require('./envelope') - -/** - * @typedef {import('libp2p-interfaces/src/record/types').Record} Record - */ - -class Envelope { - /** - * The Envelope is responsible for keeping an arbitrary signed record - * by a libp2p peer. - * - * @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. - */ - constructor ({ peerId, payloadType, payload, signature }) { - this.peerId = peerId - this.payloadType = payloadType - this.payload = payload - this.signature = signature - - // Cache - this._marshal = undefined - } - - /** - * Marshal the envelope content. - * - * @returns {Uint8Array} - */ - marshal () { - if (this._marshal) { - return this._marshal - } - - const publicKey = cryptoKeys.marshalPublicKey(this.peerId.pubKey) - - this._marshal = Protobuf.encode({ - publicKey: publicKey, - payloadType: this.payloadType, - payload: this.payload, - signature: this.signature - }).finish() - - return this._marshal - } - - /** - * Verifies if the other Envelope is identical to this one. - * - * @param {Envelope} other - * @returns {boolean} - */ - equals (other) { - return uint8arraysEquals(this.peerId.pubKey.bytes, other.peerId.pubKey.bytes) && - uint8arraysEquals(this.payloadType, other.payloadType) && - uint8arraysEquals(this.payload, other.payload) && - uint8arraysEquals(this.signature, other.signature) - } - - /** - * Validate envelope data signature for the given domain. - * - * @param {string} domain - * @returns {Promise} - */ - validate (domain) { - const signData = formatSignaturePayload(domain, this.payloadType, this.payload) - - return this.peerId.pubKey.verify(signData, this.signature) - } -} - -/** - * Helper function that prepares a Uint8Array to sign or verify a signature. - * - * @param {string} domain - * @param {Uint8Array} payloadType - * @param {Uint8Array} payload - * @returns {Uint8Array} - */ -const formatSignaturePayload = (domain, payloadType, payload) => { - // When signing, a peer will prepare a Uint8Array by concatenating the following: - // - The length of the domain separation string string in bytes - // - The domain separation string, encoded as UTF-8 - // - The length of the payload_type field in bytes - // - The value of the payload_type field - // - The length of the payload field in bytes - // - The value of the payload field - - const domainUint8Array = uint8arraysFromString(domain) - const domainLength = varint.encode(domainUint8Array.byteLength) - const payloadTypeLength = varint.encode(payloadType.length) - const payloadLength = varint.encode(payload.length) - - return uint8arraysConcat([ - new Uint8Array(domainLength), - domainUint8Array, - new Uint8Array(payloadTypeLength), - payloadType, - new Uint8Array(payloadLength), - payload - ]) -} - -/** - * Unmarshal a serialized Envelope protobuf message. - * - * @param {Uint8Array} data - * @returns {Promise} - */ -Envelope.createFromProtobuf = async (data) => { - const envelopeData = Protobuf.decode(data) - const peerId = await PeerId.createFromPubKey(envelopeData.publicKey) - - return new Envelope({ - peerId, - payloadType: envelopeData.payloadType, - payload: envelopeData.payload, - signature: envelopeData.signature - }) -} - -/** - * 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 {Promise} - */ -Envelope.seal = async (record, peerId) => { - const domain = record.domain - const payloadType = record.codec - const payload = record.marshal() - - const signData = formatSignaturePayload(domain, payloadType, payload) - const signature = await peerId.privKey.sign(signData) - - return new Envelope({ - peerId, - payloadType, - payload, - signature - }) -} - -/** - * Open and certify a given marshalled envelope. - * Data is unmarshalled and the signature validated for the given domain. - * - * @param {Uint8Array} data - * @param {string} domain - * @returns {Promise} - */ -Envelope.openAndCertify = async (data, domain) => { - const envelope = await Envelope.createFromProtobuf(data) - const valid = await envelope.validate(domain) - - if (!valid) { - throw errCode(new Error('envelope signature is not valid for the given domain'), codes.ERR_SIGNATURE_NOT_VALID) - } - - return envelope -} - -module.exports = Envelope diff --git a/src/record/peer-record/consts.js b/src/record/peer-record/consts.js deleted file mode 100644 index 9b35427ec7..0000000000 --- a/src/record/peer-record/consts.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict' - -// The domain string used for peer records contained in a Envelope. -const domain = 'libp2p-peer-record' - -// The type hint used to identify peer records in a Envelope. -// Defined in https://github.com/multiformats/multicodec/blob/master/table.csv -// with name "libp2p-peer-record" -const payloadType = Uint8Array.from([3, 1]) - -module.exports = { - ENVELOPE_DOMAIN_PEER_RECORD: domain, - ENVELOPE_PAYLOAD_TYPE_PEER_RECORD: payloadType -} diff --git a/src/record/peer-record/index.js b/src/record/peer-record/index.js deleted file mode 100644 index bcf637b87a..0000000000 --- a/src/record/peer-record/index.js +++ /dev/null @@ -1,113 +0,0 @@ -'use strict' - -const { Multiaddr } = require('multiaddr') -const PeerId = require('peer-id') -const arrayEquals = require('libp2p-utils/src/array-equals') - -const { PeerRecord: Protobuf } = require('./peer-record') -const { - ENVELOPE_DOMAIN_PEER_RECORD, - ENVELOPE_PAYLOAD_TYPE_PEER_RECORD -} = require('./consts') - -/** - * @typedef {import('../../peer-store/types').Address} Address - * @typedef {import('libp2p-interfaces/src/record/types').Record} Record - */ - -/** - * @implements {Record} - */ -class PeerRecord { - /** - * The PeerRecord is used for distributing peer routing records across the network. - * It contains the peer's reachable listen addresses. - * - * @class - * @param {Object} params - * @param {PeerId} params.peerId - * @param {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() }) { - this.domain = ENVELOPE_DOMAIN_PEER_RECORD - this.codec = ENVELOPE_PAYLOAD_TYPE_PEER_RECORD - - this.peerId = peerId - this.multiaddrs = multiaddrs - this.seqNumber = seqNumber - - // Cache - this._marshal = undefined - } - - /** - * Marshal a record to be used in an envelope. - * - * @returns {Uint8Array} - */ - marshal () { - if (this._marshal) { - return this._marshal - } - - this._marshal = Protobuf.encode({ - peerId: this.peerId.toBytes(), - seq: this.seqNumber, - addresses: this.multiaddrs.map((m) => ({ - multiaddr: m.bytes - })) - }).finish() - - return this._marshal - } - - /** - * Returns true if `this` record equals the `other`. - * - * @param {unknown} other - * @returns {boolean} - */ - equals (other) { - if (!(other instanceof PeerRecord)) { - return false - } - - // Validate PeerId - if (!this.peerId.equals(other.peerId)) { - return false - } - - // Validate seqNumber - if (this.seqNumber !== other.seqNumber) { - return false - } - - // Validate multiaddrs - if (!arrayEquals(this.multiaddrs, other.multiaddrs)) { - return false - } - - return true - } -} - -/** - * Unmarshal Peer Record Protobuf. - * - * @param {Uint8Array} buf - marshaled peer record. - * @returns {PeerRecord} - */ -PeerRecord.createFromProtobuf = (buf) => { - const peerRecord = Protobuf.decode(buf) - - const peerId = PeerId.createFromBytes(peerRecord.peerId) - const multiaddrs = (peerRecord.addresses || []).map((a) => new Multiaddr(a.multiaddr)) - const seqNumber = Number(peerRecord.seq) - - return new PeerRecord({ peerId, multiaddrs, seqNumber }) -} - -PeerRecord.DOMAIN = ENVELOPE_DOMAIN_PEER_RECORD - -module.exports = PeerRecord diff --git a/src/record/peer-record/peer-record.d.ts b/src/record/peer-record/peer-record.d.ts deleted file mode 100644 index a851b53307..0000000000 --- a/src/record/peer-record/peer-record.d.ts +++ /dev/null @@ -1,133 +0,0 @@ -import * as $protobuf from "protobufjs"; -/** Properties of a PeerRecord. */ -export interface IPeerRecord { - - /** PeerRecord peerId */ - peerId?: (Uint8Array|null); - - /** PeerRecord seq */ - seq?: (number|null); - - /** PeerRecord addresses */ - addresses?: (PeerRecord.IAddressInfo[]|null); -} - -/** Represents a PeerRecord. */ -export class PeerRecord implements IPeerRecord { - - /** - * Constructs a new PeerRecord. - * @param [p] Properties to set - */ - constructor(p?: IPeerRecord); - - /** PeerRecord peerId. */ - public peerId: Uint8Array; - - /** PeerRecord seq. */ - public seq: number; - - /** PeerRecord addresses. */ - public addresses: PeerRecord.IAddressInfo[]; - - /** - * Encodes the specified PeerRecord message. Does not implicitly {@link PeerRecord.verify|verify} messages. - * @param m PeerRecord message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: IPeerRecord, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a PeerRecord message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns PeerRecord - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): PeerRecord; - - /** - * Creates a PeerRecord message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns PeerRecord - */ - public static fromObject(d: { [k: string]: any }): PeerRecord; - - /** - * Creates a plain object from a PeerRecord message. Also converts values to other types if specified. - * @param m PeerRecord - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: PeerRecord, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this PeerRecord to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; -} - -export namespace PeerRecord { - - /** Properties of an AddressInfo. */ - interface IAddressInfo { - - /** AddressInfo multiaddr */ - multiaddr?: (Uint8Array|null); - } - - /** Represents an AddressInfo. */ - class AddressInfo implements IAddressInfo { - - /** - * Constructs a new AddressInfo. - * @param [p] Properties to set - */ - constructor(p?: PeerRecord.IAddressInfo); - - /** AddressInfo multiaddr. */ - public multiaddr: Uint8Array; - - /** - * Encodes the specified AddressInfo message. Does not implicitly {@link PeerRecord.AddressInfo.verify|verify} messages. - * @param m AddressInfo message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: PeerRecord.IAddressInfo, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes an AddressInfo message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns AddressInfo - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): PeerRecord.AddressInfo; - - /** - * Creates an AddressInfo message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns AddressInfo - */ - public static fromObject(d: { [k: string]: any }): PeerRecord.AddressInfo; - - /** - * Creates a plain object from an AddressInfo message. Also converts values to other types if specified. - * @param m AddressInfo - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: PeerRecord.AddressInfo, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this AddressInfo to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } -} diff --git a/src/record/peer-record/peer-record.js b/src/record/peer-record/peer-record.js deleted file mode 100644 index 9f95667090..0000000000 --- a/src/record/peer-record/peer-record.js +++ /dev/null @@ -1,367 +0,0 @@ -/*eslint-disable*/ -"use strict"; - -var $protobuf = require("protobufjs/minimal"); - -// Common aliases -var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; - -// Exported root namespace -var $root = $protobuf.roots["libp2p-peer-record"] || ($protobuf.roots["libp2p-peer-record"] = {}); - -$root.PeerRecord = (function() { - - /** - * Properties of a PeerRecord. - * @exports IPeerRecord - * @interface IPeerRecord - * @property {Uint8Array|null} [peerId] PeerRecord peerId - * @property {number|null} [seq] PeerRecord seq - * @property {Array.|null} [addresses] PeerRecord addresses - */ - - /** - * Constructs a new PeerRecord. - * @exports PeerRecord - * @classdesc Represents a PeerRecord. - * @implements IPeerRecord - * @constructor - * @param {IPeerRecord=} [p] Properties to set - */ - function PeerRecord(p) { - this.addresses = []; - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * PeerRecord peerId. - * @member {Uint8Array} peerId - * @memberof PeerRecord - * @instance - */ - PeerRecord.prototype.peerId = $util.newBuffer([]); - - /** - * PeerRecord seq. - * @member {number} seq - * @memberof PeerRecord - * @instance - */ - PeerRecord.prototype.seq = $util.Long ? $util.Long.fromBits(0,0,true) : 0; - - /** - * PeerRecord addresses. - * @member {Array.} addresses - * @memberof PeerRecord - * @instance - */ - PeerRecord.prototype.addresses = $util.emptyArray; - - /** - * Encodes the specified PeerRecord message. Does not implicitly {@link PeerRecord.verify|verify} messages. - * @function encode - * @memberof PeerRecord - * @static - * @param {IPeerRecord} m PeerRecord message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - PeerRecord.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.peerId != null && Object.hasOwnProperty.call(m, "peerId")) - w.uint32(10).bytes(m.peerId); - if (m.seq != null && Object.hasOwnProperty.call(m, "seq")) - w.uint32(16).uint64(m.seq); - if (m.addresses != null && m.addresses.length) { - for (var i = 0; i < m.addresses.length; ++i) - $root.PeerRecord.AddressInfo.encode(m.addresses[i], w.uint32(26).fork()).ldelim(); - } - return w; - }; - - /** - * Decodes a PeerRecord message from the specified reader or buffer. - * @function decode - * @memberof PeerRecord - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {PeerRecord} PeerRecord - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - PeerRecord.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.PeerRecord(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.peerId = r.bytes(); - break; - case 2: - m.seq = r.uint64(); - break; - case 3: - if (!(m.addresses && m.addresses.length)) - m.addresses = []; - m.addresses.push($root.PeerRecord.AddressInfo.decode(r, r.uint32())); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a PeerRecord message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof PeerRecord - * @static - * @param {Object.} d Plain object - * @returns {PeerRecord} PeerRecord - */ - PeerRecord.fromObject = function fromObject(d) { - if (d instanceof $root.PeerRecord) - return d; - var m = new $root.PeerRecord(); - if (d.peerId != null) { - if (typeof d.peerId === "string") - $util.base64.decode(d.peerId, m.peerId = $util.newBuffer($util.base64.length(d.peerId)), 0); - else if (d.peerId.length) - m.peerId = d.peerId; - } - if (d.seq != null) { - if ($util.Long) - (m.seq = $util.Long.fromValue(d.seq)).unsigned = true; - else if (typeof d.seq === "string") - m.seq = parseInt(d.seq, 10); - else if (typeof d.seq === "number") - m.seq = d.seq; - else if (typeof d.seq === "object") - m.seq = new $util.LongBits(d.seq.low >>> 0, d.seq.high >>> 0).toNumber(true); - } - if (d.addresses) { - if (!Array.isArray(d.addresses)) - throw TypeError(".PeerRecord.addresses: array expected"); - m.addresses = []; - for (var i = 0; i < d.addresses.length; ++i) { - if (typeof d.addresses[i] !== "object") - throw TypeError(".PeerRecord.addresses: object expected"); - m.addresses[i] = $root.PeerRecord.AddressInfo.fromObject(d.addresses[i]); - } - } - return m; - }; - - /** - * Creates a plain object from a PeerRecord message. Also converts values to other types if specified. - * @function toObject - * @memberof PeerRecord - * @static - * @param {PeerRecord} m PeerRecord - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - PeerRecord.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.arrays || o.defaults) { - d.addresses = []; - } - if (o.defaults) { - if (o.bytes === String) - d.peerId = ""; - else { - d.peerId = []; - if (o.bytes !== Array) - d.peerId = $util.newBuffer(d.peerId); - } - if ($util.Long) { - var n = new $util.Long(0, 0, true); - d.seq = o.longs === String ? n.toString() : o.longs === Number ? n.toNumber() : n; - } else - d.seq = o.longs === String ? "0" : 0; - } - if (m.peerId != null && m.hasOwnProperty("peerId")) { - d.peerId = o.bytes === String ? $util.base64.encode(m.peerId, 0, m.peerId.length) : o.bytes === Array ? Array.prototype.slice.call(m.peerId) : m.peerId; - } - if (m.seq != null && m.hasOwnProperty("seq")) { - if (typeof m.seq === "number") - d.seq = o.longs === String ? String(m.seq) : m.seq; - else - d.seq = o.longs === String ? $util.Long.prototype.toString.call(m.seq) : o.longs === Number ? new $util.LongBits(m.seq.low >>> 0, m.seq.high >>> 0).toNumber(true) : m.seq; - } - if (m.addresses && m.addresses.length) { - d.addresses = []; - for (var j = 0; j < m.addresses.length; ++j) { - d.addresses[j] = $root.PeerRecord.AddressInfo.toObject(m.addresses[j], o); - } - } - return d; - }; - - /** - * Converts this PeerRecord to JSON. - * @function toJSON - * @memberof PeerRecord - * @instance - * @returns {Object.} JSON object - */ - PeerRecord.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - PeerRecord.AddressInfo = (function() { - - /** - * Properties of an AddressInfo. - * @memberof PeerRecord - * @interface IAddressInfo - * @property {Uint8Array|null} [multiaddr] AddressInfo multiaddr - */ - - /** - * Constructs a new AddressInfo. - * @memberof PeerRecord - * @classdesc Represents an AddressInfo. - * @implements IAddressInfo - * @constructor - * @param {PeerRecord.IAddressInfo=} [p] Properties to set - */ - function AddressInfo(p) { - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * AddressInfo multiaddr. - * @member {Uint8Array} multiaddr - * @memberof PeerRecord.AddressInfo - * @instance - */ - AddressInfo.prototype.multiaddr = $util.newBuffer([]); - - /** - * Encodes the specified AddressInfo message. Does not implicitly {@link PeerRecord.AddressInfo.verify|verify} messages. - * @function encode - * @memberof PeerRecord.AddressInfo - * @static - * @param {PeerRecord.IAddressInfo} m AddressInfo message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - AddressInfo.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.multiaddr != null && Object.hasOwnProperty.call(m, "multiaddr")) - w.uint32(10).bytes(m.multiaddr); - return w; - }; - - /** - * Decodes an AddressInfo message from the specified reader or buffer. - * @function decode - * @memberof PeerRecord.AddressInfo - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {PeerRecord.AddressInfo} AddressInfo - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - AddressInfo.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.PeerRecord.AddressInfo(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.multiaddr = r.bytes(); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates an AddressInfo message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof PeerRecord.AddressInfo - * @static - * @param {Object.} d Plain object - * @returns {PeerRecord.AddressInfo} AddressInfo - */ - AddressInfo.fromObject = function fromObject(d) { - if (d instanceof $root.PeerRecord.AddressInfo) - return d; - var m = new $root.PeerRecord.AddressInfo(); - if (d.multiaddr != null) { - if (typeof d.multiaddr === "string") - $util.base64.decode(d.multiaddr, m.multiaddr = $util.newBuffer($util.base64.length(d.multiaddr)), 0); - else if (d.multiaddr.length) - m.multiaddr = d.multiaddr; - } - return m; - }; - - /** - * Creates a plain object from an AddressInfo message. Also converts values to other types if specified. - * @function toObject - * @memberof PeerRecord.AddressInfo - * @static - * @param {PeerRecord.AddressInfo} m AddressInfo - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - AddressInfo.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.defaults) { - if (o.bytes === String) - d.multiaddr = ""; - else { - d.multiaddr = []; - if (o.bytes !== Array) - d.multiaddr = $util.newBuffer(d.multiaddr); - } - } - if (m.multiaddr != null && m.hasOwnProperty("multiaddr")) { - d.multiaddr = o.bytes === String ? $util.base64.encode(m.multiaddr, 0, m.multiaddr.length) : o.bytes === Array ? Array.prototype.slice.call(m.multiaddr) : m.multiaddr; - } - return d; - }; - - /** - * Converts this AddressInfo to JSON. - * @function toJSON - * @memberof PeerRecord.AddressInfo - * @instance - * @returns {Object.} JSON object - */ - AddressInfo.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return AddressInfo; - })(); - - return PeerRecord; -})(); - -module.exports = $root; diff --git a/src/record/peer-record/peer-record.proto b/src/record/peer-record/peer-record.proto deleted file mode 100644 index 6b740dc80f..0000000000 --- a/src/record/peer-record/peer-record.proto +++ /dev/null @@ -1,18 +0,0 @@ -syntax = "proto3"; - -message PeerRecord { - // AddressInfo is a wrapper around a binary multiaddr. It is defined as a - // separate message to allow us to add per-address metadata in the future. - message AddressInfo { - bytes multiaddr = 1; - } - - // peer_id contains a libp2p peer id in its binary representation. - bytes peer_id = 1; - - // seq contains a monotonically-increasing sequence counter to order PeerRecords in time. - uint64 seq = 2; - - // addresses is a list of public listen addresses for the peer. - repeated AddressInfo addresses = 3; -} \ No newline at end of file diff --git a/src/record/utils.js b/src/record/utils.js deleted file mode 100644 index 512d62a258..0000000000 --- a/src/record/utils.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict' - -const Envelope = require('./envelope') -const PeerRecord = require('./peer-record') - -/** - * @typedef {import('../')} Libp2p - */ - -/** - * Create (or update if existing) self peer record and store it in the AddressBook. - * - * @param {Libp2p} libp2p - * @returns {Promise} - */ -async function updateSelfPeerRecord (libp2p) { - const peerRecord = new PeerRecord({ - peerId: libp2p.peerId, - multiaddrs: libp2p.multiaddrs - }) - const envelope = await Envelope.seal(peerRecord, libp2p.peerId) - await libp2p.peerStore.addressBook.consumePeerRecord(envelope) -} - -module.exports.updateSelfPeerRecord = updateSelfPeerRecord diff --git a/src/registrar.js b/src/registrar.js deleted file mode 100644 index 812c76011e..0000000000 --- a/src/registrar.js +++ /dev/null @@ -1,127 +0,0 @@ -'use strict' - -const debug = require('debug') -const log = Object.assign(debug('libp2p:registrar'), { - error: debug('libp2p:registrar:err') -}) -const errcode = require('err-code') - -const { - codes: { ERR_INVALID_PARAMETERS } -} = require('./errors') -const Topology = require('libp2p-interfaces/src/topology') - -/** - * @typedef {import('peer-id')} PeerId - * @typedef {import('./peer-store/types').PeerStore} PeerStore - * @typedef {import('./connection-manager')} ConnectionManager - * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection - * @typedef {import('./').HandlerProps} HandlerProps - */ - -/** - * - */ - -/** - * Responsible for notifying registered protocols of events in the network. - */ -class Registrar { - /** - * @param {Object} props - * @param {PeerStore} props.peerStore - * @param {ConnectionManager} props.connectionManager - * @class - */ - constructor ({ peerStore, connectionManager }) { - // Used on topology to listen for protocol changes - this.peerStore = peerStore - - this.connectionManager = connectionManager - - /** - * Map of topologies - * - * @type {Map} - */ - this.topologies = new Map() - - /** @type {(protocols: string[]|string, handler: (props: HandlerProps) => void) => void} */ - // @ts-ignore handle is not optional - this._handle = undefined - - this._onDisconnect = this._onDisconnect.bind(this) - this.connectionManager.on('peer:disconnect', this._onDisconnect) - } - - /** - * @returns {(protocols: string[]|string, handler: (props: HandlerProps) => void) => void} - */ - get handle () { - return this._handle - } - - /** - * @param {(protocols: string[]|string, handler: (props: HandlerProps) => void) => void} handle - */ - set handle (handle) { - this._handle = handle - } - - /** - * Get a connection with a peer. - * - * @param {PeerId} peerId - * @returns {Connection | null} - */ - getConnection (peerId) { - return this.connectionManager.get(peerId) - } - - /** - * Register handlers for a set of multicodecs given - * - * @param {Topology} topology - protocol topology - * @returns {Promise} registrar identifier - */ - async register (topology) { - if (!Topology.isTopology(topology)) { - log.error('topology must be an instance of interfaces/topology') - throw errcode(new Error('topology must be an instance of interfaces/topology'), ERR_INVALID_PARAMETERS) - } - - // Create topology - const id = (Math.random() * 1e9).toString(36) + Date.now() - - this.topologies.set(id, topology) - - // Set registrar - await topology.setRegistrar(this) - - return id - } - - /** - * Unregister topology. - * - * @param {string} id - registrar identifier - * @returns {boolean} unregistered successfully - */ - unregister (id) { - return this.topologies.delete(id) - } - - /** - * Remove a disconnected peer from the record - * - * @param {Connection} connection - * @returns {void} - */ - _onDisconnect (connection) { - for (const [, topology] of this.topologies) { - topology.disconnect(connection.remotePeer) - } - } -} - -module.exports = Registrar diff --git a/src/registrar.ts b/src/registrar.ts new file mode 100644 index 0000000000..20d960f500 --- /dev/null +++ b/src/registrar.ts @@ -0,0 +1,205 @@ +import { logger } from '@libp2p/logger' +import errCode from 'err-code' +import { codes } from './errors.js' +import { isTopology, Topology } from '@libp2p/interfaces/topology' +import type { Registrar, StreamHandler } from '@libp2p/interfaces/registrar' +import type { PeerProtocolsChangeData } from '@libp2p/interfaces/peer-store' +import type { Connection } from '@libp2p/interfaces/connection' +import type { Components } from '@libp2p/interfaces/components' + +const log = logger('libp2p:registrar') + +function supportsProtocol (peerProtocols: string[], topologyProtocols: string[]) { + for (const peerProtocol of peerProtocols) { + if (topologyProtocols.includes(peerProtocol)) { + return true + } + } + + return false +} + +/** + * Responsible for notifying registered protocols of events in the network. + */ +export class DefaultRegistrar implements Registrar { + private readonly topologies: Map + private readonly handlers: Map + private readonly components: Components + + constructor (components: Components) { + this.topologies = new Map() + this.handlers = new Map() + this.components = components + + this._onDisconnect = this._onDisconnect.bind(this) + this._onConnect = this._onConnect.bind(this) + this._onProtocolChange = this._onProtocolChange.bind(this) + + this.components.getConnectionManager().addEventListener('peer:disconnect', this._onDisconnect) + this.components.getConnectionManager().addEventListener('peer:connect', this._onConnect) + this.components.getPeerStore().addEventListener('change:protocols', this._onProtocolChange) + } + + getProtocols () { + const protocols = new Set() + + for (const topology of this.topologies.values()) { + topology.protocols.forEach(protocol => protocols.add(protocol)) + } + + for (const protocol of this.handlers.keys()) { + protocols.add(protocol) + } + + return Array.from(protocols).sort() + } + + getHandler (protocol: string) { + const handler = this.handlers.get(protocol) + + if (handler == null) { + throw new Error(`No handler registered for protocol ${protocol}`) + } + + return handler + } + + getTopologies (protocol: string) { + const output: Topology[] = [] + + for (const { topology, protocols } of this.topologies.values()) { + if (protocols.includes(protocol)) { + output.push(topology) + } + } + + return output + } + + /** + * Registers the `handler` for each protocol + */ + async handle (protocols: string | string[], handler: StreamHandler): Promise { + const protocolList = Array.isArray(protocols) ? protocols : [protocols] + + for (const protocol of protocolList) { + if (this.handlers.has(protocol)) { + throw errCode(new Error(`Handler already registered for protocol ${protocol}`), codes.ERR_PROTOCOL_HANDLER_ALREADY_REGISTERED) + } + + this.handlers.set(protocol, handler) + } + + // Add new protocols to self protocols in the Protobook + await this.components.getPeerStore().protoBook.add(this.components.getPeerId(), protocolList) + } + + /** + * Removes the handler for each protocol. The protocol + * will no longer be supported on streams. + */ + async unhandle (protocols: string | string[]) { + const protocolList = Array.isArray(protocols) ? protocols : [protocols] + + protocolList.forEach(protocol => { + this.handlers.delete(protocol) + }) + + // Remove protocols from self protocols in the Protobook + await this.components.getPeerStore().protoBook.remove(this.components.getPeerId(), protocolList) + } + + /** + * Register handlers for a set of multicodecs given + */ + async register (protocols: string | string[], topology: Topology): Promise { + if (!isTopology(topology)) { + log.error('topology must be an instance of interfaces/topology') + throw errCode(new Error('topology must be an instance of interfaces/topology'), codes.ERR_INVALID_PARAMETERS) + } + + // Create topology + const id = `${(Math.random() * 1e9).toString(36)}${Date.now()}` + + this.topologies.set(id, { + topology, + protocols: Array.isArray(protocols) ? protocols : [protocols] + }) + + // Set registrar + await topology.setRegistrar(this) + + return id + } + + /** + * Unregister topology + */ + unregister (id: string) { + this.topologies.delete(id) + } + + /** + * Remove a disconnected peer from the record + */ + _onDisconnect (evt: CustomEvent) { + const connection = evt.detail + + void this.components.getPeerStore().protoBook.get(connection.remotePeer) + .then(peerProtocols => { + for (const { topology, protocols } of this.topologies.values()) { + if (supportsProtocol(peerProtocols, protocols)) { + topology.onDisconnect(connection.remotePeer) + } + } + }) + .catch(err => { + log.error(err) + }) + } + + _onConnect (evt: CustomEvent) { + const connection = evt.detail + + void this.components.getPeerStore().protoBook.get(connection.remotePeer) + .then(peerProtocols => { + for (const { topology, protocols } of this.topologies.values()) { + if (supportsProtocol(peerProtocols, protocols)) { + topology.onConnect(connection.remotePeer, connection) + } + } + }) + .catch(err => { + log.error(err) + }) + } + + /** + * Check if a new peer support the multicodecs for this topology + */ + _onProtocolChange (evt: CustomEvent) { + const { peerId, protocols, oldProtocols } = evt.detail + + const removed = oldProtocols.filter(protocol => !protocols.includes(protocol)) + const added = protocols.filter(protocol => !oldProtocols.includes(protocol)) + + for (const { topology, protocols } of this.topologies.values()) { + if (supportsProtocol(removed, protocols)) { + topology.onDisconnect(peerId) + } + } + + for (const { topology, protocols } of this.topologies.values()) { + if (supportsProtocol(added, protocols)) { + const connection = this.components.getConnectionManager().getConnection(peerId) + + if (connection == null) { + continue + } + + topology.onConnect(peerId, connection) + } + } + } +} diff --git a/src/transport-manager.js b/src/transport-manager.js deleted file mode 100644 index 8a7303ef3d..0000000000 --- a/src/transport-manager.js +++ /dev/null @@ -1,269 +0,0 @@ -'use strict' - -const debug = require('debug') -const log = Object.assign(debug('libp2p:transports'), { - error: debug('libp2p:transports:err') -}) - -const pSettle = require('p-settle') -const { codes } = require('./errors') -const errCode = require('err-code') - -const { updateSelfPeerRecord } = require('./record/utils') - -/** - * @typedef {import('multiaddr').Multiaddr} Multiaddr - * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection - * @typedef {import('libp2p-interfaces/src/transport/types').TransportFactory} TransportFactory - * @typedef {import('libp2p-interfaces/src/transport/types').Transport} Transport - * - * @typedef {Object} TransportManagerProperties - * @property {import('./')} libp2p - * @property {import('./upgrader')} upgrader - * - * @typedef {Object} TransportManagerOptions - * @property {number} [faultTolerance = FAULT_TOLERANCE.FATAL_ALL] - Address listen error tolerance. - */ - -class TransportManager { - /** - * @class - * @param {TransportManagerProperties & TransportManagerOptions} options - */ - constructor ({ libp2p, upgrader, faultTolerance = FAULT_TOLERANCE.FATAL_ALL }) { - this.libp2p = libp2p - this.upgrader = upgrader - /** @type {Map} */ - this._transports = new Map() - this._listeners = new Map() - this._listenerOptions = new Map() - this.faultTolerance = faultTolerance - } - - /** - * Adds a `Transport` to the manager - * - * @param {string} key - * @param {TransportFactory} Transport - * @param {*} transportOptions - Additional options to pass to the transport - * @returns {void} - */ - add (key, Transport, transportOptions = {}) { - log('adding %s', key) - if (!key) { - throw errCode(new Error(`Transport must have a valid key, was given '${key}'`), codes.ERR_INVALID_KEY) - } - if (this._transports.has(key)) { - throw errCode(new Error('There is already a transport with this key'), codes.ERR_DUPLICATE_TRANSPORT) - } - - const transport = new Transport({ - ...transportOptions, - libp2p: this.libp2p, - upgrader: this.upgrader - }) - - this._transports.set(key, transport) - this._listenerOptions.set(key, transportOptions.listenerOptions || {}) - if (!this._listeners.has(key)) { - this._listeners.set(key, []) - } - } - - /** - * Stops all listeners - * - * @async - */ - async close () { - const tasks = [] - for (const [key, listeners] of this._listeners) { - log('closing listeners for %s', key) - while (listeners.length) { - const listener = listeners.pop() - listener.removeAllListeners('listening') - listener.removeAllListeners('close') - tasks.push(listener.close()) - } - } - - await Promise.all(tasks) - log('all listeners closed') - for (const key of this._listeners.keys()) { - this._listeners.set(key, []) - } - } - - /** - * Dials the given Multiaddr over it's supported transport - * - * @param {Multiaddr} ma - * @param {*} options - * @returns {Promise} - */ - async dial (ma, options) { - const transport = this.transportForMultiaddr(ma) - if (!transport) { - throw errCode(new Error(`No transport available for address ${String(ma)}`), codes.ERR_TRANSPORT_UNAVAILABLE) - } - - try { - return await transport.dial(ma, options) - } catch (/** @type {any} */ err) { - if (!err.code) err.code = codes.ERR_TRANSPORT_DIAL_FAILED - throw err - } - } - - /** - * Returns all Multiaddr's the listeners are using - * - * @returns {Multiaddr[]} - */ - getAddrs () { - /** @type {Multiaddr[]} */ - let addrs = [] - for (const listeners of this._listeners.values()) { - for (const listener of listeners) { - addrs = [...addrs, ...listener.getAddrs()] - } - } - return addrs - } - - /** - * Returns all the transports instances. - * - * @returns {IterableIterator} - */ - getTransports () { - return this._transports.values() - } - - /** - * Finds a transport that matches the given Multiaddr - * - * @param {Multiaddr} ma - * @returns {Transport|null} - */ - transportForMultiaddr (ma) { - for (const transport of this._transports.values()) { - const addrs = transport.filter([ma]) - if (addrs.length) return transport - } - return null - } - - /** - * Starts listeners for each listen Multiaddr. - * - * @async - * @param {Multiaddr[]} addrs - addresses to attempt to listen on - */ - async listen (addrs) { - if (!addrs || addrs.length === 0) { - log('no addresses were provided for listening, this node is dial only') - return - } - - const couldNotListen = [] - for (const [key, transport] of this._transports.entries()) { - const supportedAddrs = transport.filter(addrs) - const tasks = [] - - // For each supported multiaddr, create a listener - for (const addr of supportedAddrs) { - log('creating listener for %s on %s', key, addr) - const listener = transport.createListener(this._listenerOptions.get(key)) - 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)) - } - - // Keep track of transports we had no addresses for - if (tasks.length === 0) { - couldNotListen.push(key) - continue - } - - const results = await pSettle(tasks) - // If we are listening on at least 1 address, succeed. - // TODO: we should look at adding a retry (`p-retry`) here to better support - // listening on remote addresses as they may be offline. We could then potentially - // just wait for any (`p-any`) listener to succeed on each transport before returning - const isListening = results.find(r => r.isFulfilled === true) - if (!isListening && this.faultTolerance !== FAULT_TOLERANCE.NO_FATAL) { - throw errCode(new Error(`Transport (${key}) could not listen on any available address`), codes.ERR_NO_VALID_ADDRESSES) - } - } - - // If no transports were able to listen, throw an error. This likely - // means we were given addresses we do not have transports for - if (couldNotListen.length === this._transports.size) { - const message = `no valid addresses were provided for transports [${couldNotListen}]` - if (this.faultTolerance === FAULT_TOLERANCE.FATAL_ALL) { - throw errCode(new Error(message), codes.ERR_NO_VALID_ADDRESSES) - } - log(`libp2p in dial mode only: ${message}`) - } - } - - /** - * Removes the given transport from the manager. - * If a transport has any running listeners, they will be closed. - * - * @async - * @param {string} key - */ - async remove (key) { - log('removing %s', key) - 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() - } - } - - this._transports.delete(key) - this._listeners.delete(key) - } - - /** - * Removes all transports from the manager. - * If any listeners are running, they will be closed. - * - * @async - */ - async removeAll () { - const tasks = [] - for (const key of this._transports.keys()) { - tasks.push(this.remove(key)) - } - - await Promise.all(tasks) - } -} - -/** - * Enum Transport Manager Fault Tolerance values. - * FATAL_ALL should be used for failing in any listen circumstance. - * NO_FATAL should be used for not failing when not listening. - * - * @readonly - * @enum {number} - */ -const FAULT_TOLERANCE = { - FATAL_ALL: 0, - NO_FATAL: 1 -} - -TransportManager.FaultTolerance = FAULT_TOLERANCE - -module.exports = TransportManager diff --git a/src/transport-manager.ts b/src/transport-manager.ts new file mode 100644 index 0000000000..d6e4364217 --- /dev/null +++ b/src/transport-manager.ts @@ -0,0 +1,279 @@ +import { logger } from '@libp2p/logger' +import pSettle from 'p-settle' +import { codes } from './errors.js' +import errCode from 'err-code' +import type { Listener, Transport, TransportManager, TransportManagerEvents } from '@libp2p/interfaces/transport' +import type { Multiaddr } from '@multiformats/multiaddr' +import type { Connection } from '@libp2p/interfaces/connection' +import { AbortOptions, CustomEvent, EventEmitter, Startable } from '@libp2p/interfaces' +import type { Components } from '@libp2p/interfaces/components' +import { trackedMap } from '@libp2p/tracked-map' + +const log = logger('libp2p:transports') + +export interface TransportManagerInit { + faultTolerance?: FAULT_TOLERANCE +} + +export class DefaultTransportManager extends EventEmitter implements TransportManager, Startable { + private readonly components: Components + private readonly transports: Map + private readonly listeners: Map + private readonly faultTolerance: FAULT_TOLERANCE + private started: boolean + + constructor (components: Components, init: TransportManagerInit = {}) { + super() + + this.components = components + this.started = false + this.transports = new Map() + this.listeners = trackedMap({ + component: 'transport-manager', + metric: 'listeners', + metrics: this.components.getMetrics() + }) + this.faultTolerance = init.faultTolerance ?? FAULT_TOLERANCE.FATAL_ALL + } + + /** + * Adds a `Transport` to the manager + */ + add (transport: Transport) { + const tag = transport[Symbol.toStringTag] + + if (tag == null) { + throw errCode(new Error('Transport must have a valid tag'), codes.ERR_INVALID_KEY) + } + + if (this.transports.has(tag)) { + throw errCode(new Error('There is already a transport with this tag'), codes.ERR_DUPLICATE_TRANSPORT) + } + + log('adding transport %s', tag) + + this.transports.set(tag, transport) + + if (!this.listeners.has(tag)) { + this.listeners.set(tag, []) + } + } + + isStarted () { + return this.started + } + + async start () { + // Listen on the provided transports for the provided addresses + const addrs = this.components.getAddressManager().getListenAddrs() + + await this.listen(addrs) + + this.started = true + } + + /** + * Stops all listeners + */ + async stop () { + const tasks = [] + for (const [key, listeners] of this.listeners) { + log('closing listeners for %s', key) + while (listeners.length > 0) { + const listener = listeners.pop() + + if (listener == null) { + continue + } + + tasks.push(listener.close()) + } + } + + await Promise.all(tasks) + log('all listeners closed') + for (const key of this.listeners.keys()) { + this.listeners.set(key, []) + } + + this.started = false + } + + /** + * Dials the given Multiaddr over it's supported transport + */ + async dial (ma: Multiaddr, options?: AbortOptions): Promise { + const transport = this.transportForMultiaddr(ma) + + if (transport == null) { + throw errCode(new Error(`No transport available for address ${String(ma)}`), codes.ERR_TRANSPORT_UNAVAILABLE) + } + + try { + return await transport.dial(ma, { + ...options, + upgrader: this.components.getUpgrader() + }) + } catch (err: any) { + if (err.code == null) { + err.code = codes.ERR_TRANSPORT_DIAL_FAILED + } + + throw err + } + } + + /** + * Returns all Multiaddr's the listeners are using + */ + getAddrs (): Multiaddr[] { + let addrs: Multiaddr[] = [] + for (const listeners of this.listeners.values()) { + for (const listener of listeners) { + addrs = [...addrs, ...listener.getAddrs()] + } + } + return addrs + } + + /** + * Returns all the transports instances + */ + getTransports () { + return Array.of(...this.transports.values()) + } + + /** + * Finds a transport that matches the given Multiaddr + */ + transportForMultiaddr (ma: Multiaddr) { + for (const transport of this.transports.values()) { + const addrs = transport.filter([ma]) + + if (addrs.length > 0) { + return transport + } + } + } + + /** + * Starts listeners for each listen Multiaddr + */ + async listen (addrs: Multiaddr[]) { + if (addrs == null || addrs.length === 0) { + log('no addresses were provided for listening, this node is dial only') + return + } + + const couldNotListen = [] + + for (const [key, transport] of this.transports.entries()) { + const supportedAddrs = transport.filter(addrs) + const tasks = [] + + // For each supported multiaddr, create a listener + for (const addr of supportedAddrs) { + log('creating listener for %s on %s', key, addr) + const listener = transport.createListener({ + upgrader: this.components.getUpgrader() + }) + + let listeners = this.listeners.get(key) + + if (listeners == null) { + listeners = [] + this.listeners.set(key, listeners) + } + + listeners.push(listener) + + // Track listen/close events + listener.addEventListener('listening', () => { + this.dispatchEvent(new CustomEvent('listener:listening', { + detail: listener + })) + }) + listener.addEventListener('close', () => { + this.dispatchEvent(new CustomEvent('listener:close', { + detail: listener + })) + }) + + // We need to attempt to listen on everything + tasks.push(listener.listen(addr)) + } + + // Keep track of transports we had no addresses for + if (tasks.length === 0) { + couldNotListen.push(key) + continue + } + + const results = await pSettle(tasks) + // If we are listening on at least 1 address, succeed. + // TODO: we should look at adding a retry (`p-retry`) here to better support + // listening on remote addresses as they may be offline. We could then potentially + // just wait for any (`p-any`) listener to succeed on each transport before returning + const isListening = results.find(r => r.isFulfilled) + if ((isListening == null) && this.faultTolerance !== FAULT_TOLERANCE.NO_FATAL) { + throw errCode(new Error(`Transport (${key}) could not listen on any available address`), codes.ERR_NO_VALID_ADDRESSES) + } + } + + // If no transports were able to listen, throw an error. This likely + // means we were given addresses we do not have transports for + if (couldNotListen.length === this.transports.size) { + const message = `no valid addresses were provided for transports [${couldNotListen.join(', ')}]` + if (this.faultTolerance === FAULT_TOLERANCE.FATAL_ALL) { + throw errCode(new Error(message), codes.ERR_NO_VALID_ADDRESSES) + } + log(`libp2p in dial mode only: ${message}`) + } + } + + /** + * Removes the given transport from the manager. + * If a transport has any running listeners, they will be closed. + */ + async remove (key: string) { + log('removing %s', key) + + // Close any running listeners + for (const listener of this.listeners.get(key) ?? []) { + await listener.close() + } + + this.transports.delete(key) + this.listeners.delete(key) + } + + /** + * Removes all transports from the manager. + * If any listeners are running, they will be closed. + * + * @async + */ + async removeAll () { + const tasks = [] + for (const key of this.transports.keys()) { + tasks.push(this.remove(key)) + } + + await Promise.all(tasks) + } +} + +/** + * Enum Transport Manager Fault Tolerance values + */ +export enum FAULT_TOLERANCE { + /** + * should be used for failing in any listen circumstance + */ + FATAL_ALL = 0, + + /** + * should be used for not failing when not listening + */ + NO_FATAL +} diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index b8af9a6f08..0000000000 --- a/src/types.ts +++ /dev/null @@ -1,98 +0,0 @@ -import type PeerId from 'peer-id' -import type { Multiaddr } from 'multiaddr' -import type { MultiaddrConnection } from 'libp2p-interfaces/src/transport/types' - -export interface ConnectionGater { - /** - * denyDialMultiaddr tests whether we're permitted to Dial the - * specified peer. - * - * This is called by the dialer.connectToPeer implementation before - * dialling a peer. - * - * Return true to prevent dialing the passed peer. - */ - denyDialPeer: (peerId: PeerId) => Promise - - /** - * denyDialMultiaddr tests whether we're permitted to dial the specified - * multiaddr for the given peer. - * - * This is called by the dialer.connectToPeer implementation after it has - * resolved the peer's addrs, and prior to dialling each. - * - * Return true to prevent dialing the passed peer on the passed multiaddr. - */ - denyDialMultiaddr: (peerId: PeerId, multiaddr: Multiaddr) => Promise - - /** - * denyInboundConnection tests whether an incipient inbound connection is allowed. - * - * This is called by the upgrader, or by the transport directly (e.g. QUIC, - * Bluetooth), straight after it has accepted a connection from its socket. - * - * Return true to deny the incoming passed connection. - */ - denyInboundConnection: (maConn: MultiaddrConnection) => Promise - - /** - * denyOutboundConnection tests whether an incipient outbound connection is allowed. - * - * This is called by the upgrader, or by the transport directly (e.g. QUIC, - * Bluetooth), straight after it has created a connection with its socket. - * - * Return true to deny the incoming passed connection. - */ - denyOutboundConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise - - /** - * denyInboundEncryptedConnection tests whether a given connection, now encrypted, - * is allowed. - * - * This is called by the upgrader, after it has performed the security - * handshake, and before it negotiates the muxer, or by the directly by the - * transport, at the exact same checkpoint. - * - * Return true to deny the passed secured connection. - */ - denyInboundEncryptedConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise - - /** - * denyOutboundEncryptedConnection tests whether a given connection, now encrypted, - * is allowed. - * - * This is called by the upgrader, after it has performed the security - * handshake, and before it negotiates the muxer, or by the directly by the - * transport, at the exact same checkpoint. - * - * Return true to deny the passed secured connection. - */ - denyOutboundEncryptedConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise - - /** - * denyInboundUpgradedConnection tests whether a fully capable connection is allowed. - * - * This is called after encryption has been negotiated and the connection has been - * multiplexed, if a multiplexer is configured. - * - * Return true to deny the passed upgraded connection. - */ - denyInboundUpgradedConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise - - /** - * denyOutboundUpgradedConnection tests whether a fully capable connection is allowed. - * - * This is called after encryption has been negotiated and the connection has been - * multiplexed, if a multiplexer is configured. - * - * Return true to deny the passed upgraded connection. - */ - denyOutboundUpgradedConnection: (peerId: PeerId, maConn: MultiaddrConnection) => Promise - - /** - * Used by the address book to filter passed addresses. - * - * Return true to allow storing the passed multiaddr for the passed peer. - */ - filterMultiaddrForPeer: (peer: PeerId, multiaddr: Multiaddr) => Promise -} diff --git a/src/upgrader.js b/src/upgrader.js deleted file mode 100644 index cbfc77c3e4..0000000000 --- a/src/upgrader.js +++ /dev/null @@ -1,492 +0,0 @@ -'use strict' - -const debug = require('debug') -const log = Object.assign(debug('libp2p:upgrader'), { - error: debug('libp2p:upgrader:err') -}) -const errCode = require('err-code') -const Multistream = require('multistream-select') -const { Connection } = require('libp2p-interfaces/src/connection') -const PeerId = require('peer-id') -const { pipe } = require('it-pipe') -// @ts-ignore mutable-proxy does not export types -const mutableProxy = require('mutable-proxy') - -const { codes } = require('./errors') - -/** - * @typedef {import('libp2p-interfaces/src/transport/types').MultiaddrConnection} MultiaddrConnection - * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxerFactory} MuxerFactory - * @typedef {import('libp2p-interfaces/src/stream-muxer/types').Muxer} Muxer - * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream - * @typedef {import('libp2p-interfaces/src/crypto/types').Crypto} Crypto - * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection - * @typedef {import('multiaddr').Multiaddr} Multiaddr - * @typedef {import('./types').ConnectionGater} ConnectionGater - */ - -/** - * @typedef CryptoResult - * @property {MultiaddrConnection} conn A duplex iterable - * @property {PeerId} remotePeer - * @property {string} protocol - */ - -class Upgrader { - /** - * @param {object} options - * @param {PeerId} options.localPeer - * @param {ConnectionGater} options.connectionGater - * - * @param {import('./metrics')} [options.metrics] - * @param {Map} [options.cryptos] - * @param {Map} [options.muxers] - * @param {(connection: Connection) => void} options.onConnection - Called when a connection is upgraded - * @param {(connection: Connection) => void} options.onConnectionEnd - */ - constructor ({ - localPeer, - metrics, - connectionGater, - cryptos = new Map(), - muxers = new Map(), - onConnectionEnd = () => {}, - onConnection = () => {} - }) { - this.connectionGater = connectionGater - this.localPeer = localPeer - this.metrics = metrics - this.cryptos = cryptos - this.muxers = muxers - /** @type {import("./pnet") | null} */ - this.protector = null - this.protocols = new Map() - this.onConnection = onConnection - this.onConnectionEnd = onConnectionEnd - } - - /** - * Upgrades an inbound connection - * - * @async - * @param {MultiaddrConnection} maConn - * @returns {Promise} - */ - async upgradeInbound (maConn) { - let encryptedConn - let remotePeer - let upgradedConn - let Muxer - let cryptoProtocol - let setPeer - let proxyPeer - - if (await this.connectionGater.denyInboundConnection(maConn)) { - throw errCode(new Error('The multiaddr connection is blocked by gater.acceptConnection'), codes.ERR_CONNECTION_INTERCEPTED) - } - - if (this.metrics) { - ({ setTarget: setPeer, proxy: proxyPeer } = mutableProxy()) - const idString = (Math.random() * 1e9).toString(36) + Date.now() - setPeer({ toB58String: () => idString }) - maConn = this.metrics.trackStream({ stream: maConn, remotePeer: proxyPeer }) - } - - log('Starting the inbound connection upgrade') - - // Protect - let protectedConn = maConn - if (this.protector) { - protectedConn = await this.protector.protect(maConn) - } - - try { - // Encrypt the connection - ({ - conn: encryptedConn, - remotePeer, - protocol: cryptoProtocol - } = await this._encryptInbound(this.localPeer, protectedConn, this.cryptos)) - - if (await this.connectionGater.denyInboundEncryptedConnection(remotePeer, encryptedConn)) { - throw errCode(new Error('The multiaddr connection is blocked by gater.acceptEncryptedConnection'), codes.ERR_CONNECTION_INTERCEPTED) - } - - // Multiplex the connection - if (this.muxers.size) { - ({ stream: upgradedConn, Muxer } = await this._multiplexInbound(encryptedConn, this.muxers)) - } else { - upgradedConn = encryptedConn - } - } catch (/** @type {any} */ err) { - log.error('Failed to upgrade inbound connection', err) - await maConn.close(err) - throw err - } - - if (await this.connectionGater.denyInboundUpgradedConnection(remotePeer, encryptedConn)) { - throw errCode(new Error('The multiaddr connection is blocked by gater.acceptEncryptedConnection'), codes.ERR_CONNECTION_INTERCEPTED) - } - - if (this.metrics) { - this.metrics.updatePlaceholder(proxyPeer, remotePeer) - setPeer(remotePeer) - } - - log('Successfully upgraded inbound connection') - - return this._createConnection({ - cryptoProtocol, - direction: 'inbound', - maConn, - upgradedConn, - Muxer, - remotePeer - }) - } - - /** - * Upgrades an outbound connection - * - * @async - * @param {MultiaddrConnection} maConn - * @returns {Promise} - */ - async upgradeOutbound (maConn) { - const idStr = maConn.remoteAddr.getPeerId() - if (!idStr) { - throw errCode(new Error('outbound connection must have a peer id'), codes.ERR_INVALID_MULTIADDR) - } - - const remotePeerId = PeerId.createFromB58String(idStr) - - if (await this.connectionGater.denyOutboundConnection(remotePeerId, maConn)) { - throw errCode(new Error('The multiaddr connection is blocked by connectionGater.denyOutboundConnection'), codes.ERR_CONNECTION_INTERCEPTED) - } - - let encryptedConn - let remotePeer - let upgradedConn - let cryptoProtocol - let Muxer - let setPeer - let proxyPeer - - if (this.metrics) { - ({ setTarget: setPeer, proxy: proxyPeer } = mutableProxy()) - const idString = (Math.random() * 1e9).toString(36) + Date.now() - setPeer({ toB58String: () => idString }) - maConn = this.metrics.trackStream({ stream: maConn, remotePeer: proxyPeer }) - } - - log('Starting the outbound connection upgrade') - - // Protect - let protectedConn = maConn - if (this.protector) { - protectedConn = await this.protector.protect(maConn) - } - - try { - // Encrypt the connection - ({ - conn: encryptedConn, - remotePeer, - protocol: cryptoProtocol - } = await this._encryptOutbound(this.localPeer, protectedConn, remotePeerId, this.cryptos)) - - if (await this.connectionGater.denyOutboundEncryptedConnection(remotePeer, encryptedConn)) { - throw errCode(new Error('The multiaddr connection is blocked by gater.acceptEncryptedConnection'), codes.ERR_CONNECTION_INTERCEPTED) - } - - // Multiplex the connection - if (this.muxers.size) { - ({ stream: upgradedConn, Muxer } = await this._multiplexOutbound(encryptedConn, this.muxers)) - } else { - upgradedConn = encryptedConn - } - } catch (/** @type {any} */ err) { - log.error('Failed to upgrade outbound connection', err) - await maConn.close(err) - throw err - } - - if (await this.connectionGater.denyOutboundUpgradedConnection(remotePeer, encryptedConn)) { - throw errCode(new Error('The multiaddr connection is blocked by gater.acceptEncryptedConnection'), codes.ERR_CONNECTION_INTERCEPTED) - } - - if (this.metrics) { - this.metrics.updatePlaceholder(proxyPeer, remotePeer) - setPeer(remotePeer) - } - - log('Successfully upgraded outbound connection') - - return this._createConnection({ - cryptoProtocol, - direction: 'outbound', - maConn, - upgradedConn, - Muxer, - remotePeer - }) - } - - /** - * A convenience method for generating a new `Connection` - * - * @private - * @param {object} options - * @param {string} options.cryptoProtocol - The crypto protocol that was negotiated - * @param {'inbound' | 'outbound'} options.direction - One of ['inbound', 'outbound'] - * @param {MultiaddrConnection} options.maConn - The transport layer connection - * @param {MuxedStream | MultiaddrConnection} options.upgradedConn - A duplex connection returned from multiplexer and/or crypto selection - * @param {MuxerFactory} [options.Muxer] - The muxer to be used for muxing - * @param {PeerId} options.remotePeer - The peer the connection is with - * @returns {Connection} - */ - _createConnection ({ - cryptoProtocol, - direction, - maConn, - upgradedConn, - Muxer, - remotePeer - }) { - /** @type {import("libp2p-interfaces/src/stream-muxer/types").Muxer} */ - let muxer - /** @type {import("libp2p-interfaces/src/connection/connection").CreatedMuxedStream | undefined} */ - let newStream - /** @type {Connection} */ - let connection // eslint-disable-line prefer-const - - if (Muxer) { - // Create the muxer - muxer = new Muxer({ - // Run anytime a remote stream is created - onStream: async muxedStream => { - if (!connection) return - const mss = new Multistream.Listener(muxedStream) - try { - const { stream, protocol } = await mss.handle(Array.from(this.protocols.keys())) - log('%s: incoming stream opened on %s', direction, protocol) - if (this.metrics) this.metrics.trackStream({ stream, remotePeer, protocol }) - connection.addStream(muxedStream, { protocol }) - this._onStream({ connection, stream: { ...muxedStream, ...stream }, protocol }) - } catch (/** @type {any} */ err) { - log.error(err) - } - }, - // Run anytime a stream closes - onStreamEnd: muxedStream => { - connection.removeStream(muxedStream.id) - } - }) - - newStream = async (protocols) => { - log('%s: starting new stream on %s', direction, protocols) - const muxedStream = muxer.newStream() - const mss = new Multistream.Dialer(muxedStream) - try { - const { stream, protocol } = await mss.select(protocols) - if (this.metrics) this.metrics.trackStream({ stream, remotePeer, protocol }) - return { stream: { ...muxedStream, ...stream }, protocol } - } catch (/** @type {any} */ err) { - log.error('could not create new stream', err) - throw errCode(err, codes.ERR_UNSUPPORTED_PROTOCOL) - } - } - - // Pipe all data through the muxer - pipe(upgradedConn, muxer, upgradedConn).catch(log.error) - } - - const _timeline = maConn.timeline - maConn.timeline = new Proxy(_timeline, { - set: (...args) => { - if (connection && args[1] === 'close' && args[2] && !_timeline.close) { - // Wait for close to finish before notifying of the closure - (async () => { - try { - if (connection.stat.status === 'open') { - await connection.close() - } - } catch (/** @type {any} */ err) { - log.error(err) - } finally { - this.onConnectionEnd(connection) - } - })().catch(err => { - log.error(err) - }) - } - - return Reflect.set(...args) - } - }) - maConn.timeline.upgraded = Date.now() - - const errConnectionNotMultiplexed = () => { - throw errCode(new Error('connection is not multiplexed'), codes.ERR_CONNECTION_NOT_MULTIPLEXED) - } - - // Create the connection - connection = new Connection({ - localAddr: maConn.localAddr, - remoteAddr: maConn.remoteAddr, - localPeer: this.localPeer, - remotePeer: remotePeer, - stat: { - direction, - // @ts-ignore - timeline: maConn.timeline, - multiplexer: Muxer && Muxer.multicodec, - encryption: cryptoProtocol - }, - newStream: newStream || errConnectionNotMultiplexed, - getStreams: () => muxer ? muxer.streams : errConnectionNotMultiplexed(), - close: async () => { - await maConn.close() - // Ensure remaining streams are aborted - if (muxer) { - muxer.streams.map(stream => stream.abort()) - } - } - }) - - this.onConnection(connection) - - return connection - } - - /** - * Routes incoming streams to the correct handler - * - * @private - * @param {object} options - * @param {Connection} options.connection - The connection the stream belongs to - * @param {MuxedStream} options.stream - * @param {string} options.protocol - */ - _onStream ({ connection, stream, protocol }) { - const handler = this.protocols.get(protocol) - handler({ connection, stream, protocol }) - } - - /** - * Attempts to encrypt the incoming `connection` with the provided `cryptos`. - * - * @private - * @async - * @param {PeerId} localPeer - The initiators PeerId - * @param {*} connection - * @param {Map} cryptos - * @returns {Promise} An encrypted connection, remote peer `PeerId` and the protocol of the `Crypto` used - */ - async _encryptInbound (localPeer, connection, cryptos) { - const mss = new Multistream.Listener(connection) - const protocols = Array.from(cryptos.keys()) - log('handling inbound crypto protocol selection', protocols) - - try { - const { stream, protocol } = await mss.handle(protocols) - const crypto = cryptos.get(protocol) - log('encrypting inbound connection...') - - if (!crypto) { - throw new Error(`no crypto module found for ${protocol}`) - } - - return { - ...await crypto.secureInbound(localPeer, stream), - protocol - } - } catch (/** @type {any} */ err) { - throw errCode(err, codes.ERR_ENCRYPTION_FAILED) - } - } - - /** - * 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 {MultiaddrConnection} connection - * @param {PeerId} remotePeerId - * @param {Map} cryptos - * @returns {Promise} An encrypted connection, remote peer `PeerId` and the protocol of the `Crypto` used - */ - async _encryptOutbound (localPeer, connection, remotePeerId, cryptos) { - const mss = new Multistream.Dialer(connection) - const protocols = Array.from(cryptos.keys()) - log('selecting outbound crypto protocol', protocols) - - try { - const { stream, protocol } = await mss.select(protocols) - const crypto = cryptos.get(protocol) - log('encrypting outbound connection to %j', remotePeerId) - - if (!crypto) { - throw new Error(`no crypto module found for ${protocol}`) - } - - return { - ...await crypto.secureOutbound(localPeer, stream, remotePeerId), - protocol - } - } catch (/** @type {any} */ err) { - throw errCode(err, codes.ERR_ENCRYPTION_FAILED) - } - } - - /** - * Selects one of the given muxers via multistream-select. That - * muxer will be used for all future streams on the connection. - * - * @private - * @async - * @param {MultiaddrConnection} connection - A basic duplex connection to multiplex - * @param {Map} muxers - The muxers to attempt multiplexing with - * @returns {Promise<{ stream: MuxedStream, Muxer?: MuxerFactory}>} A muxed connection - */ - async _multiplexOutbound (connection, muxers) { - const dialer = new Multistream.Dialer(connection) - const protocols = Array.from(muxers.keys()) - log('outbound selecting muxer %s', protocols) - try { - const { stream, protocol } = await dialer.select(protocols) - log('%s selected as muxer protocol', protocol) - const Muxer = muxers.get(protocol) - return { stream, Muxer } - } catch (/** @type {any} */ err) { - throw errCode(err, codes.ERR_MUXER_UNAVAILABLE) - } - } - - /** - * 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 {MultiaddrConnection} connection - A basic duplex connection to multiplex - * @param {Map} muxers - The muxers to attempt multiplexing with - * @returns {Promise<{ stream: MuxedStream, Muxer?: MuxerFactory}>} A muxed connection - */ - async _multiplexInbound (connection, muxers) { - const listener = new Multistream.Listener(connection) - const protocols = Array.from(muxers.keys()) - log('inbound handling muxers %s', protocols) - try { - const { stream, protocol } = await listener.handle(protocols) - const Muxer = muxers.get(protocol) - return { stream, Muxer } - } catch (/** @type {any} */ err) { - throw errCode(err, codes.ERR_MUXER_UNAVAILABLE) - } - } -} - -module.exports = Upgrader diff --git a/src/upgrader.ts b/src/upgrader.ts new file mode 100644 index 0000000000..12e6ee7712 --- /dev/null +++ b/src/upgrader.ts @@ -0,0 +1,499 @@ +import { logger } from '@libp2p/logger' +import errCode from 'err-code' +import { Dialer, Listener } from '@libp2p/multistream-select' +import { pipe } from 'it-pipe' +// @ts-expect-error mutable-proxy does not export types +import mutableProxy from 'mutable-proxy' +import { codes } from './errors.js' +import { createConnection } from '@libp2p/connection' +import { CustomEvent, EventEmitter } from '@libp2p/interfaces' +import { peerIdFromString } from '@libp2p/peer-id' +import type { Connection, ProtocolStream, Stream } from '@libp2p/interfaces/connection' +import type { ConnectionEncrypter, SecuredConnection } from '@libp2p/interfaces/connection-encrypter' +import type { StreamMuxer, StreamMuxerFactory } from '@libp2p/interfaces/stream-muxer' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { MultiaddrConnection, Upgrader, UpgraderEvents } from '@libp2p/interfaces/transport' +import type { Duplex } from 'it-stream-types' +import type { Components } from '@libp2p/interfaces/components' + +const log = logger('libp2p:upgrader') + +interface CreateConectionOptions { + cryptoProtocol: string + direction: 'inbound' | 'outbound' + maConn: MultiaddrConnection + upgradedConn: Duplex + remotePeer: PeerId + muxerFactory?: StreamMuxerFactory +} + +interface OnStreamOptions { + connection: Connection + stream: Stream + protocol: string +} + +export interface CryptoResult extends SecuredConnection { + protocol: string +} + +export interface UpgraderInit { + connectionEncryption: ConnectionEncrypter[] + muxers: StreamMuxerFactory[] +} + +export class DefaultUpgrader extends EventEmitter implements Upgrader { + private readonly components: Components + private readonly connectionEncryption: Map + private readonly muxers: Map + + constructor (components: Components, init: UpgraderInit) { + super() + + this.components = components + this.connectionEncryption = new Map() + + init.connectionEncryption.forEach(encrypter => { + this.connectionEncryption.set(encrypter.protocol, encrypter) + }) + + this.muxers = new Map() + + init.muxers.forEach(muxer => { + this.muxers.set(muxer.protocol, muxer) + }) + } + + /** + * Upgrades an inbound connection + */ + async upgradeInbound (maConn: MultiaddrConnection): Promise { + let encryptedConn + let remotePeer + let upgradedConn: Duplex + let muxerFactory: StreamMuxerFactory | undefined + let cryptoProtocol + let setPeer + let proxyPeer + const metrics = this.components.getMetrics() + + if (await this.components.getConnectionGater().denyInboundConnection(maConn)) { + throw errCode(new Error('The multiaddr connection is blocked by gater.acceptConnection'), codes.ERR_CONNECTION_INTERCEPTED) + } + + if (metrics != null) { + ({ setTarget: setPeer, proxy: proxyPeer } = mutableProxy()) + const idString = `${(Math.random() * 1e9).toString(36)}${Date.now()}` + setPeer({ toString: () => idString }) + maConn = metrics.trackStream({ stream: maConn, remotePeer: proxyPeer }) + } + + log('starting the inbound connection upgrade') + + // Protect + let protectedConn = maConn + const protector = this.components.getConnectionProtector() + + if (protector != null) { + log('protecting the inbound connection') + protectedConn = await protector.protect(maConn) + } + + try { + // Encrypt the connection + ({ + conn: encryptedConn, + remotePeer, + protocol: cryptoProtocol + } = await this._encryptInbound(protectedConn)) + + if (await this.components.getConnectionGater().denyInboundEncryptedConnection(remotePeer, { + ...protectedConn, + ...encryptedConn + })) { + throw errCode(new Error('The multiaddr connection is blocked by gater.acceptEncryptedConnection'), codes.ERR_CONNECTION_INTERCEPTED) + } + + // Multiplex the connection + if (this.muxers.size > 0) { + const multiplexed = await this._multiplexInbound({ + ...protectedConn, + ...encryptedConn + }, this.muxers) + muxerFactory = multiplexed.muxerFactory + upgradedConn = multiplexed.stream + } else { + upgradedConn = encryptedConn + } + } catch (err: any) { + log.error('Failed to upgrade inbound connection', err) + await maConn.close(err) + throw err + } + + if (await this.components.getConnectionGater().denyInboundUpgradedConnection(remotePeer, { + ...protectedConn, + ...encryptedConn + })) { + throw errCode(new Error('The multiaddr connection is blocked by gater.acceptEncryptedConnection'), codes.ERR_CONNECTION_INTERCEPTED) + } + + if (metrics != null) { + metrics.updatePlaceholder(proxyPeer, remotePeer) + setPeer(remotePeer) + } + + log('Successfully upgraded inbound connection') + + return this._createConnection({ + cryptoProtocol, + direction: 'inbound', + maConn, + upgradedConn, + muxerFactory, + remotePeer + }) + } + + /** + * Upgrades an outbound connection + */ + async upgradeOutbound (maConn: MultiaddrConnection): Promise { + const idStr = maConn.remoteAddr.getPeerId() + if (idStr == null) { + throw errCode(new Error('outbound connection must have a peer id'), codes.ERR_INVALID_MULTIADDR) + } + + const remotePeerId = peerIdFromString(idStr) + + if (await this.components.getConnectionGater().denyOutboundConnection(remotePeerId, maConn)) { + throw errCode(new Error('The multiaddr connection is blocked by connectionGater.denyOutboundConnection'), codes.ERR_CONNECTION_INTERCEPTED) + } + + let encryptedConn + let remotePeer + let upgradedConn + let cryptoProtocol + let muxerFactory + let setPeer + let proxyPeer + const metrics = this.components.getMetrics() + + if (metrics != null) { + ({ setTarget: setPeer, proxy: proxyPeer } = mutableProxy()) + const idString = `${(Math.random() * 1e9).toString(36)}${Date.now()}` + setPeer({ toB58String: () => idString }) + maConn = metrics.trackStream({ stream: maConn, remotePeer: proxyPeer }) + } + + log('Starting the outbound connection upgrade') + + // Protect + let protectedConn = maConn + const protector = this.components.getConnectionProtector() + + if (protector != null) { + protectedConn = await protector.protect(maConn) + } + + try { + // Encrypt the connection + ({ + conn: encryptedConn, + remotePeer, + protocol: cryptoProtocol + } = await this._encryptOutbound(protectedConn, remotePeerId)) + + if (await this.components.getConnectionGater().denyOutboundEncryptedConnection(remotePeer, { + ...protectedConn, + ...encryptedConn + })) { + throw errCode(new Error('The multiaddr connection is blocked by gater.acceptEncryptedConnection'), codes.ERR_CONNECTION_INTERCEPTED) + } + + // Multiplex the connection + if (this.muxers.size > 0) { + const multiplexed = await this._multiplexOutbound({ + ...protectedConn, + ...encryptedConn + }, this.muxers) + muxerFactory = multiplexed.muxerFactory + upgradedConn = multiplexed.stream + } else { + upgradedConn = encryptedConn + } + } catch (err: any) { + log.error('Failed to upgrade outbound connection', err) + await maConn.close(err) + throw err + } + + if (await this.components.getConnectionGater().denyOutboundUpgradedConnection(remotePeer, { + ...protectedConn, + ...encryptedConn + })) { + throw errCode(new Error('The multiaddr connection is blocked by gater.acceptEncryptedConnection'), codes.ERR_CONNECTION_INTERCEPTED) + } + + if (metrics != null) { + metrics.updatePlaceholder(proxyPeer, remotePeer) + setPeer(remotePeer) + } + + log('Successfully upgraded outbound connection') + + return this._createConnection({ + cryptoProtocol, + direction: 'outbound', + maConn, + upgradedConn, + muxerFactory, + remotePeer + }) + } + + /** + * A convenience method for generating a new `Connection` + */ + _createConnection (opts: CreateConectionOptions): Connection { + const { + cryptoProtocol, + direction, + maConn, + upgradedConn, + remotePeer, + muxerFactory + } = opts + + let muxer: StreamMuxer | undefined + let newStream: ((multicodecs: string[]) => Promise) | undefined + let connection: Connection // eslint-disable-line prefer-const + + if (muxerFactory != null) { + // Create the muxer + muxer = muxerFactory.createStreamMuxer(this.components, { + // Run anytime a remote stream is created + onIncomingStream: muxedStream => { + if (connection == null) { + return + } + + void Promise.resolve() + .then(async () => { + const mss = new Listener(muxedStream) + const protocols = this.components.getRegistrar().getProtocols() + const { stream, protocol } = await mss.handle(protocols) + log('%s: incoming stream opened on %s', direction, protocol) + + const metrics = this.components.getMetrics() + + if (metrics != null) { + metrics.trackStream({ stream, remotePeer, protocol }) + } + + if (connection == null) { + return + } + + connection.addStream(muxedStream, { protocol }) + this._onStream({ connection, stream: { ...muxedStream, ...stream }, protocol }) + }) + .catch(err => { + log.error(err) + }) + }, + // Run anytime a stream closes + onStreamEnd: muxedStream => { + connection?.removeStream(muxedStream.id) + } + }) + + newStream = async (protocols: string[]): Promise => { + if (muxer == null) { + throw errCode(new Error('Stream is not multiplexed'), codes.ERR_MUXER_UNAVAILABLE) + } + + log('%s: starting new stream on %s', direction, protocols) + const muxedStream = muxer.newStream() + const mss = new Dialer(muxedStream) + const metrics = this.components.getMetrics() + + try { + let { stream, protocol } = await mss.select(protocols) + + if (metrics != null) { + stream = metrics.trackStream({ stream, remotePeer, protocol }) + } + + return { stream: { ...muxedStream, ...stream }, protocol } + } catch (err: any) { + log.error('could not create new stream', err) + throw errCode(err, codes.ERR_UNSUPPORTED_PROTOCOL) + } + } + + // Pipe all data through the muxer + pipe(upgradedConn, muxer, upgradedConn).catch(log.error) + } + + const _timeline = maConn.timeline + maConn.timeline = new Proxy(_timeline, { + set: (...args) => { + if (connection != null && args[1] === 'close' && args[2] != null && _timeline.close == null) { + // Wait for close to finish before notifying of the closure + (async () => { + try { + if (connection.stat.status === 'OPEN') { + await connection.close() + } + } catch (err: any) { + log.error(err) + } finally { + this.dispatchEvent(new CustomEvent('connectionEnd', { + detail: connection + })) + } + })().catch(err => { + log.error(err) + }) + } + + return Reflect.set(...args) + } + }) + maConn.timeline.upgraded = Date.now() + + const errConnectionNotMultiplexed = () => { + throw errCode(new Error('connection is not multiplexed'), codes.ERR_CONNECTION_NOT_MULTIPLEXED) + } + + // Create the connection + connection = createConnection({ + remoteAddr: maConn.remoteAddr, + remotePeer: remotePeer, + stat: { + status: 'OPEN', + direction, + timeline: maConn.timeline, + multiplexer: muxer?.protocol, + encryption: cryptoProtocol + }, + newStream: newStream ?? errConnectionNotMultiplexed, + getStreams: () => muxer != null ? muxer.streams : errConnectionNotMultiplexed(), + close: async () => { + await maConn.close() + // Ensure remaining streams are aborted + if (muxer != null) { + muxer.streams.map(stream => stream.abort()) + } + } + }) + + this.dispatchEvent(new CustomEvent('connection', { + detail: connection + })) + + return connection + } + + /** + * Routes incoming streams to the correct handler + */ + _onStream (opts: OnStreamOptions): void { + const { connection, stream, protocol } = opts + const handler = this.components.getRegistrar().getHandler(protocol) + handler({ connection, stream, protocol }) + } + + /** + * Attempts to encrypt the incoming `connection` with the provided `cryptos` + */ + async _encryptInbound (connection: Duplex): Promise { + const mss = new Listener(connection) + const protocols = Array.from(this.connectionEncryption.keys()) + log('handling inbound crypto protocol selection', protocols) + + try { + const { stream, protocol } = await mss.handle(protocols) + const encrypter = this.connectionEncryption.get(protocol) + + if (encrypter == null) { + throw new Error(`no crypto module found for ${protocol}`) + } + + log('encrypting inbound connection...') + + return { + ...await encrypter.secureInbound(this.components.getPeerId(), stream), + protocol + } + } catch (err: any) { + throw errCode(err, codes.ERR_ENCRYPTION_FAILED) + } + } + + /** + * Attempts to encrypt the given `connection` with the provided connection encrypters. + * The first `ConnectionEncrypter` module to succeed will be used + */ + async _encryptOutbound (connection: MultiaddrConnection, remotePeerId: PeerId): Promise { + const mss = new Dialer(connection) + const protocols = Array.from(this.connectionEncryption.keys()) + log('selecting outbound crypto protocol', protocols) + + try { + const { stream, protocol } = await mss.select(protocols) + const encrypter = this.connectionEncryption.get(protocol) + + if (encrypter == null) { + throw new Error(`no crypto module found for ${protocol}`) + } + + log('encrypting outbound connection to %p', remotePeerId) + + return { + ...await encrypter.secureOutbound(this.components.getPeerId(), stream, remotePeerId), + protocol + } + } catch (err: any) { + throw errCode(err, codes.ERR_ENCRYPTION_FAILED) + } + } + + /** + * Selects one of the given muxers via multistream-select. That + * muxer will be used for all future streams on the connection. + */ + async _multiplexOutbound (connection: MultiaddrConnection, muxers: Map): Promise<{ stream: Duplex, muxerFactory?: StreamMuxerFactory}> { + const dialer = new Dialer(connection) + const protocols = Array.from(muxers.keys()) + log('outbound selecting muxer %s', protocols) + try { + const { stream, protocol } = await dialer.select(protocols) + log('%s selected as muxer protocol', protocol) + const muxerFactory = muxers.get(protocol) + return { stream, muxerFactory } + } catch (err: any) { + log.error('error multiplexing outbound stream', err) + throw errCode(err, codes.ERR_MUXER_UNAVAILABLE) + } + } + + /** + * Registers support for one of the given muxers via multistream-select. The + * selected muxer will be used for all future streams on the connection. + */ + async _multiplexInbound (connection: MultiaddrConnection, muxers: Map): Promise<{ stream: Duplex, muxerFactory?: StreamMuxerFactory}> { + const listener = new Listener(connection) + const protocols = Array.from(muxers.keys()) + log('inbound handling muxers %s', protocols) + try { + const { stream, protocol } = await listener.handle(protocols) + const muxerFactory = muxers.get(protocol) + return { stream, muxerFactory } + } catch (err: any) { + log.error('error multiplexing inbound stream', err) + throw errCode(err, codes.ERR_MUXER_UNAVAILABLE) + } + } +} diff --git a/src/version.ts b/src/version.ts new file mode 100644 index 0000000000..c77a04e17f --- /dev/null +++ b/src/version.ts @@ -0,0 +1,3 @@ + +export const version = '0.0.0' +export const name = 'libp2p' diff --git a/test/addresses/address-manager.spec.js b/test/addresses/address-manager.spec.js deleted file mode 100644 index 1074700864..0000000000 --- a/test/addresses/address-manager.spec.js +++ /dev/null @@ -1,146 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const { Multiaddr } = require('multiaddr') -const PeerId = require('peer-id') - -const AddressManager = require('../../src/address-manager') -const peerUtils = require('../utils/creators/peer') - -const Peers = require('../fixtures/peers') - -const listenAddresses = ['/ip4/127.0.0.1/tcp/15006/ws', '/ip4/127.0.0.1/tcp/15008/ws'] -const announceAddreses = ['/dns4/peer.io'] - -describe('Address Manager', () => { - let peerId - - before(async () => { - peerId = await PeerId.createFromJSON(Peers[0]) - }) - - it('should not need any addresses', () => { - const am = new AddressManager(peerId) - - expect(am.listen.size).to.equal(0) - expect(am.announce.size).to.equal(0) - }) - - it('should return listen multiaddrs on get', () => { - const am = new AddressManager(peerId, { - listen: listenAddresses - }) - - expect(am.listen.size).to.equal(listenAddresses.length) - expect(am.announce.size).to.equal(0) - - const listenMultiaddrs = am.getListenAddrs() - expect(listenMultiaddrs.length).to.equal(2) - expect(listenMultiaddrs[0].equals(new Multiaddr(listenAddresses[0]))).to.equal(true) - expect(listenMultiaddrs[1].equals(new Multiaddr(listenAddresses[1]))).to.equal(true) - }) - - it('should return announce multiaddrs on get', () => { - const am = new AddressManager(peerId, { - listen: listenAddresses, - announce: announceAddreses - }) - - expect(am.listen.size).to.equal(listenAddresses.length) - expect(am.announce.size).to.equal(announceAddreses.length) - - const announceMultiaddrs = am.getAnnounceAddrs() - expect(announceMultiaddrs.length).to.equal(1) - expect(announceMultiaddrs[0].equals(new Multiaddr(announceAddreses[0]))).to.equal(true) - }) - - it('should add observed addresses', () => { - const am = new AddressManager(peerId) - - expect(am.observed).to.be.empty() - - am.addObservedAddr('/ip4/123.123.123.123/tcp/39201') - - expect(am.observed).to.have.property('size', 1) - }) - - it('should dedupe added observed addresses', () => { - const ma = '/ip4/123.123.123.123/tcp/39201' - const am = new AddressManager(peerId) - - expect(am.observed).to.be.empty() - - am.addObservedAddr(ma) - am.addObservedAddr(ma) - am.addObservedAddr(ma) - - expect(am.observed).to.have.property('size', 1) - expect(am.observed).to.include(ma) - }) - - it('should only emit one change:addresses event', () => { - const ma = '/ip4/123.123.123.123/tcp/39201' - const am = new AddressManager(peerId) - let eventCount = 0 - - am.on('change:addresses', () => { - eventCount++ - }) - - am.addObservedAddr(ma) - am.addObservedAddr(ma) - am.addObservedAddr(ma) - am.addObservedAddr(`${ma}/p2p/${peerId}`) - am.addObservedAddr(`${ma}/p2p/${peerId.toB58String()}`) - - expect(eventCount).to.equal(1) - }) - - it('should strip our peer address from added observed addresses', () => { - const ma = '/ip4/123.123.123.123/tcp/39201' - const am = new AddressManager(peerId) - - expect(am.observed).to.be.empty() - - am.addObservedAddr(ma) - am.addObservedAddr(`${ma}/p2p/${peerId}`) - - expect(am.observed).to.have.property('size', 1) - expect(am.observed).to.include(ma) - }) - - it('should strip our peer address from added observed addresses in difference formats', () => { - const ma = '/ip4/123.123.123.123/tcp/39201' - const am = new AddressManager(peerId) - - expect(am.observed).to.be.empty() - - am.addObservedAddr(ma) - am.addObservedAddr(`${ma}/p2p/${peerId}`) // base32 CID - am.addObservedAddr(`${ma}/p2p/${peerId.toB58String()}`) // base58btc - - expect(am.observed).to.have.property('size', 1) - expect(am.observed).to.include(ma) - }) -}) - -describe('libp2p.addressManager', () => { - let libp2p - afterEach(() => libp2p && libp2p.stop()) - - it('should populate the AddressManager from the config', async () => { - [libp2p] = await peerUtils.createPeer({ - started: false, - config: { - addresses: { - listen: listenAddresses, - announce: announceAddreses - } - } - }) - - expect(libp2p.addressManager.listen.size).to.equal(listenAddresses.length) - expect(libp2p.addressManager.announce.size).to.equal(announceAddreses.length) - }) -}) diff --git a/test/addresses/address-manager.spec.ts b/test/addresses/address-manager.spec.ts new file mode 100644 index 0000000000..a667e6d9fc --- /dev/null +++ b/test/addresses/address-manager.spec.ts @@ -0,0 +1,188 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import { Multiaddr, protocols } from '@multiformats/multiaddr' +import { AddressFilter, DefaultAddressManager } from '../../src/address-manager/index.js' +import { createNode } from '../utils/creators/peer.js' +import { createFromJSON } from '@libp2p/peer-id-factory' +import Peers from '../fixtures/peers.js' +import { stubInterface } from 'ts-sinon' +import type { TransportManager } from '@libp2p/interfaces/transport' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { Libp2p } from '../../src/index.js' +import { Components } from '@libp2p/interfaces/components' + +const listenAddresses = ['/ip4/127.0.0.1/tcp/15006/ws', '/ip4/127.0.0.1/tcp/15008/ws'] +const announceAddreses = ['/dns4/peer.io'] + +describe('Address Manager', () => { + let peerId: PeerId + + before(async () => { + peerId = await createFromJSON(Peers[0]) + }) + + it('should not need any addresses', () => { + const am = new DefaultAddressManager(new Components({ + peerId, + transportManager: stubInterface() + }), { + announceFilter: stubInterface() + }) + + expect(am.getListenAddrs()).to.be.empty() + expect(am.getAnnounceAddrs()).to.be.empty() + }) + + it('should return listen multiaddrs on get', () => { + const am = new DefaultAddressManager(new Components({ + peerId, + transportManager: stubInterface() + }), { + announceFilter: stubInterface(), + listen: listenAddresses + }) + + expect(am.getListenAddrs()).to.have.lengthOf(listenAddresses.length) + expect(am.getAnnounceAddrs()).to.be.empty() + + const listenMultiaddrs = am.getListenAddrs() + expect(listenMultiaddrs.length).to.equal(2) + expect(listenMultiaddrs[0].equals(new Multiaddr(listenAddresses[0]))).to.equal(true) + expect(listenMultiaddrs[1].equals(new Multiaddr(listenAddresses[1]))).to.equal(true) + }) + + it('should return announce multiaddrs on get', () => { + const am = new DefaultAddressManager(new Components({ + peerId, + transportManager: stubInterface() + }), { + announceFilter: stubInterface(), + listen: listenAddresses, + announce: announceAddreses + }) + + expect(am.getListenAddrs()).to.have.lengthOf(listenAddresses.length) + expect(am.getAnnounceAddrs()).to.have.lengthOf(announceAddreses.length) + + const announceMultiaddrs = am.getAnnounceAddrs() + expect(announceMultiaddrs.length).to.equal(1) + expect(announceMultiaddrs[0].equals(new Multiaddr(announceAddreses[0]))).to.equal(true) + }) + + it('should add observed addresses', () => { + const am = new DefaultAddressManager(new Components({ + peerId, + transportManager: stubInterface() + }), { + announceFilter: stubInterface() + }) + + expect(am.getObservedAddrs()).to.be.empty() + + am.addObservedAddr('/ip4/123.123.123.123/tcp/39201') + + expect(am.getObservedAddrs()).to.have.lengthOf(1) + }) + + it('should dedupe added observed addresses', () => { + const ma = '/ip4/123.123.123.123/tcp/39201' + const am = new DefaultAddressManager(new Components({ + peerId, + transportManager: stubInterface() + }), { + announceFilter: stubInterface() + }) + + expect(am.getObservedAddrs()).to.be.empty() + + am.addObservedAddr(ma) + am.addObservedAddr(ma) + am.addObservedAddr(ma) + + expect(am.getObservedAddrs()).to.have.lengthOf(1) + expect(am.getObservedAddrs().map(ma => ma.toString())).to.include(ma) + }) + + it('should only emit one change:addresses event', () => { + const ma = '/ip4/123.123.123.123/tcp/39201' + const am = new DefaultAddressManager(new Components({ + peerId, + transportManager: stubInterface() + }), { + announceFilter: stubInterface() + }) + let eventCount = 0 + + am.addEventListener('change:addresses', () => { + eventCount++ + }) + + am.addObservedAddr(ma) + am.addObservedAddr(ma) + am.addObservedAddr(ma) + am.addObservedAddr(`${ma}/p2p/${peerId.toString()}`) + + expect(eventCount).to.equal(1) + }) + + it('should strip our peer address from added observed addresses', () => { + const ma = '/ip4/123.123.123.123/tcp/39201' + const am = new DefaultAddressManager(new Components({ + peerId, + transportManager: stubInterface() + }), { + announceFilter: stubInterface() + }) + + expect(am.getObservedAddrs()).to.be.empty() + + am.addObservedAddr(ma) + am.addObservedAddr(`${ma}/p2p/${peerId.toString()}`) + + expect(am.getObservedAddrs()).to.have.lengthOf(1) + expect(am.getObservedAddrs().map(ma => ma.toString())).to.include(ma) + }) + + it('should strip our peer address from added observed addresses in difference formats', () => { + const ma = '/ip4/123.123.123.123/tcp/39201' + const am = new DefaultAddressManager(new Components({ + peerId, + transportManager: stubInterface() + }), { + announceFilter: stubInterface() + }) + + expect(am.getObservedAddrs()).to.be.empty() + + am.addObservedAddr(ma) + am.addObservedAddr(`${ma}/p2p/${peerId.toString()}`) + + expect(am.getObservedAddrs()).to.have.lengthOf(1) + expect(am.getObservedAddrs().map(ma => ma.toString())).to.include(ma) + }) +}) + +describe('libp2p.addressManager', () => { + let libp2p: Libp2p + afterEach(async () => { + if (libp2p != null) { + await libp2p.stop() + } + }) + + it('should populate the AddressManager from the config', async () => { + libp2p = await createNode({ + started: false, + config: { + addresses: { + listen: listenAddresses, + announce: announceAddreses + } + } + }) + + expect(libp2p.getMultiaddrs().map(ma => ma.decapsulateCode(protocols('p2p').code).toString())).to.have.members(announceAddreses) + expect(libp2p.getMultiaddrs().map(ma => ma.decapsulateCode(protocols('p2p').code).toString())).to.not.have.members(listenAddresses) + }) +}) diff --git a/test/addresses/addresses.node.js b/test/addresses/addresses.node.ts similarity index 50% rename from test/addresses/addresses.node.js rename to test/addresses/addresses.node.ts index 00d93ca13b..d656dde870 100644 --- a/test/addresses/addresses.node.js +++ b/test/addresses/addresses.node.ts @@ -1,25 +1,27 @@ -'use strict' /* eslint-env mocha */ -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') - -const { Multiaddr } = require('multiaddr') -const isLoopback = require('libp2p-utils/src/multiaddr/is-loopback') - -const { AddressesOptions } = require('./utils') -const peerUtils = require('../utils/creators/peer') +import { expect } from 'aegir/utils/chai.js' +import sinon from 'sinon' +import { Multiaddr, protocols } from '@multiformats/multiaddr' +import { isLoopback } from '@libp2p/utils/multiaddr/is-loopback' +import { AddressesOptions } from './utils.js' +import { createNode } from '../utils/creators/peer.js' +import type { Libp2pNode } from '../../src/libp2p.js' const listenAddresses = ['/ip4/127.0.0.1/tcp/0', '/ip4/127.0.0.1/tcp/8000/ws'] const announceAddreses = ['/dns4/peer.io/tcp/433/p2p/12D3KooWNvSZnPi3RrhrTwEY4LuuBeB6K6facKUCJcyWG1aoDd2p'] describe('libp2p.multiaddrs', () => { - let libp2p + let libp2p: Libp2pNode - afterEach(() => libp2p && libp2p.stop()) + afterEach(async () => { + if (libp2p != null) { + await libp2p.stop() + } + }) it('should keep listen addresses after start, even if changed', async () => { - [libp2p] = await peerUtils.createPeer({ + libp2p = await createNode({ started: false, config: { ...AddressesOptions, @@ -30,23 +32,23 @@ describe('libp2p.multiaddrs', () => { } }) - let listenAddrs = libp2p.addressManager.listen - expect(listenAddrs.size).to.equal(listenAddresses.length) - expect(listenAddrs.has(listenAddresses[0])).to.equal(true) - expect(listenAddrs.has(listenAddresses[1])).to.equal(true) + let listenAddrs = libp2p.components.getAddressManager().getListenAddrs().map(ma => ma.toString()) + expect(listenAddrs).to.have.lengthOf(listenAddresses.length) + expect(listenAddrs).to.include(listenAddresses[0]) + expect(listenAddrs).to.include(listenAddresses[1]) // Should not replace listen addresses after transport listen // Only transportManager has visibility of the port used await libp2p.start() - listenAddrs = libp2p.addressManager.listen - expect(listenAddrs.size).to.equal(listenAddresses.length) - expect(listenAddrs.has(listenAddresses[0])).to.equal(true) - expect(listenAddrs.has(listenAddresses[1])).to.equal(true) + listenAddrs = libp2p.components.getAddressManager().getListenAddrs().map(ma => ma.toString()) + expect(listenAddrs).to.have.lengthOf(listenAddresses.length) + expect(listenAddrs).to.include(listenAddresses[0]) + expect(listenAddrs).to.include(listenAddresses[1]) }) it('should announce transport listen addresses if announce addresses are not provided', async () => { - [libp2p] = await peerUtils.createPeer({ + libp2p = await createNode({ started: false, config: { ...AddressesOptions, @@ -58,11 +60,12 @@ describe('libp2p.multiaddrs', () => { await libp2p.start() - const tmListen = libp2p.transportManager.getAddrs().map((ma) => ma.toString()) + const tmListen = libp2p.components.getTransportManager().getAddrs().map((ma) => ma.toString()) // Announce 2 listen (transport) - const advertiseMultiaddrs = libp2p.multiaddrs.map((ma) => ma.toString()) - expect(advertiseMultiaddrs.length).to.equal(2) + const advertiseMultiaddrs = libp2p.components.getAddressManager().getAddresses().map((ma) => ma.decapsulateCode(protocols('p2p').code).toString()) + + expect(advertiseMultiaddrs).to.have.lengthOf(2) tmListen.forEach((m) => { expect(advertiseMultiaddrs).to.include(m) }) @@ -70,7 +73,7 @@ describe('libp2p.multiaddrs', () => { }) it('should only announce the given announce addresses when provided', async () => { - [libp2p] = await peerUtils.createPeer({ + libp2p = await createNode({ started: false, config: { ...AddressesOptions, @@ -83,10 +86,10 @@ describe('libp2p.multiaddrs', () => { await libp2p.start() - const tmListen = libp2p.transportManager.getAddrs().map((ma) => ma.toString()) + const tmListen = libp2p.components.getTransportManager().getAddrs().map((ma) => ma.toString()) // Announce 1 announce addr - const advertiseMultiaddrs = libp2p.multiaddrs.map((ma) => ma.toString()) + const advertiseMultiaddrs = libp2p.components.getAddressManager().getAddresses().map((ma) => ma.decapsulateCode(protocols('p2p').code).toString()) expect(advertiseMultiaddrs.length).to.equal(announceAddreses.length) advertiseMultiaddrs.forEach((m) => { expect(tmListen).to.not.include(m) @@ -95,7 +98,7 @@ describe('libp2p.multiaddrs', () => { }) it('can filter out loopback addresses by the announce filter', async () => { - [libp2p] = await peerUtils.createPeer({ + libp2p = await createNode({ started: false, config: { ...AddressesOptions, @@ -108,22 +111,22 @@ describe('libp2p.multiaddrs', () => { await libp2p.start() - expect(libp2p.multiaddrs.length).to.equal(0) + expect(libp2p.components.getAddressManager().getAddresses()).to.have.lengthOf(0) // Stub transportManager addresses to add a public address const stubMa = new Multiaddr('/ip4/120.220.10.1/tcp/1000') - sinon.stub(libp2p.transportManager, 'getAddrs').returns([ + sinon.stub(libp2p.components.getTransportManager(), 'getAddrs').returns([ ...listenAddresses.map((a) => new Multiaddr(a)), stubMa ]) - const multiaddrs = libp2p.multiaddrs + const multiaddrs = libp2p.components.getAddressManager().getAddresses() expect(multiaddrs.length).to.equal(1) - expect(multiaddrs[0].equals(stubMa)).to.eql(true) + expect(multiaddrs[0].decapsulateCode(protocols('p2p').code).equals(stubMa)).to.eql(true) }) it('can filter out loopback addresses to announced by the announce filter', async () => { - [libp2p] = await peerUtils.createPeer({ + libp2p = await createNode({ started: false, config: { ...AddressesOptions, @@ -135,21 +138,19 @@ describe('libp2p.multiaddrs', () => { } }) - const listenAddrs = libp2p.addressManager.listen - expect(listenAddrs.size).to.equal(listenAddresses.length) - expect(listenAddrs.has(listenAddresses[0])).to.equal(true) - expect(listenAddrs.has(listenAddresses[1])).to.equal(true) + const listenAddrs = libp2p.components.getAddressManager().getListenAddrs().map((ma) => ma.toString()) + expect(listenAddrs).to.have.lengthOf(listenAddresses.length) + expect(listenAddrs).to.include(listenAddresses[0]) + expect(listenAddrs).to.include(listenAddresses[1]) await libp2p.start() - const multiaddrs = libp2p.multiaddrs - expect(multiaddrs.length).to.equal(announceAddreses.length) - expect(multiaddrs.includes(listenAddresses[0])).to.equal(false) - expect(multiaddrs.includes(listenAddresses[1])).to.equal(false) + const loopbackAddrs = libp2p.components.getAddressManager().getAddresses().filter(ma => isLoopback(ma)) + expect(loopbackAddrs).to.be.empty() }) it('should include observed addresses in returned multiaddrs', async () => { - [libp2p] = await peerUtils.createPeer({ + libp2p = await createNode({ started: false, config: { ...AddressesOptions, @@ -162,11 +163,11 @@ describe('libp2p.multiaddrs', () => { await libp2p.start() - expect(libp2p.multiaddrs).to.have.lengthOf(listenAddresses.length) + expect(libp2p.components.getAddressManager().getAddresses()).to.have.lengthOf(listenAddresses.length) - libp2p.addressManager.addObservedAddr(ma) + libp2p.components.getAddressManager().addObservedAddr(new Multiaddr(ma)) - expect(libp2p.multiaddrs).to.have.lengthOf(listenAddresses.length + 1) - expect(libp2p.multiaddrs.map(ma => ma.toString())).to.include(ma) + expect(libp2p.components.getAddressManager().getAddresses()).to.have.lengthOf(listenAddresses.length + 1) + expect(libp2p.components.getAddressManager().getAddresses().map(ma => ma.decapsulateCode(protocols('p2p').code).toString())).to.include(ma) }) }) diff --git a/test/addresses/utils.js b/test/addresses/utils.js deleted file mode 100644 index 08295c7bb8..0000000000 --- a/test/addresses/utils.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict' - -const Transport1 = require('libp2p-tcp') -const Transport2 = require('libp2p-websockets') -const mergeOptions = require('merge-options') -const baseOptions = require('../utils/base-options') - -module.exports.baseOptions = baseOptions - -const AddressesOptions = mergeOptions(baseOptions, { - modules: { - transport: [Transport1, Transport2] - } -}) - -module.exports.AddressesOptions = AddressesOptions diff --git a/test/addresses/utils.ts b/test/addresses/utils.ts new file mode 100644 index 0000000000..ef8d95d5e3 --- /dev/null +++ b/test/addresses/utils.ts @@ -0,0 +1,10 @@ +import { TCP } from '@libp2p/tcp' +import { WebSockets } from '@libp2p/websockets' +import { createBaseOptions } from '../utils/base-options.js' + +export const AddressesOptions = createBaseOptions({ + transports: [ + new TCP(), + new WebSockets() + ] +}) diff --git a/test/configuration/protocol-prefix.node.js b/test/configuration/protocol-prefix.node.ts similarity index 61% rename from test/configuration/protocol-prefix.node.js rename to test/configuration/protocol-prefix.node.ts index aff8579ea0..3b9ab32cad 100644 --- a/test/configuration/protocol-prefix.node.js +++ b/test/configuration/protocol-prefix.node.ts @@ -1,30 +1,30 @@ -'use strict' /* eslint-env mocha */ -const { expect } = require('aegir/utils/chai') -const mergeOptions = require('merge-options') - -const { create } = require('../../src') -const { baseOptions } = require('./utils') +import { expect } from 'aegir/utils/chai.js' +import mergeOptions from 'merge-options' +import { validateConfig } from '../../src/config.js' +import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' +import { baseOptions } from './utils.js' describe('Protocol prefix is configurable', () => { - let libp2p + let libp2p: Libp2pNode afterEach(async () => { - libp2p && await libp2p.stop() + if (libp2p != null) { + await libp2p.stop() + } }) it('protocolPrefix is provided', async () => { const testProtocol = 'test-protocol' - libp2p = await create(mergeOptions(baseOptions, { - config: { - protocolPrefix: testProtocol - } + libp2p = await createLibp2pNode(mergeOptions(baseOptions, { + protocolPrefix: testProtocol })) await libp2p.start() const protocols = await libp2p.peerStore.protoBook.get(libp2p.peerId) expect(protocols).to.include.members([ + '/libp2p/fetch/0.0.1', '/libp2p/circuit/relay/0.1.0', `/${testProtocol}/id/1.0.0`, `/${testProtocol}/id/push/1.0.0`, @@ -33,7 +33,7 @@ describe('Protocol prefix is configurable', () => { }) it('protocolPrefix is not provided', async () => { - libp2p = await create(baseOptions) + libp2p = await createLibp2pNode(validateConfig(baseOptions)) await libp2p.start() const protocols = await libp2p.peerStore.protoBook.get(libp2p.peerId) diff --git a/test/configuration/pubsub.spec.js b/test/configuration/pubsub.spec.js deleted file mode 100644 index 46e6c8fbd5..0000000000 --- a/test/configuration/pubsub.spec.js +++ /dev/null @@ -1,129 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const mergeOptions = require('merge-options') -const pDefer = require('p-defer') -const delay = require('delay') - -const { create } = require('../../src') -const { baseOptions, pubsubSubsystemOptions } = require('./utils') -const peerUtils = require('../utils/creators/peer') - -describe('Pubsub subsystem is configurable', () => { - let libp2p - - afterEach(async () => { - libp2p && await libp2p.stop() - }) - - it('should not exist if no module is provided', async () => { - libp2p = await create(baseOptions) - expect(libp2p.pubsub).to.not.exist() - }) - - it('should exist if the module is provided', async () => { - libp2p = await create(pubsubSubsystemOptions) - expect(libp2p.pubsub).to.exist() - }) - - it('should start and stop by default once libp2p starts', async () => { - const [peerId] = await peerUtils.createPeerId() - - const customOptions = mergeOptions(pubsubSubsystemOptions, { - peerId - }) - - libp2p = await create(customOptions) - expect(libp2p.pubsub.started).to.equal(false) - - await libp2p.start() - expect(libp2p.pubsub.started).to.equal(true) - - await libp2p.stop() - expect(libp2p.pubsub.started).to.equal(false) - }) - - it('should not start if disabled once libp2p starts', async () => { - const [peerId] = await peerUtils.createPeerId() - - const customOptions = mergeOptions(pubsubSubsystemOptions, { - peerId, - config: { - pubsub: { - enabled: false - } - } - }) - - libp2p = await create(customOptions) - expect(libp2p.pubsub.started).to.equal(false) - - await libp2p.start() - expect(libp2p.pubsub.started).to.equal(false) - }) - - it('should allow a manual start', async () => { - const [peerId] = await peerUtils.createPeerId() - - const customOptions = mergeOptions(pubsubSubsystemOptions, { - peerId, - config: { - pubsub: { - enabled: false - } - } - }) - - libp2p = await create(customOptions) - await libp2p.start() - expect(libp2p.pubsub.started).to.equal(false) - - await libp2p.pubsub.start() - expect(libp2p.pubsub.started).to.equal(true) - }) -}) - -describe('Pubsub subscription handlers adapter', () => { - let libp2p - - beforeEach(async () => { - const [peerId] = await peerUtils.createPeerId() - - libp2p = await create(mergeOptions(pubsubSubsystemOptions, { - peerId - })) - - await libp2p.start() - }) - - afterEach(async () => { - libp2p && await libp2p.stop() - }) - - it('extends pubsub with subscribe handler', async () => { - let countMessages = 0 - const topic = 'topic' - const defer = pDefer() - - const handler = () => { - countMessages++ - if (countMessages > 1) { - throw new Error('only one message should be received') - } - - defer.resolve() - } - - await libp2p.pubsub.subscribe(topic, handler) - - libp2p.pubsub.emit(topic, 'useless-data') - await defer.promise - - await libp2p.pubsub.unsubscribe(topic, handler) - libp2p.pubsub.emit(topic, 'useless-data') - - // wait to guarantee that the handler is not called twice - await delay(100) - }) -}) diff --git a/test/configuration/pubsub.spec.ts b/test/configuration/pubsub.spec.ts new file mode 100644 index 0000000000..762a3fcc38 --- /dev/null +++ b/test/configuration/pubsub.spec.ts @@ -0,0 +1,106 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import mergeOptions from 'merge-options' +import pDefer from 'p-defer' +import delay from 'delay' +import { createLibp2p, Libp2p } from '../../src/index.js' +import { baseOptions, pubsubSubsystemOptions } from './utils.js' +import { createPeerId } from '../utils/creators/peer.js' +import { CustomEvent } from '@libp2p/interfaces' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { FloodSub } from '@libp2p/floodsub' +import type { PubSub } from '@libp2p/interfaces/pubsub' + +describe('Pubsub subsystem is configurable', () => { + let libp2p: Libp2p + + afterEach(async () => { + if (libp2p != null) { + await libp2p.stop() + } + }) + + it('should not exist if no module is provided', async () => { + libp2p = await createLibp2p(baseOptions) + expect(libp2p.pubsub).to.not.exist() + }) + + it('should exist if the module is provided', async () => { + libp2p = await createLibp2p(pubsubSubsystemOptions) + expect(libp2p.pubsub).to.exist() + }) + + it('should start and stop by default once libp2p starts', async () => { + const peerId = await createPeerId() + + const customOptions = mergeOptions(pubsubSubsystemOptions, { + peerId + }) + + libp2p = await createLibp2p(customOptions) + expect(libp2p.pubsub?.isStarted()).to.equal(false) + + await libp2p.start() + expect(libp2p.pubsub?.isStarted()).to.equal(true) + + await libp2p.stop() + expect(libp2p.pubsub?.isStarted()).to.equal(false) + }) +}) + +describe('Pubsub subscription handlers adapter', () => { + let libp2p: Libp2p + + beforeEach(async () => { + const peerId = await createPeerId() + + libp2p = await createLibp2p(mergeOptions(pubsubSubsystemOptions, { + peerId, + pubsub: new FloodSub({ + emitSelf: true + }) + })) + + await libp2p.start() + }) + + afterEach(async () => { + if (libp2p != null) { + await libp2p.stop() + } + }) + + it('extends pubsub with subscribe handler', async () => { + let countMessages = 0 + const topic = 'topic' + const defer = pDefer() + + const handler = () => { + countMessages++ + defer.resolve() + } + + const pubsub: PubSub | undefined = libp2p.pubsub + + if (pubsub == null) { + throw new Error('Pubsub was not enabled') + } + + pubsub.addEventListener(topic, handler) + pubsub.dispatchEvent(new CustomEvent(topic, { + detail: uint8ArrayFromString('useless-data') + })) + await defer.promise + + pubsub.removeEventListener(topic, handler) + pubsub.dispatchEvent(new CustomEvent(topic, { + detail: uint8ArrayFromString('useless-data') + })) + + // wait to guarantee that the handler is not called twice + await delay(100) + + expect(countMessages).to.equal(1) + }) +}) diff --git a/test/configuration/utils.js b/test/configuration/utils.js deleted file mode 100644 index 2e3ec5387a..0000000000 --- a/test/configuration/utils.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict' - -const Pubsub = require('libp2p-interfaces/src/pubsub') -const { NOISE: Crypto } = require('@chainsafe/libp2p-noise') -const Muxer = require('libp2p-mplex') -const Transport = require('libp2p-websockets') -const filters = require('libp2p-websockets/src/filters') -const transportKey = Transport.prototype[Symbol.toStringTag] - -const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser') -const relayAddr = MULTIADDRS_WEBSOCKETS[0] - -const mergeOptions = require('merge-options') - -const baseOptions = { - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - } -} - -module.exports.baseOptions = baseOptions - -class MockPubsub extends Pubsub { - constructor (libp2p, options = {}) { - super({ - debugName: 'mock-pubsub', - multicodecs: '/mock-pubsub', - libp2p, - ...options - }) - } -} - -const pubsubSubsystemOptions = mergeOptions(baseOptions, { - modules: { - pubsub: MockPubsub - }, - addresses: { - listen: [`${relayAddr}/p2p-circuit`] - }, - config: { - transport: { - [transportKey]: { - filter: filters.all - } - } - } -}) - -module.exports.pubsubSubsystemOptions = pubsubSubsystemOptions diff --git a/test/configuration/utils.ts b/test/configuration/utils.ts new file mode 100644 index 0000000000..3965d6b095 --- /dev/null +++ b/test/configuration/utils.ts @@ -0,0 +1,76 @@ +import { PubSubBaseProtocol } from '@libp2p/pubsub' +import { Plaintext } from '../../src/insecure/index.js' +import { Mplex } from '@libp2p/mplex' +import { WebSockets } from '@libp2p/websockets' +import * as filters from '@libp2p/websockets/filters' +import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' +import mergeOptions from 'merge-options' +import type { Message, PubSubInit, PubSubRPC, PubSubRPCMessage } from '@libp2p/interfaces/pubsub' +import type { Libp2pInit, Libp2pOptions } from '../../src/index.js' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import * as cborg from 'cborg' +import { peerIdFromString } from '@libp2p/peer-id' + +const relayAddr = MULTIADDRS_WEBSOCKETS[0] + +export const baseOptions: Partial = { + peerId: peerIdFromString('12D3KooWJKCJW8Y26pRFNv78TCMGLNTfyN8oKaFswMRYXTzSbSst'), + transports: [new WebSockets()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Plaintext()] +} + +class MockPubSub extends PubSubBaseProtocol { + constructor (init?: PubSubInit) { + super({ + multicodecs: ['/mock-pubsub'], + ...init + }) + } + + decodeRpc (bytes: Uint8Array): PubSubRPC { + return cborg.decode(bytes) + } + + encodeRpc (rpc: PubSubRPC): Uint8Array { + return cborg.encode(rpc) + } + + decodeMessage (bytes: Uint8Array): PubSubRPCMessage { + return cborg.decode(bytes) + } + + encodeMessage (rpc: PubSubRPCMessage): Uint8Array { + return cborg.encode(rpc) + } + + async publishMessage (from: PeerId, message: Message): Promise { + const peers = this.getSubscribers(message.topic) + + if (peers == null || peers.length === 0) { + return + } + + peers.forEach(id => { + if (this.components.getPeerId().equals(id)) { + return + } + + if (id.equals(from)) { + return + } + + this.send(id, { messages: [message] }) + }) + } +} + +export const pubsubSubsystemOptions: Libp2pOptions = mergeOptions(baseOptions, { + pubsub: new MockPubSub(), + addresses: { + listen: [`${relayAddr.toString()}/p2p-circuit`] + }, + transports: [ + new WebSockets({ filter: filters.all }) + ] +}) diff --git a/test/connection-manager/auto-dialler.spec.js b/test/connection-manager/auto-dialler.spec.js deleted file mode 100644 index 4b69adfd6f..0000000000 --- a/test/connection-manager/auto-dialler.spec.js +++ /dev/null @@ -1,64 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') -const AutoDialler = require('../../src/connection-manager/auto-dialler') -const pWaitFor = require('p-wait-for') -const PeerId = require('peer-id') -const delay = require('delay') - -describe('Auto-dialler', () => { - let autoDialler - let libp2p - let options - - beforeEach(async () => { - libp2p = {} - options = {} - autoDialler = new AutoDialler(libp2p, options) - }) - - afterEach(async () => { - sinon.restore() - }) - - it('should not dial self', async () => { - // peers with protocols are dialled before peers without protocols - const self = { - id: await PeerId.create(), - protocols: [ - '/foo/bar' - ] - } - const other = { - id: await PeerId.create(), - protocols: [] - } - - autoDialler._options.minConnections = 10 - libp2p.peerId = self.id - libp2p.connections = { - size: 1 - } - libp2p.peerStore = { - getPeers: sinon.stub().returns([self, other]) - } - libp2p.connectionManager = { - get: () => {} - } - libp2p.dialer = { - connectToPeer: sinon.stub().resolves() - } - - await autoDialler.start() - - await pWaitFor(() => libp2p.dialer.connectToPeer.callCount === 1) - await delay(1000) - - await autoDialler.stop() - - expect(libp2p.dialer.connectToPeer.callCount).to.equal(1) - expect(libp2p.dialer.connectToPeer.calledWith(self.id)).to.be.false() - }) -}) diff --git a/test/connection-manager/auto-dialler.spec.ts b/test/connection-manager/auto-dialler.spec.ts new file mode 100644 index 0000000000..0ed22920db --- /dev/null +++ b/test/connection-manager/auto-dialler.spec.ts @@ -0,0 +1,61 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import { AutoDialler } from '../../src/connection-manager/auto-dialler.js' +import pWaitFor from 'p-wait-for' +import delay from 'delay' +import { createEd25519PeerId } from '@libp2p/peer-id-factory' +import { Components } from '@libp2p/interfaces/components' +import { stubInterface } from 'ts-sinon' +import type { ConnectionManager } from '@libp2p/interfaces/registrar' +import type { PeerStore, Peer } from '@libp2p/interfaces/peer-store' +import type { Dialer } from '@libp2p/interfaces/dialer' + +describe('Auto-dialler', () => { + it('should not dial self', async () => { + // peers with protocols are dialled before peers without protocols + const self: Peer = { + id: await createEd25519PeerId(), + protocols: [ + '/foo/bar' + ], + addresses: [], + metadata: new Map() + } + const other: Peer = { + id: await createEd25519PeerId(), + protocols: [], + addresses: [], + metadata: new Map() + } + + const peerStore = stubInterface() + + peerStore.all.returns(Promise.resolve([ + self, other + ])) + + const connectionManager = stubInterface() + connectionManager.getConnectionList.returns([]) + const dialer = stubInterface() + + const autoDialler = new AutoDialler(new Components({ + peerId: self.id, + peerStore, + connectionManager, + dialer + }), { + minConnections: 10 + }) + + await autoDialler.start() + + await pWaitFor(() => dialer.dial.callCount === 1) + await delay(1000) + + await autoDialler.stop() + + expect(dialer.dial.callCount).to.equal(1) + expect(dialer.dial.calledWith(self.id)).to.be.false() + }) +}) diff --git a/test/connection-manager/index.node.js b/test/connection-manager/index.node.ts similarity index 53% rename from test/connection-manager/index.node.js rename to test/connection-manager/index.node.ts index 19f59bffc5..6eaacf8767 100644 --- a/test/connection-manager/index.node.js +++ b/test/connection-manager/index.node.ts @@ -1,153 +1,184 @@ -'use strict' /* eslint-env mocha */ -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') -const { CLOSED } = require('libp2p-interfaces/src/connection/status') - -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') -const { codes } = require('../../src/errors') -const { Multiaddr } = require('multiaddr') +import { expect } from 'aegir/utils/chai.js' +import { createNode, createPeerId } from '../utils/creators/peer.js' +import { mockConnection, mockDuplex, mockMultiaddrConnection, mockUpgrader } from '@libp2p/interface-compliance-tests/mocks' +import { createBaseOptions } from '../utils/base-options.browser.js' +import type { Libp2p } from '../../src/index.js' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import { DefaultConnectionManager } from '../../src/connection-manager/index.js' +import { Components } from '@libp2p/interfaces/components' +import { CustomEvent } from '@libp2p/interfaces' +import * as STATUS from '@libp2p/interfaces/connection/status' +import { stubInterface } from 'ts-sinon' +import type { KeyBook, PeerStore } from '@libp2p/interfaces/peer-store' +import sinon from 'sinon' +import pWaitFor from 'p-wait-for' +import type { Connection } from '@libp2p/interfaces/connection' +import delay from 'delay' +import type { Libp2pNode } from '../../src/libp2p.js' +import { codes } from '../../src/errors.js' describe('Connection Manager', () => { - let libp2p - let peerIds + let libp2p: Libp2p + let peerIds: PeerId[] before(async () => { - peerIds = await peerUtils.createPeerId({ number: 2 }) + peerIds = await Promise.all([ + createPeerId(), + createPeerId() + ]) }) beforeEach(async () => { - [libp2p] = await peerUtils.createPeer({ - config: { + libp2p = await createNode({ + config: createBaseOptions({ peerId: peerIds[0], addresses: { listen: ['/ip4/127.0.0.1/tcp/0/ws'] - }, - modules: baseOptions.modules - } + } + }) }) }) - afterEach(() => libp2p.stop()) + afterEach(async () => { + await libp2p.stop() + }) it('should filter connections on disconnect, removing the closed one', async () => { - const conn1 = await mockConnection({ localPeer: peerIds[0], remotePeer: peerIds[1] }) - const conn2 = await mockConnection({ localPeer: peerIds[0], remotePeer: peerIds[1] }) + const upgrader = mockUpgrader() + const peerStore = stubInterface() + peerStore.keyBook = stubInterface() + + const connectionManager = new DefaultConnectionManager(new Components({ upgrader, peerStore })) + + await connectionManager.start() - const id = peerIds[1].toB58String() + const conn1 = await mockConnection(mockMultiaddrConnection(mockDuplex(), peerIds[1])) + const conn2 = await mockConnection(mockMultiaddrConnection(mockDuplex(), peerIds[1])) + + expect(connectionManager.getConnections(peerIds[1])).to.have.lengthOf(0) // Add connection to the connectionManager - libp2p.connectionManager.onConnect(conn1) - libp2p.connectionManager.onConnect(conn2) + upgrader.dispatchEvent(new CustomEvent('connection', { detail: conn1 })) + upgrader.dispatchEvent(new CustomEvent('connection', { detail: conn2 })) + + expect(connectionManager.getConnections(peerIds[1])).to.have.lengthOf(2) - expect(libp2p.connectionManager.connections.get(id).length).to.eql(2) + await conn2.close() + upgrader.dispatchEvent(new CustomEvent('connectionEnd', { detail: conn2 })) - conn2._stat.status = 'closed' - libp2p.connectionManager.onDisconnect(conn2) + expect(connectionManager.getConnections(peerIds[1])).to.have.lengthOf(1) - const peerConnections = libp2p.connectionManager.connections.get(id) - expect(peerConnections.length).to.eql(1) - expect(peerConnections[0]._stat.status).to.eql('open') + expect(conn1).to.have.nested.property('stat.status', STATUS.OPEN) + + await connectionManager.stop() }) - it('should add connection on dial and remove on node stop', async () => { - const [remoteLibp2p] = await peerUtils.createPeer({ - config: { - peerId: peerIds[1], - addresses: { - listen: ['/ip4/127.0.0.1/tcp/15003/ws'] - }, - modules: baseOptions.modules - } - }) + it('should close connections on stop', async () => { + const upgrader = mockUpgrader() + const peerStore = stubInterface() + peerStore.keyBook = stubInterface() + + const connectionManager = new DefaultConnectionManager(new Components({ upgrader, peerStore })) - // Spy on emit for easy verification - sinon.spy(libp2p.connectionManager, 'emit') - sinon.spy(remoteLibp2p.connectionManager, 'emit') + await connectionManager.start() - await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) - await libp2p.dial(remoteLibp2p.peerId) + const conn1 = await mockConnection(mockMultiaddrConnection(mockDuplex(), peerIds[1])) + const conn2 = await mockConnection(mockMultiaddrConnection(mockDuplex(), peerIds[1])) - // check connect event - expect(libp2p.connectionManager.emit.callCount).to.equal(1) - const [event, connection] = libp2p.connectionManager.emit.getCall(0).args - expect(event).to.equal('peer:connect') - expect(connection.remotePeer.equals(remoteLibp2p.peerId)).to.equal(true) + // Add connection to the connectionManager + upgrader.dispatchEvent(new CustomEvent('connection', { detail: conn1 })) + upgrader.dispatchEvent(new CustomEvent('connection', { detail: conn2 })) - const libp2pConn = libp2p.connectionManager.get(remoteLibp2p.peerId) - expect(libp2pConn).to.exist() + expect(connectionManager.getConnections(peerIds[1])).to.have.lengthOf(2) - const remoteConn = remoteLibp2p.connectionManager.get(libp2p.peerId) - expect(remoteConn).to.exist() + await connectionManager.stop() - await remoteLibp2p.stop() - expect(remoteLibp2p.connectionManager.size).to.eql(0) + expect(connectionManager.getConnections(peerIds[1])).to.have.lengthOf(0) }) }) describe('libp2p.connections', () => { - let peerIds + let peerIds: PeerId[] + let libp2p: Libp2p before(async () => { - peerIds = await peerUtils.createPeerId({ number: 2 }) + peerIds = await Promise.all([ + createPeerId(), + createPeerId() + ]) + }) + + afterEach(async () => { + if (libp2p != null) { + await libp2p.stop() + } }) it('libp2p.connections gets the connectionManager conns', async () => { - const [libp2p] = await peerUtils.createPeer({ - config: { + libp2p = await createNode({ + config: createBaseOptions({ peerId: peerIds[0], addresses: { listen: ['/ip4/127.0.0.1/tcp/15003/ws'] - }, - modules: baseOptions.modules - } + } + }) }) - const [remoteLibp2p] = await peerUtils.createPeer({ - config: { + const remoteLibp2p = await createNode({ + config: createBaseOptions({ peerId: peerIds[1], addresses: { listen: ['/ip4/127.0.0.1/tcp/15004/ws'] - }, - modules: baseOptions.modules - } + } + }) }) - await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) - await libp2p.dial(remoteLibp2p.peerId) + await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.getMultiaddrs()) + const conn = await libp2p.dial(remoteLibp2p.peerId) - expect(libp2p.connections.size).to.eql(1) + expect(conn).to.be.ok() + expect(libp2p.getConnections()).to.have.lengthOf(1) await libp2p.stop() await remoteLibp2p.stop() }) describe('proactive connections', () => { - let nodes = [] + let libp2p: Libp2pNode + let nodes: Libp2p[] = [] beforeEach(async () => { - nodes = await peerUtils.createPeer({ - number: 2, - config: { - addresses: { - listen: ['/ip4/127.0.0.1/tcp/0/ws'] + nodes = await Promise.all([ + createNode({ + config: { + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0/ws'] + } } - } - }) + }), + createNode({ + config: { + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0/ws'] + } + } + }) + ]) }) afterEach(async () => { await Promise.all(nodes.map((node) => node.stop())) + + if (libp2p != null) { + await libp2p.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, + libp2p = await createNode({ started: false, config: { addresses: { @@ -160,76 +191,77 @@ describe('libp2p.connections', () => { }) // Populate PeerStore before starting - await libp2p.peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs) - await libp2p.peerStore.addressBook.set(nodes[1].peerId, nodes[1].multiaddrs) + await libp2p.peerStore.addressBook.set(nodes[0].peerId, nodes[0].getMultiaddrs()) + await libp2p.peerStore.addressBook.set(nodes[1].peerId, nodes[1].getMultiaddrs()) await libp2p.start() // Wait for peers to connect - await pWaitFor(() => libp2p.connectionManager.size === 2) + await pWaitFor(() => libp2p.getConnections().length === 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, + libp2p = await createNode({ started: false, config: { addresses: { listen: ['/ip4/127.0.0.1/tcp/0/ws'] }, connectionManager: { - minConnections + minConnections, + maxConnections: 1 } } }) // Populate PeerStore before starting - await libp2p.peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs) - await libp2p.peerStore.addressBook.set(nodes[1].peerId, nodes[1].multiaddrs) + await libp2p.peerStore.addressBook.set(nodes[0].peerId, nodes[0].getMultiaddrs()) + await libp2p.peerStore.addressBook.set(nodes[1].peerId, nodes[1].getMultiaddrs()) await libp2p.start() // Wait for peer to connect - await pWaitFor(() => libp2p.connectionManager.size === minConnections) + await pWaitFor(() => libp2p.components.getConnectionManager().getConnectionMap().size === minConnections) // Wait more time to guarantee no other connection happened await delay(200) - expect(libp2p.connectionManager.size).to.eql(minConnections) + expect(libp2p.components.getConnectionManager().getConnectionMap().size).to.eql(minConnections) await libp2p.stop() }) - it('should connect to all the peers stored in the PeerStore until reaching the minConnections sorted', async () => { + // flaky + it.skip('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, + libp2p = await createNode({ started: false, config: { addresses: { listen: ['/ip4/127.0.0.1/tcp/0/ws'] }, connectionManager: { - minConnections + minConnections, + maxConnections: 1 } } }) // Populate PeerStore before starting - await libp2p.peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs) - await libp2p.peerStore.addressBook.set(nodes[1].peerId, nodes[1].multiaddrs) + await libp2p.peerStore.addressBook.set(nodes[0].peerId, nodes[0].getMultiaddrs()) + await libp2p.peerStore.addressBook.set(nodes[1].peerId, nodes[1].getMultiaddrs()) await libp2p.peerStore.protoBook.set(nodes[1].peerId, ['/protocol-min-conns']) await libp2p.start() // Wait for peer to connect - await pWaitFor(() => libp2p.connectionManager.size === minConnections) + await pWaitFor(() => libp2p.components.getConnectionManager().getConnectionMap().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() + expect(libp2p.components.getConnectionManager().getConnection(nodes[0].peerId)).to.not.exist() + expect(libp2p.components.getConnectionManager().getConnection(nodes[1].peerId)).to.exist() await libp2p.stop() }) @@ -238,8 +270,7 @@ describe('libp2p.connections', () => { const minConnections = 1 const autoDialInterval = 1000 - const [libp2p] = await peerUtils.createPeer({ - fixture: false, + libp2p = await createNode({ config: { addresses: { listen: ['/ip4/127.0.0.1/tcp/0/ws'] @@ -252,96 +283,97 @@ describe('libp2p.connections', () => { }) // Populate PeerStore after starting (discovery) - await libp2p.peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs) + await libp2p.peerStore.addressBook.set(nodes[0].peerId, nodes[0].getMultiaddrs()) // Wait for peer to connect const conn = await libp2p.dial(nodes[0].peerId) - expect(libp2p.connectionManager.get(nodes[0].peerId)).to.exist() + expect(libp2p.components.getConnectionManager().getConnection(nodes[0].peerId)).to.exist() await conn.close() // Closed - await pWaitFor(() => libp2p.connectionManager.size === 0) + await pWaitFor(() => libp2p.components.getConnectionManager().getConnectionMap().size === 0) // Connected - await pWaitFor(() => libp2p.connectionManager.size === 1) + await pWaitFor(() => libp2p.components.getConnectionManager().getConnectionMap().size === 1) - expect(libp2p.connectionManager.get(nodes[0].peerId)).to.exist() + expect(libp2p.components.getConnectionManager().getConnection(nodes[0].peerId)).to.exist() await libp2p.stop() }) it('should be closed status once immediately stopping', async () => { - const [libp2p] = await peerUtils.createPeer({ - config: { + libp2p = await createNode({ + config: createBaseOptions({ peerId: peerIds[0], addresses: { listen: ['/ip4/127.0.0.1/tcp/15003/ws'] - }, - modules: baseOptions.modules - } + } + }) }) - const [remoteLibp2p] = await peerUtils.createPeer({ - config: { + const remoteLibp2p = await createNode({ + config: createBaseOptions({ peerId: peerIds[1], addresses: { listen: ['/ip4/127.0.0.1/tcp/15004/ws'] - }, - modules: baseOptions.modules - } + } + }) }) - await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) + await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.getMultiaddrs()) await libp2p.dial(remoteLibp2p.peerId) - const totalConns = Array.from(libp2p.connections.values()) + const totalConns = Array.from(libp2p.components.getConnectionManager().getConnectionMap().values()) expect(totalConns.length).to.eql(1) const conns = totalConns[0] expect(conns.length).to.eql(1) const conn = conns[0] await libp2p.stop() - expect(conn.stat.status).to.eql(CLOSED) + expect(conn.stat.status).to.eql(STATUS.CLOSED) await remoteLibp2p.stop() }) }) describe('connection gater', () => { - let libp2p - let remoteLibp2p + let libp2p: Libp2pNode + let remoteLibp2p: Libp2pNode beforeEach(async () => { - [remoteLibp2p] = await peerUtils.createPeer({ - config: { + remoteLibp2p = await createNode({ + config: createBaseOptions({ peerId: peerIds[1], addresses: { listen: ['/ip4/127.0.0.1/tcp/0/ws'] - }, - modules: baseOptions.modules - } + } + }) }) }) afterEach(async () => { - remoteLibp2p && await remoteLibp2p.stop() - libp2p && await libp2p.stop() + if (remoteLibp2p != null) { + await remoteLibp2p.stop() + } + + if (libp2p != null) { + await libp2p.stop() + } }) it('intercept peer dial', async () => { const denyDialPeer = sinon.stub().returns(true) - ;[libp2p] = await peerUtils.createPeer({ - config: { + libp2p = await createNode({ + config: createBaseOptions({ peerId: peerIds[0], addresses: { listen: ['/ip4/127.0.0.1/tcp/0/ws'] }, - modules: baseOptions.modules, connectionGater: { denyDialPeer } - } + }) }) - await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) + await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.getMultiaddrs()) await expect(libp2p.dial(remoteLibp2p.peerId)) .to.eventually.be.rejected().with.property('code', codes.ERR_PEER_DIAL_INTERCEPTED) @@ -350,48 +382,43 @@ describe('libp2p.connections', () => { it('intercept addr dial', async () => { const denyDialMultiaddr = sinon.stub().returns(false) - ;[libp2p] = await peerUtils.createPeer({ - config: { + libp2p = await createNode({ + config: createBaseOptions({ peerId: peerIds[0], addresses: { listen: ['/ip4/127.0.0.1/tcp/0/ws'] }, - modules: baseOptions.modules, connectionGater: { denyDialMultiaddr } - } + }) }) - await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) - await libp2p.dialer.connectToPeer(remoteLibp2p.peerId) + await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.getMultiaddrs()) + await libp2p.components.getDialer().dial(remoteLibp2p.peerId) - const peerIdMultiaddr = new Multiaddr(`/p2p/${remoteLibp2p.peerId}`) - - for (const multiaddr of remoteLibp2p.multiaddrs) { - expect(denyDialMultiaddr.calledWith(remoteLibp2p.peerId, multiaddr.encapsulate(peerIdMultiaddr))).to.be.true() + for (const multiaddr of remoteLibp2p.getMultiaddrs()) { + expect(denyDialMultiaddr.calledWith(remoteLibp2p.peerId, multiaddr)).to.be.true() } }) it('intercept multiaddr store during multiaddr dial', async () => { const filterMultiaddrForPeer = sinon.stub().returns(true) - ;[libp2p] = await peerUtils.createPeer({ - config: { + libp2p = await createNode({ + config: createBaseOptions({ peerId: peerIds[0], addresses: { listen: ['/ip4/127.0.0.1/tcp/0/ws'] }, - modules: baseOptions.modules, connectionGater: { filterMultiaddrForPeer } - } + }) }) - const peerIdMultiaddr = new Multiaddr(`/p2p/${remoteLibp2p.peerId}`) - const fullMultiaddr = remoteLibp2p.multiaddrs[0].encapsulate(peerIdMultiaddr) + const fullMultiaddr = remoteLibp2p.getMultiaddrs()[0] - await libp2p.dialer.connectToPeer(fullMultiaddr) + await libp2p.components.getDialer().dial(fullMultiaddr) expect(filterMultiaddrForPeer.callCount).to.equal(2) @@ -403,19 +430,18 @@ describe('libp2p.connections', () => { it('intercept accept inbound connection', async () => { const denyInboundConnection = sinon.stub().returns(false) - ;[libp2p] = await peerUtils.createPeer({ - config: { + libp2p = await createNode({ + config: createBaseOptions({ peerId: peerIds[0], addresses: { listen: ['/ip4/127.0.0.1/tcp/0/ws'] }, - modules: baseOptions.modules, connectionGater: { denyInboundConnection } - } + }) }) - await remoteLibp2p.peerStore.addressBook.set(libp2p.peerId, libp2p.multiaddrs) + await remoteLibp2p.peerStore.addressBook.set(libp2p.peerId, libp2p.getMultiaddrs()) await remoteLibp2p.dial(libp2p.peerId) expect(denyInboundConnection.called).to.be.true() @@ -424,19 +450,18 @@ describe('libp2p.connections', () => { it('intercept accept outbound connection', async () => { const denyOutboundConnection = sinon.stub().returns(false) - ;[libp2p] = await peerUtils.createPeer({ - config: { + libp2p = await createNode({ + config: createBaseOptions({ peerId: peerIds[0], addresses: { listen: ['/ip4/127.0.0.1/tcp/0/ws'] }, - modules: baseOptions.modules, connectionGater: { denyOutboundConnection } - } + }) }) - await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) + await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.getMultiaddrs()) await libp2p.dial(remoteLibp2p.peerId) expect(denyOutboundConnection.called).to.be.true() @@ -445,89 +470,85 @@ describe('libp2p.connections', () => { it('intercept inbound encrypted', async () => { const denyInboundEncryptedConnection = sinon.stub().returns(false) - ;[libp2p] = await peerUtils.createPeer({ - config: { + libp2p = await createNode({ + config: createBaseOptions({ peerId: peerIds[0], addresses: { listen: ['/ip4/127.0.0.1/tcp/0/ws'] }, - modules: baseOptions.modules, connectionGater: { denyInboundEncryptedConnection } - } + }) }) - await remoteLibp2p.peerStore.addressBook.set(libp2p.peerId, libp2p.multiaddrs) + await remoteLibp2p.peerStore.addressBook.set(libp2p.peerId, libp2p.getMultiaddrs()) await remoteLibp2p.dial(libp2p.peerId) expect(denyInboundEncryptedConnection.called).to.be.true() - expect(denyInboundEncryptedConnection.getCall(0)).to.have.nested.property('args[0].id').that.equalBytes(remoteLibp2p.peerId.id) + expect(denyInboundEncryptedConnection.getCall(0)).to.have.nested.property('args[0].multihash.digest').that.equalBytes(remoteLibp2p.peerId.multihash.digest) }) it('intercept outbound encrypted', async () => { const denyOutboundEncryptedConnection = sinon.stub().returns(false) - ;[libp2p] = await peerUtils.createPeer({ - config: { + libp2p = await createNode({ + config: createBaseOptions({ peerId: peerIds[0], addresses: { listen: ['/ip4/127.0.0.1/tcp/0/ws'] }, - modules: baseOptions.modules, connectionGater: { denyOutboundEncryptedConnection } - } + }) }) - await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) + await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.getMultiaddrs()) await libp2p.dial(remoteLibp2p.peerId) expect(denyOutboundEncryptedConnection.called).to.be.true() - expect(denyOutboundEncryptedConnection.getCall(0)).to.have.nested.property('args[0].id').that.equalBytes(remoteLibp2p.peerId.id) + expect(denyOutboundEncryptedConnection.getCall(0)).to.have.nested.property('args[0].multihash.digest').that.equalBytes(remoteLibp2p.peerId.multihash.digest) }) it('intercept inbound upgraded', async () => { const denyInboundUpgradedConnection = sinon.stub().returns(false) - ;[libp2p] = await peerUtils.createPeer({ - config: { + libp2p = await createNode({ + config: createBaseOptions({ peerId: peerIds[0], addresses: { listen: ['/ip4/127.0.0.1/tcp/0/ws'] }, - modules: baseOptions.modules, connectionGater: { denyInboundUpgradedConnection } - } + }) }) - await remoteLibp2p.peerStore.addressBook.set(libp2p.peerId, libp2p.multiaddrs) + await remoteLibp2p.peerStore.addressBook.set(libp2p.peerId, libp2p.getMultiaddrs()) await remoteLibp2p.dial(libp2p.peerId) expect(denyInboundUpgradedConnection.called).to.be.true() - expect(denyInboundUpgradedConnection.getCall(0)).to.have.nested.property('args[0].id').that.equalBytes(remoteLibp2p.peerId.id) + expect(denyInboundUpgradedConnection.getCall(0)).to.have.nested.property('args[0].multihash.digest').that.equalBytes(remoteLibp2p.peerId.multihash.digest) }) it('intercept outbound upgraded', async () => { const denyOutboundUpgradedConnection = sinon.stub().returns(false) - ;[libp2p] = await peerUtils.createPeer({ - config: { + libp2p = await createNode({ + config: createBaseOptions({ peerId: peerIds[0], addresses: { listen: ['/ip4/127.0.0.1/tcp/0/ws'] }, - modules: baseOptions.modules, connectionGater: { denyOutboundUpgradedConnection } - } + }) }) - await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) + await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.getMultiaddrs()) await libp2p.dial(remoteLibp2p.peerId) expect(denyOutboundUpgradedConnection.called).to.be.true() - expect(denyOutboundUpgradedConnection.getCall(0)).to.have.nested.property('args[0].id').that.equalBytes(remoteLibp2p.peerId.id) + expect(denyOutboundUpgradedConnection.getCall(0)).to.have.nested.property('args[0].multihash.digest').that.equalBytes(remoteLibp2p.peerId.multihash.digest) }) }) }) diff --git a/test/connection-manager/index.spec.js b/test/connection-manager/index.spec.js deleted file mode 100644 index d400f54b28..0000000000 --- a/test/connection-manager/index.spec.js +++ /dev/null @@ -1,132 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') - -const peerUtils = require('../utils/creators/peer') -const mockConnection = require('../utils/mockConnection') -const baseOptions = require('../utils/base-options.browser') - -describe('Connection Manager', () => { - let libp2p - - afterEach(async () => { - sinon.restore() - libp2p && await libp2p.stop() - }) - - it('should be able to create without metrics', async () => { - [libp2p] = await peerUtils.createPeer({ - config: { - modules: baseOptions.modules - }, - started: false - }) - - const spy = sinon.spy(libp2p.connectionManager, 'start') - - await libp2p.start() - expect(spy).to.have.property('callCount', 1) - expect(libp2p.connectionManager._metrics).to.not.exist() - }) - - it('should be able to create with metrics', async () => { - [libp2p] = await peerUtils.createPeer({ - config: { - modules: baseOptions.modules, - metrics: { - enabled: true - } - }, - started: false - }) - - const spy = sinon.spy(libp2p.connectionManager, 'start') - - await libp2p.start() - expect(spy).to.have.property('callCount', 1) - expect(libp2p.connectionManager._libp2p.metrics).to.exist() - }) - - it('should close lowest value peer connection when the maximum has been reached', async () => { - const max = 5 - ;[libp2p] = await peerUtils.createPeer({ - config: { - modules: baseOptions.modules, - connectionManager: { - maxConnections: max, - minConnections: 2 - } - }, - started: false - }) - - await libp2p.start() - - sinon.spy(libp2p.connectionManager, '_maybeDisconnectOne') - - // Add 1 too many connections - const spies = new Map() - await Promise.all([...new Array(max + 1)].map(async (_, index) => { - const connection = await mockConnection() - const spy = sinon.spy(connection, 'close') - // The connections have the same remote id, give them random ones - // so that we can verify the correct connection was closed - sinon.stub(connection.remotePeer, 'toB58String').returns(index) - const value = Math.random() - spies.set(value, spy) - libp2p.connectionManager.setPeerValue(connection.remotePeer, value) - await libp2p.connectionManager.onConnect(connection) - })) - - // get the lowest value - const lowest = Array.from(spies.keys()).sort()[0] - const lowestSpy = spies.get(lowest) - - expect(libp2p.connectionManager._maybeDisconnectOne).to.have.property('callCount', 1) - expect(lowestSpy).to.have.property('callCount', 1) - }) - - it('should close connection when the maximum has been reached even without peer values', async () => { - const max = 5 - ;[libp2p] = await peerUtils.createPeer({ - config: { - modules: baseOptions.modules, - connectionManager: { - maxConnections: max, - minConnections: 0 - } - }, - started: false - }) - - await libp2p.start() - - sinon.spy(libp2p.connectionManager, '_maybeDisconnectOne') - - // Add 1 too many connections - const spy = sinon.spy() - await Promise.all([...new Array(max + 1)].map(async () => { - const connection = await mockConnection() - sinon.stub(connection, 'close').callsFake(async () => spy()) // eslint-disable-line - await libp2p.connectionManager.onConnect(connection) - })) - - expect(libp2p.connectionManager._maybeDisconnectOne).to.have.property('callCount', 1) - expect(spy).to.have.property('callCount', 1) - }) - - it('should fail if the connection manager has mismatched connection limit options', async () => { - await expect(peerUtils.createPeer({ - config: { - modules: baseOptions.modules, - connectionManager: { - maxConnections: 5, - minConnections: 6 - } - }, - started: false - })).to.eventually.rejected('maxConnections must be greater') - }) -}) diff --git a/test/connection-manager/index.spec.ts b/test/connection-manager/index.spec.ts new file mode 100644 index 0000000000..6f6dba9198 --- /dev/null +++ b/test/connection-manager/index.spec.ts @@ -0,0 +1,143 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import sinon from 'sinon' +import { createNode } from '../utils/creators/peer.js' +import { createBaseOptions } from '../utils/base-options.browser.js' +import type { Libp2pNode } from '../../src/libp2p.js' +import type { DefaultConnectionManager } from '../../src/connection-manager/index.js' +import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-compliance-tests/mocks' +import { createEd25519PeerId } from '@libp2p/peer-id-factory' +import { CustomEvent } from '@libp2p/interfaces' + +describe('Connection Manager', () => { + let libp2p: Libp2pNode + + afterEach(async () => { + sinon.restore() + + if (libp2p != null) { + await libp2p.stop() + } + }) + + it('should be able to create without metrics', async () => { + libp2p = await createNode({ + config: createBaseOptions(), + started: false + }) + + const spy = sinon.spy(libp2p.components.getConnectionManager() as DefaultConnectionManager, 'start') + + await libp2p.start() + expect(spy).to.have.property('callCount', 1) + expect(libp2p.components.getMetrics()).to.not.exist() + }) + + it('should be able to create with metrics', async () => { + libp2p = await createNode({ + config: createBaseOptions({ + metrics: { + enabled: true + } + }), + started: false + }) + + const spy = sinon.spy(libp2p.components.getConnectionManager() as DefaultConnectionManager, 'start') + + await libp2p.start() + expect(spy).to.have.property('callCount', 1) + expect(libp2p.components.getMetrics()).to.exist() + }) + + it('should close lowest value peer connection when the maximum has been reached', async () => { + const max = 5 + libp2p = await createNode({ + config: createBaseOptions({ + connectionManager: { + maxConnections: max, + minConnections: 2 + } + }), + started: false + }) + + await libp2p.start() + + const connectionManager = libp2p.components.getConnectionManager() as DefaultConnectionManager + const connectionManagerMaybeDisconnectOneSpy = sinon.spy(connectionManager, '_maybeDisconnectOne') + + // Add 1 too many connections + const spies = new Map>>() + await Promise.all([...new Array(max + 1)].map(async (_, index) => { + const connection = mockConnection(mockMultiaddrConnection(mockDuplex(), await createEd25519PeerId())) + const spy = sinon.spy(connection, 'close') + // The connections have the same remote id, give them random ones + // so that we can verify the correct connection was closed + // sinon.stub(connection.remotePeer, 'toString').returns(index) + const value = Math.random() + spies.set(value, spy) + connectionManager.setPeerValue(connection.remotePeer, value) + await connectionManager.onConnect(new CustomEvent('connection', { detail: connection })) + })) + + // get the lowest value + const lowest = Array.from(spies.keys()).sort((a, b) => { + if (a > b) { + return 1 + } + + if (a < b) { + return -1 + } + + return 0 + })[0] + const lowestSpy = spies.get(lowest) + + expect(connectionManagerMaybeDisconnectOneSpy.callCount).to.equal(1) + expect(lowestSpy).to.have.property('callCount', 1) + }) + + it('should close connection when the maximum has been reached even without peer values', async () => { + const max = 5 + libp2p = await createNode({ + config: createBaseOptions({ + connectionManager: { + maxConnections: max, + minConnections: 0 + } + }), + started: false + }) + + await libp2p.start() + + const connectionManager = libp2p.components.getConnectionManager() as DefaultConnectionManager + const connectionManagerMaybeDisconnectOneSpy = sinon.spy(connectionManager, '_maybeDisconnectOne') + + // Add 1 too many connections + const spy = sinon.spy() + await Promise.all([...new Array(max + 1)].map(async () => { + const connection = mockConnection(mockMultiaddrConnection(mockDuplex(), await createEd25519PeerId())) + sinon.stub(connection, 'close').callsFake(async () => spy()) // eslint-disable-line + await connectionManager.onConnect(new CustomEvent('connection', { detail: connection })) + })) + + expect(connectionManagerMaybeDisconnectOneSpy.callCount).to.equal(1) + expect(spy).to.have.property('callCount', 1) + }) + + it('should fail if the connection manager has mismatched connection limit options', async () => { + await expect(createNode({ + config: createBaseOptions({ + connectionManager: { + maxConnections: 5, + minConnections: 6 + } + }), + started: false + })).to.eventually.rejected('maxConnections must be greater') + }) +}) diff --git a/test/content-routing/content-routing.node.js b/test/content-routing/content-routing.node.ts similarity index 56% rename from test/content-routing/content-routing.node.js rename to test/content-routing/content-routing.node.ts index 694d71cf61..25f429dc22 100644 --- a/test/content-routing/content-routing.node.js +++ b/test/content-routing/content-routing.node.ts @@ -1,30 +1,29 @@ -'use strict' /* eslint-env mocha */ -const { expect } = require('aegir/utils/chai') -const nock = require('nock') -const sinon = require('sinon') - -const pDefer = require('p-defer') -const mergeOptions = require('merge-options') - -const { CID } = require('multiformats/cid') -const ipfsHttpClient = require('ipfs-http-client') -const DelegatedContentRouter = require('libp2p-delegated-content-routing') -const { Multiaddr } = require('multiaddr') -const drain = require('it-drain') -const all = require('it-all') - -const peerUtils = require('../utils/creators/peer') -const { baseOptions, routingOptions } = require('./utils') +import { expect } from 'aegir/utils/chai.js' +import nock from 'nock' +import sinon from 'sinon' +import pDefer from 'p-defer' +import { CID } from 'multiformats/cid' +import { create as createIpfsHttpClient } from 'ipfs-http-client' +import { DelegatedContentRouting } from '@libp2p/delegated-content-routing' +import { Multiaddr } from '@multiformats/multiaddr' +import drain from 'it-drain' +import all from 'it-all' +import { createNode, createPeerId, populateAddressBooks } from '../utils/creators/peer.js' +import { createBaseOptions } from '../utils/base-options.js' +import { createRoutingOptions } from './utils.js' +import type { Libp2p } from '../../src/index.js' +import type { PeerInfo } from '@libp2p/interfaces/peer-info' +import type { Libp2pNode } from '../../src/libp2p.js' describe('content-routing', () => { describe('no routers', () => { - let node + let node: Libp2p before(async () => { - [node] = await peerUtils.createPeer({ - config: baseOptions + node = await createNode({ + config: createBaseOptions() }) }) @@ -32,15 +31,17 @@ describe('content-routing', () => { it('.findProviders should return an error', async () => { try { + // @ts-expect-error invalid params for await (const _ of node.contentRouting.findProviders('a cid')) {} // eslint-disable-line throw new Error('.findProviders should return an error') - } catch (/** @type {any} */ err) { + } catch (err: any) { expect(err).to.exist() expect(err.code).to.equal('ERR_NO_ROUTERS_AVAILABLE') } }) it('.provide should return an error', async () => { + // @ts-expect-error invalid params await expect(node.contentRouting.provide('a cid')) .to.eventually.be.rejected() .and.to.have.property('code', 'ERR_NO_ROUTERS_AVAILABLE') @@ -49,17 +50,21 @@ describe('content-routing', () => { describe('via dht router', () => { const number = 5 - let nodes + let nodes: Libp2pNode[] before(async () => { - nodes = await peerUtils.createPeer({ - number, - config: routingOptions - }) + nodes = await Promise.all([ + createNode({ config: createRoutingOptions() }), + createNode({ config: createRoutingOptions() }), + createNode({ config: createRoutingOptions() }), + createNode({ config: createRoutingOptions() }), + createNode({ config: createRoutingOptions() }) + ]) + await populateAddressBooks(nodes) // Ring dial await Promise.all( - nodes.map((peer, i) => peer.dial(nodes[(i + 1) % number].peerId)) + nodes.map(async (peer, i) => await peer.dial(nodes[(i + 1) % number].peerId)) ) }) @@ -67,103 +72,107 @@ describe('content-routing', () => { sinon.restore() }) - after(() => Promise.all(nodes.map((n) => n.stop()))) + after(async () => await Promise.all(nodes.map(async (n) => await n.stop()))) - it('should use the nodes dht to provide', () => { + it('should use the nodes dht to provide', async () => { const deferred = pDefer() - sinon.stub(nodes[0]._dht, 'provide').callsFake(() => { + if (nodes[0].dht == null) { + throw new Error('DHT was not configured') + } + + sinon.stub(nodes[0].dht, 'provide').callsFake(async function * () { // eslint-disable-line require-yield deferred.resolve() }) - nodes[0].contentRouting.provide() - return deferred.promise + void nodes[0].contentRouting.provide(CID.parse('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB')) + + return await deferred.promise }) it('should use the nodes dht to find providers', async () => { const deferred = pDefer() - const [providerPeerId] = await peerUtils.createPeerId({ fixture: false }) - sinon.stub(nodes[0]._dht, 'findProviders').callsFake(function * () { - deferred.resolve() + if (nodes[0].dht == null) { + throw new Error('DHT was not configured') + } + + sinon.stub(nodes[0].dht, 'findProviders').callsFake(async function * () { yield { + from: nodes[0].peerId, + type: 0, name: 'PROVIDER', providers: [{ - id: providerPeerId, - multiaddrs: [] + id: nodes[0].peerId, + multiaddrs: [], + protocols: [] }] } + deferred.resolve() }) - await nodes[0].contentRouting.findProviders().next() + await drain(nodes[0].contentRouting.findProviders(CID.parse('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB'))) - return deferred.promise + return await deferred.promise }) }) describe('via delegate router', () => { - let node - let delegate + let node: Libp2pNode + let delegate: DelegatedContentRouting beforeEach(async () => { - const [peerId] = await peerUtils.createPeerId({ fixture: true }) - - delegate = new DelegatedContentRouter(peerId, ipfsHttpClient.create({ + delegate = new DelegatedContentRouting(createIpfsHttpClient({ host: '0.0.0.0', protocol: 'http', port: 60197 })) - ;[node] = await peerUtils.createPeer({ - config: mergeOptions(baseOptions, { - modules: { - contentRouting: [delegate] - }, - config: { - dht: { - enabled: false - } - } + node = await createNode({ + config: createBaseOptions({ + contentRouters: [ + delegate + ], + dht: undefined }) }) }) - afterEach(() => { - sinon.restore() - }) - - afterEach(() => node.stop()) + afterEach(async () => { + if (node != null) { + await node.stop() + } - it('should only have one router', () => { - expect(node.contentRouting.routers).to.have.lengthOf(1) + sinon.restore() }) - it('should use the delegate router to provide', () => { + it('should use the delegate router to provide', async () => { const deferred = pDefer() - sinon.stub(delegate, 'provide').callsFake(() => { + sinon.stub(delegate, 'provide').callsFake(async () => { deferred.resolve() }) - node.contentRouting.provide() - return deferred.promise + void node.contentRouting.provide(CID.parse('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB')) + + return await deferred.promise }) it('should use the delegate router to find providers', async () => { const deferred = pDefer() - const [providerPeerId] = await peerUtils.createPeerId({ fixture: false }) - sinon.stub(delegate, 'findProviders').callsFake(function * () { - deferred.resolve() + sinon.stub(delegate, 'findProviders').callsFake(async function * () { yield { - id: providerPeerId, - multiaddrs: [] + id: node.peerId, + multiaddrs: [], + protocols: [] } + deferred.resolve() }) - await node.contentRouting.findProviders().next() + await drain(node.contentRouting.findProviders(CID.parse('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB'))) - return deferred.promise + return await deferred.promise }) it('should be able to register as a provider', async () => { @@ -219,13 +228,10 @@ describe('content-routing', () => { 'X-Chunked-Output', '1' ]) - const providers = [] - for await (const provider of node.contentRouting.findProviders(cid, { timeout: 1000 })) { - providers.push(provider) - } + const providers = await all(node.contentRouting.findProviders(cid)) expect(providers).to.have.length(1) - expect(providers[0].id.toB58String()).to.equal(provider) + expect(providers[0].id.toString()).to.equal(provider) expect(mockApi.isDone()).to.equal(true) }) @@ -241,7 +247,7 @@ describe('content-routing', () => { try { for await (const _ of node.contentRouting.findProviders(cid)) { } // eslint-disable-line throw new Error('should handle errors when finding providers') - } catch (/** @type {any} */ err) { + } catch (err: any) { expect(err).to.exist() } @@ -250,23 +256,19 @@ describe('content-routing', () => { }) describe('via dht and delegate routers', () => { - let node - let delegate + let node: Libp2pNode + let delegate: DelegatedContentRouting beforeEach(async () => { - const [peerId] = await peerUtils.createPeerId({ fixture: true }) - - delegate = new DelegatedContentRouter(peerId, ipfsHttpClient.create({ + delegate = new DelegatedContentRouting(createIpfsHttpClient({ host: '0.0.0.0', protocol: 'http', port: 60197 })) - ;[node] = await peerUtils.createPeer({ - config: mergeOptions(routingOptions, { - modules: { - contentRouting: [delegate] - } + node = await createNode({ + config: createRoutingOptions({ + contentRouters: [delegate] }) }) }) @@ -275,25 +277,30 @@ describe('content-routing', () => { sinon.restore() }) - afterEach(() => node.stop()) + afterEach(async () => await node.stop()) it('should store the multiaddrs of a peer', async () => { - const [providerPeerId] = await peerUtils.createPeerId({ fixture: false }) - const result = { + const providerPeerId = await createPeerId() + const result: PeerInfo = { id: providerPeerId, multiaddrs: [ new Multiaddr('/ip4/123.123.123.123/tcp/49320') - ] + ], + protocols: [] + } + + if (node.dht == null) { + throw new Error('DHT was not configured') } - sinon.stub(node._dht, 'findProviders').callsFake(function * () {}) - sinon.stub(delegate, 'findProviders').callsFake(function * () { + sinon.stub(node.dht, 'findProviders').callsFake(async function * () {}) + sinon.stub(delegate, 'findProviders').callsFake(async function * () { yield result }) expect(await node.peerStore.has(providerPeerId)).to.not.be.ok() - await drain(node.contentRouting.findProviders('a cid')) + await drain(node.contentRouting.findProviders(CID.parse('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB'))) expect(await node.peerStore.addressBook.get(providerPeerId)).to.deep.include({ isCertified: false, @@ -302,17 +309,22 @@ describe('content-routing', () => { }) it('should not wait for routing findProviders to finish before returning results', async () => { - const [providerPeerId] = await peerUtils.createPeerId({ fixture: false }) + const providerPeerId = await createPeerId() const result = { id: providerPeerId, multiaddrs: [ new Multiaddr('/ip4/123.123.123.123/tcp/49320') - ] + ], + protocols: [] + } + + if (node.dht == null) { + throw new Error('DHT was not configured') } const defer = pDefer() - sinon.stub(node._dht, 'findProviders').callsFake(async function * () { // eslint-disable-line require-yield + sinon.stub(node.dht, 'findProviders').callsFake(async function * () { // eslint-disable-line require-yield await defer.promise }) sinon.stub(delegate, 'findProviders').callsFake(async function * () { @@ -321,50 +333,70 @@ describe('content-routing', () => { await defer.promise }) - for await (const provider of node.contentRouting.findProviders('a cid')) { + for await (const provider of node.contentRouting.findProviders(CID.parse('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB'))) { expect(provider.id).to.deep.equal(providerPeerId) defer.resolve() } }) it('should dedupe results', async () => { - const [providerPeerId] = await peerUtils.createPeerId({ fixture: false }) + const providerPeerId = await createPeerId() const result = { id: providerPeerId, multiaddrs: [ new Multiaddr('/ip4/123.123.123.123/tcp/49320') - ] + ], + protocols: [] } - sinon.stub(node._dht, 'findProviders').callsFake(async function * () { - yield result + if (node.dht == null) { + throw new Error('DHT was not configured') + } + + sinon.stub(node.dht, 'findProviders').callsFake(async function * () { + yield { + from: providerPeerId, + type: 0, + name: 'PROVIDER', + providers: [ + result + ] + } }) sinon.stub(delegate, 'findProviders').callsFake(async function * () { yield result }) - const results = await all(node.contentRouting.findProviders('a cid')) + const results = await all(node.contentRouting.findProviders(CID.parse('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB'))) expect(results).to.be.an('array').with.lengthOf(1).that.deep.equals([result]) }) it('should combine multiaddrs when different addresses are returned by different content routers', async () => { - const [providerPeerId] = await peerUtils.createPeerId({ fixture: false }) + const providerPeerId = await createPeerId() const result1 = { id: providerPeerId, multiaddrs: [ new Multiaddr('/ip4/123.123.123.123/tcp/49320') - ] + ], + protocols: [] } const result2 = { id: providerPeerId, multiaddrs: [ new Multiaddr('/ip4/213.213.213.213/tcp/2344') - ] + ], + protocols: [] } - sinon.stub(node._dht, 'findProviders').callsFake(async function * () { + if (node.dht == null) { + throw new Error('DHT was not configured') + } + + sinon.stub(node.dht, 'findProviders').callsFake(async function * () { yield { + from: providerPeerId, + type: 0, name: 'PROVIDER', providers: [ result1 @@ -375,7 +407,7 @@ describe('content-routing', () => { yield result2 }) - await drain(node.contentRouting.findProviders('a cid')) + await drain(node.contentRouting.findProviders(CID.parse('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB'))) expect(await node.peerStore.addressBook.get(providerPeerId)).to.deep.include({ isCertified: false, @@ -390,16 +422,19 @@ describe('content-routing', () => { const dhtDeferred = pDefer() const delegatedDeferred = pDefer() - sinon.stub(node._dht, 'provide').callsFake(async function * () { - yield + if (node.dht == null) { + throw new Error('DHT was not configured') + } + + sinon.stub(node.dht, 'provide').callsFake(async function * () { // eslint-disable-line require-yield dhtDeferred.resolve() }) - sinon.stub(delegate, 'provide').callsFake(() => { + sinon.stub(delegate, 'provide').callsFake(async function () { delegatedDeferred.resolve() }) - await node.contentRouting.provide() + await node.contentRouting.provide(CID.parse('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB')) await Promise.all([ dhtDeferred.promise, @@ -408,14 +443,21 @@ describe('content-routing', () => { }) it('should use the dht if the delegate fails to find providers', async () => { - const [providerPeerId] = await peerUtils.createPeerId({ fixture: false }) + const providerPeerId = await createPeerId() const results = [{ id: providerPeerId, - multiaddrs: [] + multiaddrs: [], + protocols: [] }] - sinon.stub(node._dht, 'findProviders').callsFake(function * () { + if (node.dht == null) { + throw new Error('DHT was not configured') + } + + sinon.stub(node.dht, 'findProviders').callsFake(async function * () { yield { + from: providerPeerId, + type: 0, name: 'PROVIDER', providers: [ results[0] @@ -423,11 +465,11 @@ describe('content-routing', () => { } }) - sinon.stub(delegate, 'findProviders').callsFake(function * () { // eslint-disable-line require-yield + sinon.stub(delegate, 'findProviders').callsFake(async function * () { // eslint-disable-line require-yield }) const providers = [] - for await (const prov of node.contentRouting.findProviders('a cid')) { + for await (const prov of node.contentRouting.findProviders(CID.parse('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB'))) { providers.push(prov) } @@ -436,20 +478,25 @@ describe('content-routing', () => { }) it('should use the delegate if the dht fails to find providers', async () => { - const [providerPeerId] = await peerUtils.createPeerId({ fixture: false }) + const providerPeerId = await createPeerId() const results = [{ id: providerPeerId, - multiaddrs: [] + multiaddrs: [], + protocols: [] }] - sinon.stub(node._dht, 'findProviders').callsFake(function * () {}) + if (node.dht == null) { + throw new Error('DHT was not configured') + } + + sinon.stub(node.dht, 'findProviders').callsFake(async function * () {}) - sinon.stub(delegate, 'findProviders').callsFake(function * () { + sinon.stub(delegate, 'findProviders').callsFake(async function * () { yield results[0] }) const providers = [] - for await (const prov of node.contentRouting.findProviders('a cid')) { + for await (const prov of node.contentRouting.findProviders(CID.parse('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB'))) { providers.push(prov) } diff --git a/test/content-routing/dht/configuration.node.js b/test/content-routing/dht/configuration.node.js deleted file mode 100644 index ed22d3e9c6..0000000000 --- a/test/content-routing/dht/configuration.node.js +++ /dev/null @@ -1,94 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const mergeOptions = require('merge-options') - -const { create } = require('../../../src') -const { baseOptions, subsystemOptions } = require('./utils') -const peerUtils = require('../../utils/creators/peer') - -const listenAddr = '/ip4/127.0.0.1/tcp/0' - -describe('DHT subsystem is configurable', () => { - let libp2p - - afterEach(async () => { - libp2p && await libp2p.stop() - }) - - it('should not exist if no module is provided', async () => { - libp2p = await create(baseOptions) - expect(libp2p._dht).to.not.exist() - }) - - it('should exist if the module is provided', async () => { - libp2p = await create(subsystemOptions) - expect(libp2p._dht).to.exist() - }) - - it('should start and stop by default once libp2p starts', async () => { - const [peerId] = await peerUtils.createPeerId(1) - - const customOptions = mergeOptions(subsystemOptions, { - peerId, - addresses: { - listen: [listenAddr] - } - }) - - libp2p = await create(customOptions) - expect(libp2p._dht.isStarted()).to.equal(false) - - await libp2p.start() - expect(libp2p._dht.isStarted()).to.equal(true) - - await libp2p.stop() - expect(libp2p._dht.isStarted()).to.equal(false) - }) - - it('should not start if disabled once libp2p starts', async () => { - const [peerId] = await peerUtils.createPeerId(1) - - const customOptions = mergeOptions(subsystemOptions, { - peerId, - addresses: { - listen: [listenAddr] - }, - config: { - dht: { - enabled: false - } - } - }) - - libp2p = await create(customOptions) - expect(libp2p._dht.isStarted()).to.equal(false) - - await libp2p.start() - expect(libp2p._dht.isStarted()).to.equal(false) - }) - - it('should allow a manual start', async () => { - const [peerId] = await peerUtils.createPeerId(1) - - const customOptions = mergeOptions(subsystemOptions, { - peerId, - addresses: { - listen: [listenAddr] - }, - config: { - dht: { - enabled: false - } - } - }) - - libp2p = await create(customOptions) - await libp2p.start() - expect(libp2p._dht.isStarted()).to.equal(false) - - await libp2p._dht.start() - expect(libp2p._dht.isStarted()).to.equal(true) - }) -}) diff --git a/test/content-routing/dht/configuration.node.ts b/test/content-routing/dht/configuration.node.ts new file mode 100644 index 0000000000..57b58c90d4 --- /dev/null +++ b/test/content-routing/dht/configuration.node.ts @@ -0,0 +1,27 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import { createLibp2p, Libp2p } from '../../../src/index.js' +import { createSubsystemOptions } from './utils.js' + +describe('DHT subsystem is configurable', () => { + let libp2p: Libp2p + + afterEach(async () => { + if (libp2p != null) { + await libp2p.stop() + } + }) + + it('should not exist if no module is provided', async () => { + libp2p = await createLibp2p(createSubsystemOptions({ + dht: undefined + })) + expect(libp2p.dht).to.not.exist() + }) + + it('should exist if the module is provided', async () => { + libp2p = await createLibp2p(createSubsystemOptions()) + expect(libp2p.dht).to.exist() + }) +}) diff --git a/test/content-routing/dht/operation.node.js b/test/content-routing/dht/operation.node.js deleted file mode 100644 index e6662b2084..0000000000 --- a/test/content-routing/dht/operation.node.js +++ /dev/null @@ -1,146 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') - -const { Multiaddr } = require('multiaddr') -const pWaitFor = require('p-wait-for') -const mergeOptions = require('merge-options') -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') - -const { create } = require('../../../src') -const { subsystemOptions, subsystemMulticodecs } = require('./utils') -const peerUtils = require('../../utils/creators/peer') - -const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/8000') -const remoteListenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/8001') - -describe('DHT subsystem operates correctly', () => { - let peerId, remotePeerId - let libp2p, remoteLibp2p - let remAddr - - beforeEach(async () => { - [peerId, remotePeerId] = await peerUtils.createPeerId({ number: 2 }) - }) - - describe('dht started before connect', () => { - beforeEach(async () => { - libp2p = await create(mergeOptions(subsystemOptions, { - peerId, - addresses: { - listen: [listenAddr] - } - })) - - remoteLibp2p = await create(mergeOptions(subsystemOptions, { - peerId: remotePeerId, - addresses: { - listen: [remoteListenAddr] - } - })) - - await Promise.all([ - libp2p.start(), - remoteLibp2p.start() - ]) - - await libp2p.peerStore.addressBook.set(remotePeerId, [remoteListenAddr]); - [remAddr] = await libp2p.peerStore.addressBook.getMultiaddrsForPeer(remotePeerId) - }) - - afterEach(() => Promise.all([ - libp2p && libp2p.stop(), - remoteLibp2p && remoteLibp2p.stop() - ])) - - it('should get notified of connected peers on dial', async () => { - const connection = await libp2p.dialProtocol(remAddr, subsystemMulticodecs) - - expect(connection).to.exist() - - return Promise.all([ - pWaitFor(() => libp2p._dht._lan._routingTable.size === 1), - pWaitFor(() => remoteLibp2p._dht._lan._routingTable.size === 1) - ]) - }) - - it('should put on a peer and get from the other', async () => { - const key = uint8ArrayFromString('hello') - const value = uint8ArrayFromString('world') - - await libp2p.dialProtocol(remAddr, subsystemMulticodecs) - await Promise.all([ - pWaitFor(() => libp2p._dht._lan._routingTable.size === 1), - pWaitFor(() => remoteLibp2p._dht._lan._routingTable.size === 1) - ]) - - await libp2p.contentRouting.put(key, value) - - const fetchedValue = await remoteLibp2p.contentRouting.get(key) - expect(fetchedValue).to.have.property('val').that.equalBytes(value) - }) - }) - - describe('dht started after connect', () => { - beforeEach(async () => { - libp2p = await create(mergeOptions(subsystemOptions, { - peerId, - addresses: { - listen: [listenAddr] - } - })) - - remoteLibp2p = await create(mergeOptions(subsystemOptions, { - peerId: remotePeerId, - addresses: { - listen: [remoteListenAddr] - }, - config: { - dht: { - enabled: false - } - } - })) - - await libp2p.start() - await remoteLibp2p.start() - - await libp2p.peerStore.addressBook.set(remotePeerId, [remoteListenAddr]) - remAddr = (await libp2p.peerStore.addressBook.getMultiaddrsForPeer(remotePeerId))[0] - }) - - afterEach(() => Promise.all([ - libp2p && libp2p.stop(), - remoteLibp2p && remoteLibp2p.stop() - ])) - - it('should get notified of connected peers after starting', async () => { - const connection = await libp2p.dial(remAddr) - - expect(connection).to.exist() - expect(libp2p._dht._lan._routingTable.size).to.be.eql(0) - - await remoteLibp2p._dht.start() - // should be 0 directly after start - TODO this may be susceptible to timing bugs, we should have - // the ability to report stats on the DHT routing table instead of reaching into it's heart like this - expect(remoteLibp2p._dht._lan._routingTable.size).to.be.eql(0) - return pWaitFor(() => libp2p._dht._lan._routingTable.size === 1) - }) - - it('should put on a peer and get from the other', async () => { - await libp2p.dial(remAddr) - - const key = uint8ArrayFromString('hello') - const value = uint8ArrayFromString('world') - - await remoteLibp2p._dht.start() - await pWaitFor(() => libp2p._dht._lan._routingTable.size === 1) - - await libp2p.contentRouting.put(key, value) - - const fetchedValue = await remoteLibp2p.contentRouting.get(key) - expect(fetchedValue).to.have.property('val').that.equalBytes(value) - }) - }) -}) diff --git a/test/content-routing/dht/operation.node.ts b/test/content-routing/dht/operation.node.ts new file mode 100644 index 0000000000..e3fa534b95 --- /dev/null +++ b/test/content-routing/dht/operation.node.ts @@ -0,0 +1,178 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import { Multiaddr } from '@multiformats/multiaddr' +import pWaitFor from 'p-wait-for' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { subsystemMulticodecs, createSubsystemOptions } from './utils.js' +import { createPeerId } from '../../utils/creators/peer.js' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import { createLibp2pNode, Libp2pNode } from '../../../src/libp2p.js' +import { isStartable } from '@libp2p/interfaces' + +const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/8000') +const remoteListenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/8001') + +async function getRemoteAddr (remotePeerId: PeerId, libp2p: Libp2pNode) { + const addrs = await libp2p.components.getPeerStore().addressBook.get(remotePeerId) + + if (addrs.length === 0) { + throw new Error('No addrs found') + } + + const addr = addrs[0] + + return addr.multiaddr.encapsulate(`/p2p/${remotePeerId.toString()}`) +} + +describe('DHT subsystem operates correctly', () => { + let peerId: PeerId, remotePeerId: PeerId + let libp2p: Libp2pNode, remoteLibp2p: Libp2pNode + let remAddr: Multiaddr + + beforeEach(async () => { + [peerId, remotePeerId] = await Promise.all([ + createPeerId(), + createPeerId() + ]) + }) + + describe('dht started before connect', () => { + beforeEach(async () => { + libp2p = await createLibp2pNode(createSubsystemOptions({ + peerId, + addresses: { + listen: [listenAddr.toString()] + } + })) + + remoteLibp2p = await createLibp2pNode(createSubsystemOptions({ + peerId: remotePeerId, + addresses: { + listen: [remoteListenAddr.toString()] + } + })) + + await Promise.all([ + libp2p.start(), + remoteLibp2p.start() + ]) + + await libp2p.components.getPeerStore().addressBook.set(remotePeerId, [remoteListenAddr]) + remAddr = await getRemoteAddr(remotePeerId, libp2p) + }) + + afterEach(async () => { + if (libp2p != null) { + await libp2p.stop() + } + + if (remoteLibp2p != null) { + await remoteLibp2p.stop() + } + }) + + it('should get notified of connected peers on dial', async () => { + const connection = await libp2p.dialProtocol(remAddr, subsystemMulticodecs) + + expect(connection).to.exist() + + return await Promise.all([ + pWaitFor(() => libp2p.dht?.lan.routingTable.size === 1), + pWaitFor(() => remoteLibp2p.dht?.lan.routingTable.size === 1) + ]) + }) + + it('should put on a peer and get from the other', async () => { + const key = uint8ArrayFromString('hello') + const value = uint8ArrayFromString('world') + + await libp2p.dialProtocol(remAddr, subsystemMulticodecs) + await Promise.all([ + pWaitFor(() => libp2p.dht?.lan.routingTable.size === 1), + pWaitFor(() => remoteLibp2p.dht?.lan.routingTable.size === 1) + ]) + + await libp2p.components.getContentRouting().put(key, value) + + const fetchedValue = await remoteLibp2p.components.getContentRouting().get(key) + expect(fetchedValue).to.equalBytes(value) + }) + }) + + describe('dht started after connect', () => { + beforeEach(async () => { + libp2p = await createLibp2pNode(createSubsystemOptions({ + peerId, + addresses: { + listen: [listenAddr.toString()] + } + })) + + remoteLibp2p = await createLibp2pNode(createSubsystemOptions({ + peerId: remotePeerId, + addresses: { + listen: [remoteListenAddr.toString()] + } + })) + + await libp2p.start() + await remoteLibp2p.start() + + await libp2p.components.getPeerStore().addressBook.set(remotePeerId, [remoteListenAddr]) + remAddr = await getRemoteAddr(remotePeerId, libp2p) + }) + + afterEach(async () => { + if (libp2p != null) { + await libp2p.stop() + } + + if (remoteLibp2p != null) { + await remoteLibp2p.stop() + } + }) + + // TODO: we pre-fill the routing tables on dht startup with artificial peers so this test + // doesn't really work as intended. We should be testing that a connected peer can change + // it's supported protocols and we should notice that change so there may be something to + // salvage from here, though it could be better as identify protocol tests. + it.skip('should get notified of connected peers after starting', async () => { + const connection = await libp2p.dial(remAddr) + + expect(connection).to.exist() + expect(libp2p.dht?.lan.routingTable).to.be.empty() + + const dht = remoteLibp2p.dht + + if (isStartable(dht)) { + await dht.start() + } + + // should be 0 directly after start - TODO this may be susceptible to timing bugs, we should have + // the ability to report stats on the DHT routing table instead of reaching into it's heart like this + expect(remoteLibp2p.dht?.lan.routingTable).to.be.empty() + + return await pWaitFor(() => libp2p.dht?.lan.routingTable.size === 1) + }) + + it('should put on a peer and get from the other', async () => { + await libp2p.dial(remAddr) + + const key = uint8ArrayFromString('hello') + const value = uint8ArrayFromString('world') + + const dht = remoteLibp2p.dht + + if (isStartable(dht)) { + await dht.start() + } + + await pWaitFor(() => libp2p.dht?.lan.routingTable.size === 1) + await libp2p.components.getContentRouting().put(key, value) + + const fetchedValue = await remoteLibp2p.components.getContentRouting().get(key) + expect(fetchedValue).to.equalBytes(value) + }) + }) +}) diff --git a/test/content-routing/dht/utils.js b/test/content-routing/dht/utils.js deleted file mode 100644 index 0a37de41ae..0000000000 --- a/test/content-routing/dht/utils.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict' - -const KadDht = require('libp2p-kad-dht') -const Crypto = require('../../../src/insecure/plaintext') -const Muxer = require('libp2p-mplex') -const Transport = require('libp2p-tcp') - -const mergeOptions = require('merge-options') - -const baseOptions = { - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - } -} - -module.exports.baseOptions = baseOptions - -const subsystemOptions = mergeOptions(baseOptions, { - modules: { - dht: KadDht - }, - config: { - dht: { - kBucketSize: 20, - enabled: true - } - } -}) - -module.exports.subsystemOptions = subsystemOptions -module.exports.subsystemMulticodecs = [ - '/ipfs/lan/kad/1.0.0' -] diff --git a/test/content-routing/dht/utils.ts b/test/content-routing/dht/utils.ts new file mode 100644 index 0000000000..e3a9106161 --- /dev/null +++ b/test/content-routing/dht/utils.ts @@ -0,0 +1,15 @@ +import { KadDHT } from '@libp2p/kad-dht' +import type { Libp2pOptions } from '../../../src/index.js' +import { createBaseOptions } from '../../utils/base-options.js' + +export function createSubsystemOptions (...overrides: Libp2pOptions[]) { + return createBaseOptions({ + dht: new KadDHT({ + kBucketSize: 20 + }) + }, ...overrides) +} + +export const subsystemMulticodecs = [ + '/ipfs/lan/kad/1.0.0' +] diff --git a/test/content-routing/utils.js b/test/content-routing/utils.js deleted file mode 100644 index 7b43d05046..0000000000 --- a/test/content-routing/utils.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict' - -const KadDht = require('libp2p-kad-dht') -const mergeOptions = require('merge-options') -const baseOptions = require('../utils/base-options') - -module.exports.baseOptions = baseOptions - -const routingOptions = mergeOptions(baseOptions, { - modules: { - dht: KadDht - }, - config: { - dht: { - kBucketSize: 20, - enabled: true - } - } -}) - -module.exports.routingOptions = routingOptions diff --git a/test/content-routing/utils.ts b/test/content-routing/utils.ts new file mode 100644 index 0000000000..25ae8b23be --- /dev/null +++ b/test/content-routing/utils.ts @@ -0,0 +1,11 @@ +import { KadDHT } from '@libp2p/kad-dht' +import type { Libp2pOptions } from '../../src/index.js' +import { createBaseOptions } from '../utils/base-options.js' + +export function createRoutingOptions (...overrides: Libp2pOptions[]): Libp2pOptions { + return createBaseOptions({ + dht: new KadDHT({ + kBucketSize: 20 + }) + }, ...overrides) +} diff --git a/test/core/consume-peer-record.spec.js b/test/core/consume-peer-record.spec.js deleted file mode 100644 index 609a6c29e0..0000000000 --- a/test/core/consume-peer-record.spec.js +++ /dev/null @@ -1,46 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const Transport = require('libp2p-websockets') -const { NOISE: Crypto } = require('@chainsafe/libp2p-noise') - -const Libp2p = require('../../src') -const { createPeerId } = require('../utils/creators/peer') - -describe('Consume peer record', () => { - let libp2p - - beforeEach(async () => { - const [peerId] = await createPeerId() - const config = { - peerId, - modules: { - transport: [Transport], - connEncryption: [Crypto] - } - } - libp2p = await Libp2p.create(config) - }) - - afterEach(async () => { - await libp2p.stop() - }) - - it('should consume peer record when observed addrs are added', async () => { - let done - - libp2p.peerStore.addressBook.consumePeerRecord = () => { - done() - } - - const p = new Promise(resolve => { - done = resolve - }) - - libp2p.addressManager.addObservedAddr('/ip4/123.123.123.123/tcp/3983') - - await p - - libp2p.stop() - }) -}) diff --git a/test/core/consume-peer-record.spec.ts b/test/core/consume-peer-record.spec.ts new file mode 100644 index 0000000000..70dad598dd --- /dev/null +++ b/test/core/consume-peer-record.spec.ts @@ -0,0 +1,51 @@ +/* eslint-env mocha */ + +import { WebSockets } from '@libp2p/websockets' +import { NOISE } from '@chainsafe/libp2p-noise' +import { createPeerId } from '../utils/creators/peer.js' +import { Multiaddr } from '@multiformats/multiaddr' +import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' +import type { Libp2pOptions } from '../../src/index.js' + +describe('Consume peer record', () => { + let libp2p: Libp2pNode + + beforeEach(async () => { + const peerId = await createPeerId() + const config: Libp2pOptions = { + peerId, + transports: [ + new WebSockets() + ], + connectionEncryption: [ + NOISE + ] + } + libp2p = await createLibp2pNode(config) + }) + + afterEach(async () => { + await libp2p.stop() + }) + + it('should consume peer record when observed addrs are added', async () => { + let done: () => void + + libp2p.components.getPeerStore().addressBook.consumePeerRecord = async () => { + done() + return true + } + + const p = new Promise(resolve => { + done = resolve + }) + + await libp2p.start() + + libp2p.components.getAddressManager().addObservedAddr(new Multiaddr('/ip4/123.123.123.123/tcp/3983')) + + await p + + await libp2p.stop() + }) +}) diff --git a/test/core/encryption.spec.js b/test/core/encryption.spec.js deleted file mode 100644 index da5c6e11ff..0000000000 --- a/test/core/encryption.spec.js +++ /dev/null @@ -1,54 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') - -const Transport = require('libp2p-websockets') -const { NOISE: Crypto } = require('@chainsafe/libp2p-noise') - -const Libp2p = require('../../src') -const { codes: ErrorCodes } = require('../../src/errors') -const { createPeerId } = require('../utils/creators/peer') - -describe('Connection encryption configuration', () => { - let peerId - - before(async () => { - [peerId] = await createPeerId() - }) - - it('is required', async () => { - const config = { - peerId, - modules: { - transport: [Transport] - } - } - - await expect(Libp2p.create(config)).to.eventually.be.rejected() - .and.to.have.property('code', ErrorCodes.CONN_ENCRYPTION_REQUIRED) - }) - - it('is required and needs at least one module', async () => { - const config = { - peerId, - modules: { - transport: [Transport], - connEncryption: [] - } - } - await expect(Libp2p.create(config)).to.eventually.be.rejected() - .and.to.have.property('code', ErrorCodes.CONN_ENCRYPTION_REQUIRED) - }) - - it('can be created', async () => { - const config = { - peerId, - modules: { - transport: [Transport], - connEncryption: [Crypto] - } - } - await Libp2p.create(config) - }) -}) diff --git a/test/core/encryption.spec.ts b/test/core/encryption.spec.ts new file mode 100644 index 0000000000..6d2ac81703 --- /dev/null +++ b/test/core/encryption.spec.ts @@ -0,0 +1,54 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import { WebSockets } from '@libp2p/websockets' +import { NOISE } from '@chainsafe/libp2p-noise' +import { createLibp2p, Libp2pOptions } from '../../src/index.js' +import { codes as ErrorCodes } from '../../src/errors.js' +import { createPeerId } from '../utils/creators/peer.js' +import type { PeerId } from '@libp2p/interfaces/peer-id' + +describe('Connection encryption configuration', () => { + let peerId: PeerId + + before(async () => { + peerId = await createPeerId() + }) + + it('is required', async () => { + const config = { + peerId, + transports: [ + new WebSockets() + ] + } + + await expect(createLibp2p(config)).to.eventually.be.rejected() + .and.to.have.property('code', ErrorCodes.CONN_ENCRYPTION_REQUIRED) + }) + + it('is required and needs at least one module', async () => { + const config = { + peerId, + transports: [ + new WebSockets() + ], + connectionEncryption: [] + } + await expect(createLibp2p(config)).to.eventually.be.rejected() + .and.to.have.property('code', ErrorCodes.CONN_ENCRYPTION_REQUIRED) + }) + + it('can be created', async () => { + const config: Libp2pOptions = { + peerId, + transports: [ + new WebSockets() + ], + connectionEncryption: [ + NOISE + ] + } + await createLibp2p(config) + }) +}) diff --git a/test/core/listening.node.js b/test/core/listening.node.ts similarity index 57% rename from test/core/listening.node.js rename to test/core/listening.node.ts index c83c6d8951..04f81c9105 100644 --- a/test/core/listening.node.js +++ b/test/core/listening.node.ts @@ -1,22 +1,20 @@ -'use strict' /* eslint-env mocha */ -const { expect } = require('aegir/utils/chai') - -const Transport = require('libp2p-tcp') -const { NOISE: Crypto } = require('@chainsafe/libp2p-noise') - -const { create } = require('../../src') -const peerUtils = require('../utils/creators/peer') +import { expect } from 'aegir/utils/chai.js' +import { TCP } from '@libp2p/tcp' +import { NOISE } from '@chainsafe/libp2p-noise' +import { createPeerId } from '../utils/creators/peer.js' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' const listenAddr = '/ip4/0.0.0.0/tcp/0' describe('Listening', () => { - let peerId - let libp2p + let peerId: PeerId + let libp2p: Libp2pNode before(async () => { - [peerId] = await peerUtils.createPeerId() + peerId = await createPeerId() }) after(async () => { @@ -24,20 +22,22 @@ describe('Listening', () => { }) it('should replace wildcard host and port with actual host and port on startup', async () => { - libp2p = await create({ + libp2p = await createLibp2pNode({ peerId, addresses: { listen: [listenAddr] }, - modules: { - transport: [Transport], - connEncryption: [Crypto] - } + transports: [ + new TCP() + ], + connectionEncryption: [ + NOISE + ] }) await libp2p.start() - const addrs = libp2p.transportManager.getAddrs() + const addrs = libp2p.components.getTransportManager().getAddrs() // Should get something like: // /ip4/127.0.0.1/tcp/50866 diff --git a/test/core/ping.node.js b/test/core/ping.node.js deleted file mode 100644 index 63ebec0160..0000000000 --- a/test/core/ping.node.js +++ /dev/null @@ -1,84 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') - -const pTimes = require('p-times') -const pipe = require('it-pipe') - -const peerUtils = require('../utils/creators/peer') -const baseOptions = require('../utils/base-options') -const { PROTOCOL } = require('../../src/ping/constants') - -describe('ping', () => { - let nodes - - beforeEach(async () => { - nodes = await peerUtils.createPeer({ - number: 3, - config: baseOptions - }) - - await nodes[0].peerStore.addressBook.set(nodes[1].peerId, nodes[1].multiaddrs) - await nodes[1].peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs) - }) - - afterEach(() => Promise.all(nodes.map(n => n.stop()))) - - 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') - }) - - it('ping several times for getting an average', async () => { - const latencies = await pTimes(5, () => nodes[1].ping(nodes[0].peerId)) - - const averageLatency = latencies.reduce((p, c) => p + c, 0) / latencies.length - expect(averageLatency).to.be.a('Number') - }) - - it('only waits for the first response to arrive', async () => { - nodes[1].handle(PROTOCOL, async ({ connection, stream }) => { - let firstInvocation = true - - await pipe( - stream, - function (stream) { - const output = { - [Symbol.asyncIterator]: () => output, - next: async () => { - if (firstInvocation) { - firstInvocation = false - - // eslint-disable-next-line no-unreachable-loop - for await (const data of stream) { - return { - value: data, - done: false - } - } - } else { - return new Promise() // never resolve - } - } - } - - return output - }, - stream - ) - }) - - const latency = await nodes[0].ping(nodes[1].peerId) - - expect(latency).to.be.a('Number') - }) -}) diff --git a/test/core/ping.node.ts b/test/core/ping.node.ts new file mode 100644 index 0000000000..45d5d5195e --- /dev/null +++ b/test/core/ping.node.ts @@ -0,0 +1,75 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import pTimes from 'p-times' +import { pipe } from 'it-pipe' +import { createNode, populateAddressBooks } from '../utils/creators/peer.js' +import { createBaseOptions } from '../utils/base-options.js' +import { PROTOCOL } from '../../src/ping/constants.js' +import { Multiaddr } from '@multiformats/multiaddr' +import pDefer from 'p-defer' +import type { Libp2pNode } from '../../src/libp2p.js' + +describe('ping', () => { + let nodes: Libp2pNode[] + + beforeEach(async () => { + nodes = await Promise.all([ + createNode({ config: createBaseOptions() }), + createNode({ config: createBaseOptions() }), + createNode({ config: createBaseOptions() }) + ]) + await populateAddressBooks(nodes) + + await nodes[0].components.getPeerStore().addressBook.set(nodes[1].peerId, nodes[1].getMultiaddrs()) + await nodes[1].components.getPeerStore().addressBook.set(nodes[0].peerId, nodes[0].getMultiaddrs()) + }) + + afterEach(async () => await Promise.all(nodes.map(async n => await n.stop()))) + + it('ping once from peer0 to peer1 using a multiaddr', async () => { + const ma = new Multiaddr(`${nodes[2].getMultiaddrs()[0].toString()}/p2p/${nodes[2].peerId.toString()}`) + 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') + }) + + it('ping several times for getting an average', async () => { + const latencies = await pTimes(5, async () => await nodes[1].ping(nodes[0].peerId)) + + const averageLatency = latencies.reduce((p, c) => p + c, 0) / latencies.length + expect(averageLatency).to.be.a('Number') + }) + + it('only waits for the first response to arrive', async () => { + const defer = pDefer() + + await nodes[1].unhandle(PROTOCOL) + await nodes[1].handle(PROTOCOL, ({ stream }) => { + void pipe( + stream, + async function * (stream) { + for await (const data of stream) { + yield data + + // something longer than the test timeout + await defer.promise + } + }, + stream + ) + }) + + const latency = await nodes[0].ping(nodes[1].peerId) + + expect(latency).to.be.a('Number') + + defer.resolve() + }) +}) diff --git a/test/dialing/dial-request.spec.js b/test/dialing/dial-request.spec.js deleted file mode 100644 index 3ec2eba8aa..0000000000 --- a/test/dialing/dial-request.spec.js +++ /dev/null @@ -1,224 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') - -const { AbortError } = require('libp2p-interfaces/src/transport/errors') -const AggregateError = require('aggregate-error') -const pDefer = require('p-defer') -const delay = require('delay') - -const DialRequest = require('../../src/dialer/dial-request') -const createMockConnection = require('../utils/mockConnection') -const error = new Error('dial failes') - -describe('Dial Request', () => { - it('should end when a single multiaddr dials succeeds', async () => { - const mockConnection = await createMockConnection() - const actions = { - 1: () => Promise.reject(error), - 2: () => Promise.resolve(mockConnection), - 3: () => Promise.reject(error) - } - const dialAction = (num) => actions[num]() - const tokens = ['a', 'b'] - const controller = new AbortController() - const dialer = { - getTokens: () => [...tokens], - releaseToken: () => {} - } - - const dialRequest = new DialRequest({ - addrs: Object.keys(actions), - dialer, - dialAction - }) - - sinon.spy(actions, 1) - sinon.spy(actions, 2) - sinon.spy(actions, 3) - sinon.spy(dialer, 'releaseToken') - const result = await dialRequest.run({ signal: controller.signal }) - expect(result).to.equal(mockConnection) - expect(actions[1]).to.have.property('callCount', 1) - expect(actions[2]).to.have.property('callCount', 1) - expect(actions[3]).to.have.property('callCount', 0) - expect(dialer.releaseToken).to.have.property('callCount', tokens.length) - }) - - it('should release tokens when all addr dials have started', async () => { - const mockConnection = await createMockConnection() - const firstDials = pDefer() - const deferred = pDefer() - const actions = { - 1: () => firstDials.promise, - 2: () => firstDials.promise, - 3: () => deferred.promise - } - const dialAction = (num) => actions[num]() - const tokens = ['a', 'b'] - const controller = new AbortController() - const dialer = { - getTokens: () => [...tokens], - releaseToken: () => {} - } - - const dialRequest = new DialRequest({ - addrs: Object.keys(actions), - dialer, - dialAction - }) - - sinon.spy(actions, 1) - sinon.spy(actions, 2) - sinon.spy(actions, 3) - sinon.spy(dialer, 'releaseToken') - dialRequest.run({ signal: controller.signal }) - // Let the first dials run - await delay(0) - - // Finish the first 2 dials - firstDials.reject(error) - await delay(0) - - // Only 1 dial should remain, so 1 token should have been released - expect(actions[1]).to.have.property('callCount', 1) - expect(actions[2]).to.have.property('callCount', 1) - expect(actions[3]).to.have.property('callCount', 1) - expect(dialer.releaseToken).to.have.property('callCount', 1) - - // Finish the dial and release the 2nd token - deferred.resolve(mockConnection) - await delay(0) - expect(dialer.releaseToken).to.have.property('callCount', 2) - }) - - it('should throw an AggregateError if all dials fail', async () => { - const actions = { - 1: () => Promise.reject(error), - 2: () => Promise.reject(error), - 3: () => Promise.reject(error) - } - const dialAction = (num) => actions[num]() - const addrs = Object.keys(actions) - const tokens = ['a', 'b'] - const controller = new AbortController() - const dialer = { - getTokens: () => [...tokens], - releaseToken: () => {} - } - - const dialRequest = new DialRequest({ - addrs, - dialer, - dialAction - }) - - sinon.spy(actions, 1) - sinon.spy(actions, 2) - sinon.spy(actions, 3) - sinon.spy(dialer, 'getTokens') - sinon.spy(dialer, 'releaseToken') - - try { - await dialRequest.run({ signal: controller.signal }) - expect.fail('Should have thrown') - } catch (/** @type {any} */ err) { - expect(err).to.be.an.instanceof(AggregateError) - } - - expect(actions[1]).to.have.property('callCount', 1) - expect(actions[2]).to.have.property('callCount', 1) - expect(actions[3]).to.have.property('callCount', 1) - expect(dialer.getTokens.calledWith(addrs.length)).to.equal(true) - expect(dialer.releaseToken).to.have.property('callCount', tokens.length) - }) - - it('should handle a large number of addrs', async () => { - const reject = sinon.stub().callsFake(() => Promise.reject(error)) - const actions = {} - const addrs = [...new Array(25)].map((_, index) => index + 1) - addrs.forEach(addr => { - actions[addr] = reject - }) - - const dialAction = (addr) => actions[addr]() - const tokens = ['a', 'b'] - const controller = new AbortController() - const dialer = { - getTokens: () => [...tokens], - releaseToken: () => {} - } - - const dialRequest = new DialRequest({ - addrs, - dialer, - dialAction - }) - - sinon.spy(dialer, 'releaseToken') - try { - await dialRequest.run({ signal: controller.signal }) - expect.fail('Should have thrown') - } catch (/** @type {any} */ err) { - expect(err).to.be.an.instanceof(AggregateError) - } - - expect(reject).to.have.property('callCount', addrs.length) - expect(dialer.releaseToken).to.have.property('callCount', tokens.length) - }) - - it('should abort all dials when its signal is aborted', async () => { - const deferToAbort = ({ signal }) => { - if (signal.aborted) throw new Error('already aborted') - const deferred = pDefer() - const onAbort = () => { - deferred.reject(new AbortError()) - signal.removeEventListener('abort', onAbort) - } - signal.addEventListener('abort', onAbort) - return deferred.promise - } - - const actions = { - 1: deferToAbort, - 2: deferToAbort, - 3: deferToAbort - } - const dialAction = (num, opts) => actions[num](opts) - const addrs = Object.keys(actions) - const tokens = ['a', 'b'] - const controller = new AbortController() - const dialer = { - getTokens: () => [...tokens], - releaseToken: () => {} - } - - const dialRequest = new DialRequest({ - addrs, - dialer, - dialAction - }) - - sinon.spy(actions, 1) - sinon.spy(actions, 2) - sinon.spy(actions, 3) - sinon.spy(dialer, 'getTokens') - sinon.spy(dialer, 'releaseToken') - - try { - setTimeout(() => controller.abort(), 100) - await dialRequest.run({ signal: controller.signal }) - expect.fail('dial should have failed') - } catch (/** @type {any} */ err) { - expect(err).to.be.an.instanceof(AggregateError) - } - - expect(actions[1]).to.have.property('callCount', 1) - expect(actions[2]).to.have.property('callCount', 1) - expect(actions[3]).to.have.property('callCount', 1) - expect(dialer.getTokens.calledWith(addrs.length)).to.equal(true) - expect(dialer.releaseToken).to.have.property('callCount', tokens.length) - }) -}) diff --git a/test/dialing/dial-request.spec.ts b/test/dialing/dial-request.spec.ts new file mode 100644 index 0000000000..c41d86368a --- /dev/null +++ b/test/dialing/dial-request.spec.ts @@ -0,0 +1,218 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import sinon from 'sinon' +import { AbortError } from '@libp2p/interfaces/errors' +import pDefer from 'p-defer' +import delay from 'delay' +import { DialAction, DialRequest } from '../../src/dialer/dial-request.js' +import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-compliance-tests/mocks' +import { createEd25519PeerId } from '@libp2p/peer-id-factory' +import { Multiaddr } from '@multiformats/multiaddr' +import { DefaultDialer } from '../../src/dialer/index.js' +import { Components } from '@libp2p/interfaces/components' +const error = new Error('dial failure') + +describe('Dial Request', () => { + it('should end when a single multiaddr dials succeeds', async () => { + const connection = mockConnection(mockMultiaddrConnection(mockDuplex(), await createEd25519PeerId())) + const actions: Record Promise> = { + '/ip4/127.0.0.1/tcp/1231': async () => await Promise.reject(error), + '/ip4/127.0.0.1/tcp/1232': async () => await Promise.resolve(connection), + '/ip4/127.0.0.1/tcp/1233': async () => await Promise.reject(error) + } + const dialAction: DialAction = async (num) => await actions[num.toString()]() + const controller = new AbortController() + const dialer = new DefaultDialer(new Components(), { + maxParallelDials: 2 + }) + const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') + const dialRequest = new DialRequest({ + addrs: Object.keys(actions).map(str => new Multiaddr(str)), + dialer, + dialAction + }) + + sinon.spy(actions, '/ip4/127.0.0.1/tcp/1231') + sinon.spy(actions, '/ip4/127.0.0.1/tcp/1232') + sinon.spy(actions, '/ip4/127.0.0.1/tcp/1233') + + const result = await dialRequest.run({ signal: controller.signal }) + expect(result).to.equal(connection) + expect(actions['/ip4/127.0.0.1/tcp/1231']).to.have.property('callCount', 1) + expect(actions['/ip4/127.0.0.1/tcp/1232']).to.have.property('callCount', 1) + expect(actions['/ip4/127.0.0.1/tcp/1233']).to.have.property('callCount', 0) + expect(dialerReleaseTokenSpy.callCount).to.equal(2) + }) + + it('should release tokens when all addr dials have started', async () => { + const connection = mockConnection(mockMultiaddrConnection(mockDuplex(), await createEd25519PeerId())) + const firstDials = pDefer() + const deferred = pDefer() + const actions: Record Promise> = { + '/ip4/127.0.0.1/tcp/1231': async () => await firstDials.promise, + '/ip4/127.0.0.1/tcp/1232': async () => await firstDials.promise, + '/ip4/127.0.0.1/tcp/1233': async () => await deferred.promise + } + const dialAction: DialAction = async (num) => await actions[num.toString()]() + const controller = new AbortController() + const dialer = new DefaultDialer(new Components(), { + maxParallelDials: 2 + }) + const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') + const dialRequest = new DialRequest({ + addrs: Object.keys(actions).map(str => new Multiaddr(str)), + dialer, + dialAction + }) + + sinon.spy(actions, '/ip4/127.0.0.1/tcp/1231') + sinon.spy(actions, '/ip4/127.0.0.1/tcp/1232') + sinon.spy(actions, '/ip4/127.0.0.1/tcp/1233') + + void dialRequest.run({ signal: controller.signal }) + // Let the first dials run + await delay(0) + + // Finish the first 2 dials + firstDials.reject(error) + await delay(0) + + // Only 1 dial should remain, so 1 token should have been released + expect(actions['/ip4/127.0.0.1/tcp/1231']).to.have.property('callCount', 1) + expect(actions['/ip4/127.0.0.1/tcp/1232']).to.have.property('callCount', 1) + expect(actions['/ip4/127.0.0.1/tcp/1233']).to.have.property('callCount', 1) + expect(dialerReleaseTokenSpy.callCount).to.equal(1) + + // Finish the dial and release the 2nd token + deferred.resolve(connection) + await delay(0) + expect(dialerReleaseTokenSpy.callCount).to.equal(2) + }) + + it('should throw an AggregateError if all dials fail', async () => { + const actions: Record Promise> = { + '/ip4/127.0.0.1/tcp/1231': async () => await Promise.reject(error), + '/ip4/127.0.0.1/tcp/1232': async () => await Promise.reject(error), + '/ip4/127.0.0.1/tcp/1233': async () => await Promise.reject(error) + } + const dialAction: DialAction = async (num) => await actions[num.toString()]() + const addrs = Object.keys(actions) + const controller = new AbortController() + const dialer = new DefaultDialer(new Components(), { + maxParallelDials: 2 + }) + const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') + const dialerGetTokensSpy = sinon.spy(dialer, 'getTokens') + const dialRequest = new DialRequest({ + addrs: Object.keys(actions).map(str => new Multiaddr(str)), + dialer, + dialAction + }) + + sinon.spy(actions, '/ip4/127.0.0.1/tcp/1231') + sinon.spy(actions, '/ip4/127.0.0.1/tcp/1232') + sinon.spy(actions, '/ip4/127.0.0.1/tcp/1233') + + try { + await dialRequest.run({ signal: controller.signal }) + expect.fail('Should have thrown') + } catch (err: any) { + expect(err).to.have.property('name', 'AggregateError') + } + + expect(actions['/ip4/127.0.0.1/tcp/1231']).to.have.property('callCount', 1) + expect(actions['/ip4/127.0.0.1/tcp/1232']).to.have.property('callCount', 1) + expect(actions['/ip4/127.0.0.1/tcp/1233']).to.have.property('callCount', 1) + + expect(dialerGetTokensSpy.calledWith(addrs.length)).to.equal(true) + expect(dialerReleaseTokenSpy.callCount).to.equal(2) + }) + + it('should handle a large number of addrs', async () => { + const reject = sinon.stub().callsFake(async () => await Promise.reject(error)) + const actions: Record Promise> = {} + const addrs = [...new Array(25)].map((_, index) => `/ip4/127.0.0.1/tcp/12${index + 1}`) + addrs.forEach(addr => { + actions[addr] = reject + }) + + const dialAction: DialAction = async (num) => await actions[num.toString()]() + const controller = new AbortController() + const dialer = new DefaultDialer(new Components(), { + maxParallelDials: 2 + }) + const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') + const dialRequest = new DialRequest({ + addrs: Object.keys(actions).map(str => new Multiaddr(str)), + dialer, + dialAction + }) + + try { + await dialRequest.run({ signal: controller.signal }) + expect.fail('Should have thrown') + } catch (err: any) { + expect(err).to.have.property('name', 'AggregateError') + } + + expect(reject).to.have.property('callCount', addrs.length) + expect(dialerReleaseTokenSpy.callCount).to.equal(2) + }) + + it('should abort all dials when its signal is aborted', async () => { + const deferToAbort = async (args: { signal: AbortSignal }) => { + const { signal } = args + + if (signal.aborted) { + throw new Error('already aborted') + } + + const deferred = pDefer() + const onAbort = () => { + deferred.reject(new AbortError()) + signal.removeEventListener('abort', onAbort) + } + signal.addEventListener('abort', onAbort) + return await deferred.promise + } + + const actions: Record Promise> = { + '/ip4/127.0.0.1/tcp/1231': deferToAbort, + '/ip4/127.0.0.1/tcp/1232': deferToAbort, + '/ip4/127.0.0.1/tcp/1233': deferToAbort + } + const dialAction: DialAction = async (num) => await actions[num.toString()]() + const addrs = Object.keys(actions) + const controller = new AbortController() + const dialer = new DefaultDialer(new Components(), { + maxParallelDials: 2 + }) + const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') + const dialerGetTokensSpy = sinon.spy(dialer, 'getTokens') + const dialRequest = new DialRequest({ + addrs: Object.keys(actions).map(str => new Multiaddr(str)), + dialer, + dialAction + }) + + sinon.spy(actions, '/ip4/127.0.0.1/tcp/1231') + sinon.spy(actions, '/ip4/127.0.0.1/tcp/1232') + sinon.spy(actions, '/ip4/127.0.0.1/tcp/1233') + + try { + setTimeout(() => controller.abort(), 100) + await dialRequest.run({ signal: controller.signal }) + expect.fail('dial should have failed') + } catch (err: any) { + expect(err).to.have.property('name', 'AggregateError') + } + + expect(actions['/ip4/127.0.0.1/tcp/1231']).to.have.property('callCount', 1) + expect(actions['/ip4/127.0.0.1/tcp/1232']).to.have.property('callCount', 1) + expect(actions['/ip4/127.0.0.1/tcp/1233']).to.have.property('callCount', 1) + + expect(dialerGetTokensSpy.calledWith(addrs.length)).to.equal(true) + expect(dialerReleaseTokenSpy.callCount).to.equal(2) + }) +}) diff --git a/test/dialing/direct.node.js b/test/dialing/direct.node.js deleted file mode 100644 index 81d8205d2b..0000000000 --- a/test/dialing/direct.node.js +++ /dev/null @@ -1,572 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') -const Transport = require('libp2p-tcp') -const Muxer = require('libp2p-mplex') -const { NOISE: Crypto } = require('@chainsafe/libp2p-noise') -const { Multiaddr } = require('multiaddr') -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') -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') -const { MemoryDatastore } = require('datastore-core/memory') -const Libp2p = require('../../src') -const Dialer = require('../../src/dialer') -const AddressManager = require('../../src/address-manager') -const PeerStore = require('../../src/peer-store') -const TransportManager = require('../../src/transport-manager') -const { codes: ErrorCodes } = require('../../src/errors') -const Protector = require('../../src/pnet') -const swarmKeyBuffer = uint8ArrayFromString(require('../fixtures/swarm.key')) -const { mockConnectionGater } = require('../utils/mock-connection-gater') -const mockUpgrader = require('../utils/mockUpgrader') -const createMockConnection = require('../utils/mockConnection') -const Peers = require('../fixtures/peers') -const { createPeerId } = require('../utils/creators/peer') - -const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') -const unsupportedAddr = new Multiaddr('/ip4/127.0.0.1/tcp/9999/ws/p2p/QmckxVrJw1Yo8LqvmDJNUmdAsKtSbiKWmrXJFyKmUraBoN') - -describe('Dialing (direct, TCP)', () => { - const connectionGater = mockConnectionGater() - let remoteTM - let localTM - let peerStore - let remoteAddr - - beforeEach(async () => { - const [localPeerId, remotePeerId] = await Promise.all([ - PeerId.createFromJSON(Peers[0]), - PeerId.createFromJSON(Peers[1]) - ]) - - peerStore = new PeerStore({ - peerId: remotePeerId, - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - remoteTM = new TransportManager({ - libp2p: { - addressManager: new AddressManager(remotePeerId, { listen: [listenAddr] }), - peerId: remotePeerId, - peerStore - }, - upgrader: mockUpgrader - }) - remoteTM.add(Transport.prototype[Symbol.toStringTag], Transport) - - localTM = new TransportManager({ - libp2p: { - peerId: localPeerId, - peerStore: new PeerStore({ - peerId: localPeerId, - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - }, - upgrader: mockUpgrader - }) - localTM.add(Transport.prototype[Symbol.toStringTag], Transport) - - await remoteTM.listen([listenAddr]) - - remoteAddr = remoteTM.getAddrs()[0].encapsulate(`/p2p/${remotePeerId.toB58String()}`) - }) - - afterEach(() => remoteTM.close()) - - afterEach(() => { - sinon.restore() - }) - - it('should be able to connect to a remote node via its multiaddr', async () => { - const dialer = new Dialer({ - transportManager: localTM, - peerStore, - connectionGater - }) - - const connection = await dialer.connectToPeer(remoteAddr) - expect(connection).to.exist() - await connection.close() - }) - - it('should be able to connect to a remote node via its stringified multiaddr', async () => { - const dialer = new Dialer({ - transportManager: localTM, - peerStore, - connectionGater - }) - const connection = await dialer.connectToPeer(remoteAddr.toString()) - expect(connection).to.exist() - await connection.close() - }) - - it('should fail to connect to an unsupported multiaddr', async () => { - const dialer = new Dialer({ - transportManager: localTM, - peerStore, - connectionGater - }) - - await expect(dialer.connectToPeer(unsupportedAddr)) - .to.eventually.be.rejectedWith(Error) - .and.to.have.nested.property('.code', ErrorCodes.ERR_NO_VALID_ADDRESSES) - }) - - it('should fail to connect if peer has no known addresses', async () => { - const dialer = new Dialer({ - transportManager: localTM, - peerStore, - connectionGater - }) - 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 peerId = await PeerId.createFromJSON(Peers[0]) - const peerStore = new PeerStore({ - peerId, - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - const dialer = new Dialer({ - transportManager: localTM, - peerStore, - connectionGater - }) - - await peerStore.addressBook.set(peerId, remoteTM.getAddrs()) - - const connection = await dialer.connectToPeer(peerId) - expect(connection).to.exist() - await connection.close() - }) - - it('should fail to connect to a given peer with unsupported addresses', async () => { - const dialer = new Dialer({ - transportManager: localTM, - peerStore: { - addressBook: { - add: () => {}, - getMultiaddrsForPeer: () => [unsupportedAddr] - } - }, - connectionGater - }) - const peerId = await PeerId.createFromJSON(Peers[0]) - - await expect(dialer.connectToPeer(peerId)) - .to.eventually.be.rejectedWith(Error) - .and.to.have.nested.property('.code', ErrorCodes.ERR_NO_VALID_ADDRESSES) - }) - - it('should only try to connect to addresses supported by the transports configured', async () => { - const remoteAddrs = remoteTM.getAddrs() - const dialer = new Dialer({ - transportManager: localTM, - peerStore: { - addressBook: { - add: () => { }, - getMultiaddrsForPeer: () => [...remoteAddrs, unsupportedAddr] - } - }, - connectionGater - }) - const peerId = await PeerId.createFromJSON(Peers[0]) - - sinon.spy(localTM, 'dial') - const connection = await dialer.connectToPeer(peerId) - expect(localTM.dial.callCount).to.equal(remoteAddrs.length) - expect(connection).to.exist() - await connection.close() - }) - - it('should abort dials on queue task timeout', async () => { - const dialer = new Dialer({ - transportManager: localTM, - peerStore, - dialTimeout: 50, - connectionGater - }) - sinon.stub(localTM, 'dial').callsFake(async (addr, options) => { - expect(options.signal).to.exist() - expect(options.signal.aborted).to.equal(false) - expect(addr.toString()).to.eql(remoteAddr.toString()) - await delay(60) - expect(options.signal.aborted).to.equal(true) - throw new AbortError() - }) - - await expect(dialer.connectToPeer(remoteAddr)) - .to.eventually.be.rejectedWith(Error) - .and.to.have.property('code', ErrorCodes.ERR_TIMEOUT) - }) - - it('should dial to the max concurrency', async () => { - const addrs = [ - new Multiaddr('/ip4/0.0.0.0/tcp/8000'), - new Multiaddr('/ip4/0.0.0.0/tcp/8001'), - new Multiaddr('/ip4/0.0.0.0/tcp/8002') - ] - const dialer = new Dialer({ - transportManager: localTM, - maxParallelDials: 2, - peerStore: { - addressBook: { - add: () => {}, - getMultiaddrsForPeer: () => addrs - } - }, - connectionGater - }) - - expect(dialer.tokens).to.have.length(2) - - const deferredDial = pDefer() - sinon.stub(localTM, 'dial').callsFake(() => deferredDial.promise) - - const [peerId] = await createPeerId() - - // Perform 3 multiaddr dials - dialer.connectToPeer(peerId) - - // Let the call stack run - await delay(0) - - // We should have 2 in progress, and 1 waiting - expect(dialer.tokens).to.have.length(0) - - deferredDial.resolve(await createMockConnection()) - - // Let the call stack run - await delay(0) - - // Only two dials should be executed, as the first dial will succeed - expect(localTM.dial.callCount).to.equal(2) - expect(dialer.tokens).to.have.length(2) - }) - - describe('libp2p.dialer', () => { - let peerId, remotePeerId - let libp2p - let remoteLibp2p - let remoteAddr - - before(async () => { - [peerId, remotePeerId] = await Promise.all([ - PeerId.createFromJSON(Peers[0]), - PeerId.createFromJSON(Peers[1]) - ]) - - remoteLibp2p = new Libp2p({ - peerId: remotePeerId, - addresses: { - listen: [listenAddr] - }, - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - } - }) - await remoteLibp2p.handle('/echo/1.0.0', ({ stream }) => pipe(stream, stream)) - - await remoteLibp2p.start() - remoteAddr = remoteLibp2p.transportManager.getAddrs()[0].encapsulate(`/p2p/${remotePeerId.toB58String()}`) - }) - - afterEach(async () => { - sinon.restore() - libp2p && await libp2p.stop() - libp2p = null - }) - - after(() => remoteLibp2p.stop()) - - it('should fail if no peer id is provided', async () => { - libp2p = new Libp2p({ - peerId, - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - } - }) - - await libp2p.start() - - sinon.spy(libp2p.dialer, 'connectToPeer') - - try { - await libp2p.dial(remoteLibp2p.transportManager.getAddrs()[0]) - } catch (/** @type {any} */ err) { - expect(err).to.have.property('code', ErrorCodes.ERR_INVALID_MULTIADDR) - return - } - - expect.fail('dial should have failed') - }) - - it('should use the dialer for connecting to a multiaddr', async () => { - libp2p = new Libp2p({ - peerId, - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - } - }) - - await libp2p.start() - - sinon.spy(libp2p.dialer, 'connectToPeer') - - const connection = await libp2p.dial(remoteAddr) - expect(connection).to.exist() - const { stream, protocol } = await connection.newStream('/echo/1.0.0') - expect(stream).to.exist() - expect(protocol).to.equal('/echo/1.0.0') - await connection.close() - expect(libp2p.dialer.connectToPeer.callCount).to.be.greaterThan(0) - }) - - it('should use the dialer for connecting to a peer', async () => { - libp2p = new Libp2p({ - peerId, - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - } - }) - - await libp2p.start() - - sinon.spy(libp2p.dialer, 'connectToPeer') - await libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) - - const connection = await libp2p.dial(remotePeerId) - expect(connection).to.exist() - const { stream, protocol } = await connection.newStream('/echo/1.0.0') - expect(stream).to.exist() - expect(protocol).to.equal('/echo/1.0.0') - await connection.close() - expect(libp2p.dialer.connectToPeer.callCount).to.be.greaterThan(0) - }) - - it('should close all streams when the connection closes', async () => { - libp2p = new Libp2p({ - peerId, - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - } - }) - - await libp2p.start() - - // register some stream handlers to simulate several protocols - await libp2p.handle('/stream-count/1', ({ stream }) => pipe(stream, stream)) - await libp2p.handle('/stream-count/2', ({ stream }) => pipe(stream, stream)) - await remoteLibp2p.handle('/stream-count/3', ({ stream }) => pipe(stream, stream)) - await remoteLibp2p.handle('/stream-count/4', ({ stream }) => pipe(stream, stream)) - - await 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 throw when using dialProtocol with no protocols', async () => { - libp2p = new Libp2p({ - peerId, - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - } - }) - - await libp2p.start() - - await expect(libp2p.dialProtocol(remotePeerId)) - .to.eventually.be.rejectedWith(Error) - .and.to.have.property('code', ErrorCodes.ERR_INVALID_PROTOCOLS_FOR_STREAM) - - await expect(libp2p.dialProtocol(remotePeerId, [])) - .to.eventually.be.rejectedWith(Error) - .and.to.have.property('code', ErrorCodes.ERR_INVALID_PROTOCOLS_FOR_STREAM) - }) - - it('should be able to use hangup to close connections', async () => { - libp2p = new Libp2p({ - peerId, - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - } - }) - - await libp2p.start() - - const connection = await libp2p.dial(remoteAddr) - expect(connection).to.exist() - expect(connection.stat.timeline.close).to.not.exist() - await libp2p.hangUp(connection.remotePeer) - expect(connection.stat.timeline.close).to.exist() - }) - - it('should be able to use hangup by address string to close connections', async () => { - libp2p = new Libp2p({ - peerId, - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - } - }) - - await libp2p.start() - - const connection = await libp2p.dial(`${remoteAddr.toString()}`) - expect(connection).to.exist() - expect(connection.stat.timeline.close).to.not.exist() - await libp2p.hangUp(connection.remotePeer) - expect(connection.stat.timeline.close).to.exist() - }) - - it('should use the protectors when provided for connecting', async () => { - const protector = new Protector(swarmKeyBuffer) - libp2p = new Libp2p({ - peerId, - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto], - connProtector: protector - } - }) - - sinon.spy(libp2p.upgrader.protector, 'protect') - sinon.stub(remoteLibp2p.upgrader, 'protector').value(new Protector(swarmKeyBuffer)) - - await libp2p.start() - - const connection = await libp2p.dialer.connectToPeer(remoteAddr) - expect(connection).to.exist() - const { stream, protocol } = await connection.newStream('/echo/1.0.0') - expect(stream).to.exist() - expect(protocol).to.equal('/echo/1.0.0') - await connection.close() - expect(libp2p.upgrader.protector.protect.callCount).to.equal(1) - }) - - it('should coalesce parallel dials to the same peer (id in multiaddr)', async () => { - libp2p = new Libp2p({ - peerId, - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - } - }) - - await libp2p.start() - - const dials = 10 - const fullAddress = remoteAddr.encapsulate(`/p2p/${remoteLibp2p.peerId.toB58String()}`) - - await libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) - const dialResults = await Promise.all([...new Array(dials)].map((_, index) => { - if (index % 2 === 0) return libp2p.dial(remoteLibp2p.peerId) - return libp2p.dial(fullAddress) - })) - - // All should succeed and we should have ten results - expect(dialResults).to.have.length(10) - for (const connection of dialResults) { - expect(Connection.isConnection(connection)).to.equal(true) - } - - // 1 connection, because we know the peer in the multiaddr - expect(libp2p.connectionManager.size).to.equal(1) - expect(remoteLibp2p.connectionManager.size).to.equal(1) - }) - - it('should coalesce parallel dials to the same error on failure', async () => { - libp2p = new Libp2p({ - peerId, - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - } - }) - - await libp2p.start() - - const dials = 10 - const error = new Error('Boom') - sinon.stub(libp2p.transportManager, 'dial').callsFake(() => Promise.reject(error)) - - await libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) - const dialResults = await pSettle([...new Array(dials)].map((_, index) => { - if (index % 2 === 0) return libp2p.dial(remoteLibp2p.peerId) - return libp2p.dial(remoteAddr) - })) - - // All should succeed and we should have ten results - expect(dialResults).to.have.length(10) - for (const result of dialResults) { - expect(result).to.have.property('isRejected', true) - expect(result.reason).to.be.an.instanceof(AggregateError) - // All errors should be the exact same as `error` - for (const err of result.reason) { - expect(err).to.equal(error) - } - } - - // 1 connection, because we know the peer in the multiaddr - expect(libp2p.connectionManager.size).to.equal(0) - expect(remoteLibp2p.connectionManager.size).to.equal(0) - }) - }) -}) diff --git a/test/dialing/direct.node.ts b/test/dialing/direct.node.ts new file mode 100644 index 0000000000..e92985004e --- /dev/null +++ b/test/dialing/direct.node.ts @@ -0,0 +1,572 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import sinon from 'sinon' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { NOISE } from '@chainsafe/libp2p-noise' +import { Multiaddr } from '@multiformats/multiaddr' + +import delay from 'delay' +import pDefer from 'p-defer' +import pSettle, { PromiseResult } from 'p-settle' +import pWaitFor from 'p-wait-for' +import { pipe } from 'it-pipe' +import { pushable } from 'it-pushable' +import { Connection, isConnection } from '@libp2p/interfaces/connection' +import { AbortError } from '@libp2p/interfaces/errors' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { MemoryDatastore } from 'datastore-core/memory' +import { DefaultDialer } from '../../src/dialer/index.js' +import { DefaultAddressManager } from '../../src/address-manager/index.js' +import { PersistentPeerStore } from '@libp2p/peer-store' +import { DefaultTransportManager } from '../../src/transport-manager.js' +import { codes as ErrorCodes } from '../../src/errors.js' +import { mockConnectionGater, mockDuplex, mockMultiaddrConnection, mockUpgrader, mockConnection } from '@libp2p/interface-compliance-tests/mocks' +import Peers from '../fixtures/peers.js' +import { Components } from '@libp2p/interfaces/components' +import type { PeerStore } from '@libp2p/interfaces/peer-store' +import { createFromJSON } from '@libp2p/peer-id-factory' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' +import { PreSharedKeyConnectionProtector } from '../../src/pnet/index.js' +import swarmKey from '../fixtures/swarm.key.js' +import { DefaultConnectionManager } from '../../src/connection-manager/index.js' + +const swarmKeyBuffer = uint8ArrayFromString(swarmKey) +const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') +const unsupportedAddr = new Multiaddr('/ip4/127.0.0.1/tcp/9999/ws/p2p/QmckxVrJw1Yo8LqvmDJNUmdAsKtSbiKWmrXJFyKmUraBoN') + +describe('Dialing (direct, TCP)', () => { + let remoteTM: DefaultTransportManager + let localTM: DefaultTransportManager + let peerStore: PeerStore + let remoteAddr: Multiaddr + let remoteComponents: Components + let localComponents: Components + + beforeEach(async () => { + const [localPeerId, remotePeerId] = await Promise.all([ + createFromJSON(Peers[0]), + createFromJSON(Peers[1]) + ]) + + remoteComponents = new Components({ + peerId: remotePeerId, + datastore: new MemoryDatastore(), + upgrader: mockUpgrader(), + connectionGater: mockConnectionGater() + }) + remoteComponents.setAddressManager(new DefaultAddressManager(remoteComponents, { + listen: [ + listenAddr.toString() + ] + })) + peerStore = new PersistentPeerStore(remoteComponents, { + addressFilter: remoteComponents.getConnectionGater().filterMultiaddrForPeer + }) + remoteComponents.setPeerStore(peerStore) + remoteTM = new DefaultTransportManager(remoteComponents) + remoteTM.add(new TCP()) + + localComponents = new Components({ + peerId: localPeerId, + datastore: new MemoryDatastore(), + upgrader: mockUpgrader(), + connectionGater: mockConnectionGater() + }) + localComponents.setPeerStore(new PersistentPeerStore(localComponents, { + addressFilter: localComponents.getConnectionGater().filterMultiaddrForPeer + })) + localComponents.setConnectionManager(new DefaultConnectionManager(localComponents)) + + localTM = new DefaultTransportManager(localComponents) + localTM.add(new TCP()) + + localComponents.setTransportManager(localTM) + + await remoteTM.listen([listenAddr]) + + remoteAddr = remoteTM.getAddrs()[0].encapsulate(`/p2p/${remotePeerId.toString()}`) + }) + + afterEach(async () => await remoteTM.stop()) + + afterEach(() => { + sinon.restore() + }) + + it('should be able to connect to a remote node via its multiaddr', async () => { + const dialer = new DefaultDialer(localComponents) + + const connection = await dialer.dial(remoteAddr) + expect(connection).to.exist() + await connection.close() + }) + + it('should fail to connect to an unsupported multiaddr', async () => { + const dialer = new DefaultDialer(localComponents) + + await expect(dialer.dial(unsupportedAddr)) + .to.eventually.be.rejectedWith(Error) + .and.to.have.nested.property('.code', ErrorCodes.ERR_NO_VALID_ADDRESSES) + }) + + it('should fail to connect if peer has no known addresses', async () => { + const dialer = new DefaultDialer(localComponents) + const peerId = await createFromJSON(Peers[1]) + + await expect(dialer.dial(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 () => { + await localComponents.getPeerStore().addressBook.set(remoteComponents.getPeerId(), remoteTM.getAddrs()) + + const dialer = new DefaultDialer(localComponents) + + const connection = await dialer.dial(remoteComponents.getPeerId()) + expect(connection).to.exist() + await connection.close() + }) + + it('should fail to connect to a given peer with unsupported addresses', async () => { + await localComponents.getPeerStore().addressBook.add(remoteComponents.getPeerId(), [unsupportedAddr]) + + const dialer = new DefaultDialer(localComponents) + + await expect(dialer.dial(remoteComponents.getPeerId())) + .to.eventually.be.rejectedWith(Error) + .and.to.have.nested.property('.code', ErrorCodes.ERR_NO_VALID_ADDRESSES) + }) + + it('should only try to connect to addresses supported by the transports configured', async () => { + const remoteAddrs = remoteTM.getAddrs() + + const peerId = await createFromJSON(Peers[1]) + await localComponents.getPeerStore().addressBook.add(peerId, [...remoteAddrs, unsupportedAddr]) + + const dialer = new DefaultDialer(localComponents) + + sinon.spy(localTM, 'dial') + const connection = await dialer.dial(peerId) + expect(localTM.dial).to.have.property('callCount', remoteAddrs.length) + expect(connection).to.exist() + + await connection.close() + }) + + it('should abort dials on queue task timeout', async () => { + const dialer = new DefaultDialer(localComponents, { + dialTimeout: 50 + }) + + sinon.stub(localTM, 'dial').callsFake(async (addr, options = {}) => { + expect(options.signal).to.exist() + expect(options.signal?.aborted).to.equal(false) + expect(addr.toString()).to.eql(remoteAddr.toString()) + await delay(60) + expect(options.signal?.aborted).to.equal(true) + throw new AbortError() + }) + + await expect(dialer.dial(remoteAddr)) + .to.eventually.be.rejectedWith(Error) + .and.to.have.property('code', ErrorCodes.ERR_TIMEOUT) + }) + + it('should dial to the max concurrency', async () => { + const addrs = [ + new Multiaddr('/ip4/0.0.0.0/tcp/8000'), + new Multiaddr('/ip4/0.0.0.0/tcp/8001'), + new Multiaddr('/ip4/0.0.0.0/tcp/8002') + ] + const peerId = await createFromJSON(Peers[1]) + + await localComponents.getPeerStore().addressBook.add(peerId, addrs) + + const dialer = new DefaultDialer(localComponents, { + maxParallelDials: 2 + }) + + expect(dialer.tokens).to.have.lengthOf(2) + + const deferredDial = pDefer() + sinon.stub(localTM, 'dial').callsFake(async () => await deferredDial.promise) + + // Perform 3 multiaddr dials + void dialer.dial(peerId) + + // Let the call stack run + await delay(0) + + // We should have 2 in progress, and 1 waiting + expect(dialer.tokens).to.have.lengthOf(0) + + deferredDial.resolve(mockConnection(mockMultiaddrConnection(mockDuplex(), peerId))) + + // Let the call stack run + await delay(0) + + // Only two dials should be executed, as the first dial will succeed + expect(localTM.dial).to.have.property('callCount', 2) + expect(dialer.tokens).to.have.lengthOf(2) + }) +}) + +describe('libp2p.dialer (direct, TCP)', () => { + let peerId: PeerId, remotePeerId: PeerId + let libp2p: Libp2pNode + let remoteLibp2p: Libp2pNode + let remoteAddr: Multiaddr + + beforeEach(async () => { + [peerId, remotePeerId] = await Promise.all([ + createFromJSON(Peers[0]), + createFromJSON(Peers[1]) + ]) + + remoteLibp2p = await createLibp2pNode({ + peerId: remotePeerId, + addresses: { + listen: [listenAddr.toString()] + }, + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + await remoteLibp2p.handle('/echo/1.0.0', ({ stream }) => { + void pipe(stream, stream) + }) + + await remoteLibp2p.start() + remoteAddr = remoteLibp2p.components.getTransportManager().getAddrs()[0].encapsulate(`/p2p/${remotePeerId.toString()}`) + }) + + afterEach(async () => { + sinon.restore() + + if (libp2p != null) { + await libp2p.stop() + } + + if (remoteLibp2p != null) { + await remoteLibp2p.stop() + } + }) + + it('should fail if no peer id is provided', async () => { + libp2p = await createLibp2pNode({ + peerId, + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + + await libp2p.start() + + await expect(libp2p.dial(remoteLibp2p.components.getTransportManager().getAddrs()[0])).to.eventually.be.rejected() + .with.property('code', ErrorCodes.ERR_INVALID_MULTIADDR) + }) + + it('should use the dialer for connecting to a multiaddr', async () => { + libp2p = await createLibp2pNode({ + peerId, + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + + await libp2p.start() + + const dialerDialSpy = sinon.spy(libp2p.components.getDialer(), 'dial') + + const connection = await libp2p.dial(remoteAddr) + expect(connection).to.exist() + const { stream, protocol } = await connection.newStream(['/echo/1.0.0']) + expect(stream).to.exist() + expect(protocol).to.equal('/echo/1.0.0') + expect(dialerDialSpy.callCount).to.be.greaterThan(0) + await connection.close() + }) + + it('should use the dialer for connecting to a peer', async () => { + libp2p = await createLibp2pNode({ + peerId, + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + + await libp2p.start() + + const dialerDialSpy = sinon.spy(libp2p.components.getDialer(), 'dial') + + await libp2p.components.getPeerStore().addressBook.set(remotePeerId, remoteLibp2p.getMultiaddrs()) + + const connection = await libp2p.dial(remotePeerId) + expect(connection).to.exist() + const { stream, protocol } = await connection.newStream('/echo/1.0.0') + expect(stream).to.exist() + expect(protocol).to.equal('/echo/1.0.0') + await connection.close() + expect(dialerDialSpy.callCount).to.be.greaterThan(0) + }) + + it('should close all streams when the connection closes', async () => { + libp2p = await createLibp2pNode({ + peerId, + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + + await libp2p.start() + + // register some stream handlers to simulate several protocols + await libp2p.handle('/stream-count/1', ({ stream }) => { + void pipe(stream, stream) + }) + await libp2p.handle('/stream-count/2', ({ stream }) => { + void pipe(stream, stream) + }) + await remoteLibp2p.handle('/stream-count/3', ({ stream }) => { + void pipe(stream, stream) + }) + await remoteLibp2p.handle('/stream-count/4', ({ stream }) => { + void pipe(stream, stream) + }) + + await libp2p.components.getPeerStore().addressBook.set(remotePeerId, remoteLibp2p.getMultiaddrs()) + 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() + void stream.sink(source) + source.push(uint8ArrayFromString('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.getConnections(libp2p.peerId) + + if (remoteConn == null) { + throw new Error('No remote connection found') + } + + expect(connection.streams).to.have.length(5) + expect(remoteConn).to.have.lengthOf(1) + expect(remoteConn).to.have.nested.property('[0].streams').with.lengthOf(5) + + // Close the connection and verify all streams have been closed + await connection.close() + await pWaitFor(() => connection.streams.length === 0) + await pWaitFor(() => remoteConn[0].streams.length === 0) + }) + + it('should throw when using dialProtocol with no protocols', async () => { + libp2p = await createLibp2pNode({ + peerId, + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + + await libp2p.start() + + // @ts-expect-error invalid params + await expect(libp2p.dialProtocol(remoteAddr)) + .to.eventually.be.rejectedWith(Error) + .and.to.have.property('code', ErrorCodes.ERR_INVALID_PROTOCOLS_FOR_STREAM) + + await expect(libp2p.dialProtocol(remoteAddr, [])) + .to.eventually.be.rejectedWith(Error) + .and.to.have.property('code', ErrorCodes.ERR_INVALID_PROTOCOLS_FOR_STREAM) + }) + + it('should be able to use hangup to close connections', async () => { + libp2p = await createLibp2pNode({ + peerId, + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + + await libp2p.start() + + const connection = await libp2p.dial(remoteAddr) + expect(connection).to.exist() + expect(connection.stat.timeline.close).to.not.exist() + await libp2p.hangUp(connection.remotePeer) + expect(connection.stat.timeline.close).to.exist() + }) + + it('should use the protectors when provided for connecting', async () => { + libp2p = await createLibp2pNode({ + peerId, + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ], + connectionProtector: new PreSharedKeyConnectionProtector({ + psk: swarmKeyBuffer + }) + }) + + const protector = libp2p.components.getConnectionProtector() + + if (protector == null) { + throw new Error('No protector was configured') + } + + const protectorProtectSpy = sinon.spy(protector, 'protect') + + remoteLibp2p.components.setConnectionProtector(new PreSharedKeyConnectionProtector({ enabled: true, psk: swarmKeyBuffer })) + + await libp2p.start() + + const connection = await libp2p.dial(remoteAddr) + expect(connection).to.exist() + const { stream, protocol } = await connection.newStream('/echo/1.0.0') + expect(stream).to.exist() + expect(protocol).to.equal('/echo/1.0.0') + await connection.close() + expect(protectorProtectSpy.callCount).to.equal(1) + }) + + it('should coalesce parallel dials to the same peer (id in multiaddr)', async () => { + libp2p = await createLibp2pNode({ + peerId, + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + + await libp2p.start() + + const dials = 10 + const fullAddress = remoteAddr.encapsulate(`/p2p/${remoteLibp2p.peerId.toString()}`) + + await libp2p.components.getPeerStore().addressBook.set(remotePeerId, remoteLibp2p.getMultiaddrs()) + const dialResults = await Promise.all([...new Array(dials)].map(async (_, index) => { + if (index % 2 === 0) return await libp2p.dial(remoteLibp2p.peerId) + return await libp2p.dial(fullAddress) + })) + + // All should succeed and we should have ten results + expect(dialResults).to.have.length(10) + for (const connection of dialResults) { + expect(isConnection(connection)).to.equal(true) + } + + // 1 connection, because we know the peer in the multiaddr + expect(libp2p.getConnections()).to.have.lengthOf(1) + expect(remoteLibp2p.getConnections()).to.have.lengthOf(1) + }) + + it('should coalesce parallel dials to the same error on failure', async () => { + libp2p = await createLibp2pNode({ + peerId, + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + + await libp2p.start() + + const dials = 10 + const error = new Error('Boom') + sinon.stub(libp2p.components.getTransportManager(), 'dial').callsFake(async () => await Promise.reject(error)) + + await libp2p.components.getPeerStore().addressBook.set(remotePeerId, remoteLibp2p.getMultiaddrs()) + const dialResults: Array> = await pSettle([...new Array(dials)].map(async (_, index) => { + if (index % 2 === 0) return await libp2p.dial(remoteLibp2p.peerId) + return await libp2p.dial(remoteAddr) + })) + + // All should succeed and we should have ten results + expect(dialResults).to.have.length(10) + + for (const result of dialResults) { + expect(result).to.have.property('isRejected', true) + expect(result).to.have.property('reason').that.has.property('name', 'AggregateError') + + // All errors should be the exact same as `error` + // @ts-expect-error reason is any + for (const err of result.reason.errors) { + expect(err).to.equal(error) + } + } + + // 1 connection, because we know the peer in the multiaddr + expect(libp2p.getConnections()).to.have.lengthOf(0) + expect(remoteLibp2p.getConnections()).to.have.lengthOf(0) + }) +}) diff --git a/test/dialing/direct.spec.js b/test/dialing/direct.spec.js deleted file mode 100644 index aa0e45bbc4..0000000000 --- a/test/dialing/direct.spec.js +++ /dev/null @@ -1,637 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') -const pDefer = require('p-defer') -const pWaitFor = require('p-wait-for') -const delay = require('delay') -const Transport = require('libp2p-websockets') -const filters = require('libp2p-websockets/src/filters') -const Muxer = require('libp2p-mplex') -const { NOISE: Crypto } = require('@chainsafe/libp2p-noise') -const { Multiaddr } = require('multiaddr') -const AggregateError = require('aggregate-error') -const { AbortError } = require('libp2p-interfaces/src/transport/errors') -const { MemoryDatastore } = require('datastore-core/memory') -const { codes: ErrorCodes } = require('../../src/errors') -const Constants = require('../../src/constants') -const Dialer = require('../../src/dialer') -const addressSort = require('libp2p-utils/src/address-sort') -const PeerStore = require('../../src/peer-store') -const TransportManager = require('../../src/transport-manager') -const Libp2p = require('../../src') -const { mockConnectionGater } = require('../utils/mock-connection-gater') - -const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser') -const mockUpgrader = require('../utils/mockUpgrader') -const createMockConnection = require('../utils/mockConnection') -const { createPeerId } = require('../utils/creators/peer') -const unsupportedAddr = new Multiaddr('/ip4/127.0.0.1/tcp/9999/ws/p2p/QmckxVrJw1Yo8LqvmDJNUmdAsKtSbiKWmrXJFyKmUraBoN') -const remoteAddr = MULTIADDRS_WEBSOCKETS[0] - -describe('Dialing (direct, WebSockets)', () => { - const connectionGater = mockConnectionGater() - let localTM - let peerStore - let peerId - - before(async () => { - [peerId] = await createPeerId() - peerStore = new PeerStore({ - peerId, - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - localTM = new TransportManager({ - libp2p: {}, - upgrader: mockUpgrader, - onConnection: () => {} - }) - localTM.add(Transport.prototype[Symbol.toStringTag], Transport, { filter: filters.all }) - }) - - afterEach(async () => { - await peerStore.delete(peerId) - sinon.restore() - }) - - it('should have appropriate defaults', () => { - const dialer = new Dialer({ - transportManager: localTM, - peerStore, - connectionGater - }) - expect(dialer.maxParallelDials).to.equal(Constants.MAX_PARALLEL_DIALS) - expect(dialer.timeout).to.equal(Constants.DIAL_TIMEOUT) - }) - - it('should limit the number of tokens it provides', () => { - const dialer = new Dialer({ - transportManager: localTM, - peerStore, - connectionGater - }) - const maxPerPeer = Constants.MAX_PER_PEER_DIALS - expect(dialer.tokens).to.have.length(Constants.MAX_PARALLEL_DIALS) - const tokens = dialer.getTokens(maxPerPeer + 1) - expect(tokens).to.have.length(maxPerPeer) - expect(dialer.tokens).to.have.length(Constants.MAX_PARALLEL_DIALS - maxPerPeer) - }) - - it('should not return tokens if non are left', () => { - const dialer = new Dialer({ - transportManager: localTM, - peerStore, - connectionGater - }) - sinon.stub(dialer, 'tokens').value([]) - const tokens = dialer.getTokens(1) - expect(tokens.length).to.equal(0) - }) - - it('should NOT be able to return a token twice', () => { - const dialer = new Dialer({ - transportManager: localTM, - peerStore, - connectionGater - }) - const tokens = dialer.getTokens(1) - expect(tokens).to.have.length(1) - expect(dialer.tokens).to.have.length(Constants.MAX_PARALLEL_DIALS - 1) - dialer.releaseToken(tokens[0]) - dialer.releaseToken(tokens[0]) - expect(dialer.tokens).to.have.length(Constants.MAX_PARALLEL_DIALS) - }) - - it('should be able to connect to a remote node via its multiaddr', async () => { - const dialer = new Dialer({ - transportManager: localTM, - peerStore: { - addressBook: { - add: () => {}, - getMultiaddrsForPeer: () => [remoteAddr] - } - }, - connectionGater - }) - - const connection = await dialer.connectToPeer(remoteAddr) - expect(connection).to.exist() - await connection.close() - }) - - it('should be able to connect to a remote node via its stringified multiaddr', async () => { - const dialer = new Dialer({ - transportManager: localTM, - peerStore: { - addressBook: { - add: () => {}, - getMultiaddrsForPeer: () => [remoteAddr] - } - }, - connectionGater - }) - - const connection = await dialer.connectToPeer(remoteAddr.toString()) - expect(connection).to.exist() - await connection.close() - }) - - it('should fail to connect to an unsupported multiaddr', async () => { - const dialer = new Dialer({ - transportManager: localTM, - peerStore, - connectionGater - }) - - await expect(dialer.connectToPeer(unsupportedAddr)) - .to.eventually.be.rejectedWith(AggregateError) - }) - - it('should be able to connect to a given peer', async () => { - const dialer = new Dialer({ - transportManager: localTM, - peerStore: { - addressBook: { - add: () => {}, - getMultiaddrsForPeer: () => [remoteAddr] - } - }, - connectionGater - }) - - const connection = await dialer.connectToPeer(peerId) - expect(connection).to.exist() - await connection.close() - }) - - it('should fail to connect to a given peer with unsupported addresses', async () => { - const dialer = new Dialer({ - transportManager: localTM, - peerStore: { - addressBook: { - set: () => {}, - getMultiaddrsForPeer: () => [unsupportedAddr] - } - }, - connectionGater - }) - - await expect(dialer.connectToPeer(peerId)) - .to.eventually.be.rejectedWith(AggregateError) - }) - - it('should abort dials on queue task timeout', async () => { - const dialer = new Dialer({ - transportManager: localTM, - dialTimeout: 50, - peerStore: { - addressBook: { - add: () => {}, - getMultiaddrsForPeer: () => [remoteAddr] - } - }, - connectionGater - }) - sinon.stub(localTM, 'dial').callsFake(async (addr, options) => { - expect(options.signal).to.exist() - expect(options.signal.aborted).to.equal(false) - expect(addr.toString()).to.eql(remoteAddr.toString()) - await delay(60) - expect(options.signal.aborted).to.equal(true) - throw new AbortError() - }) - - await expect(dialer.connectToPeer(remoteAddr)) - .to.eventually.be.rejected() - .and.to.have.property('code', ErrorCodes.ERR_TIMEOUT) - }) - - it('should throw when a peer advertises more than the allowed number of peers', async () => { - const spy = sinon.spy() - const dialer = new Dialer({ - transportManager: localTM, - maxAddrsToDial: 10, - peerStore: { - delete: spy, - addressBook: { - add: () => { }, - getMultiaddrsForPeer: () => Array.from({ length: 11 }, (_, i) => new Multiaddr(`/ip4/127.0.0.1/tcp/1500${i}/ws/p2p/12D3KooWHFKTMzwerBtsVmtz4ZZEQy2heafxzWw6wNn5PPYkBxJ5`)) - } - }, - connectionGater - }) - - await expect(dialer.connectToPeer(remoteAddr)) - .to.eventually.be.rejected() - .and.to.have.property('code', ErrorCodes.ERR_TOO_MANY_ADDRESSES) - expect(spy.calledOnce).to.be.true() - }) - - it('should sort addresses on dial', async () => { - const peerMultiaddrs = [ - new Multiaddr('/ip4/127.0.0.1/tcp/15001/ws'), - new Multiaddr('/ip4/20.0.0.1/tcp/15001/ws'), - new Multiaddr('/ip4/30.0.0.1/tcp/15001/ws') - ] - - sinon.spy(addressSort, 'publicAddressesFirst') - sinon.stub(localTM, 'dial').callsFake(createMockConnection) - - const dialer = new Dialer({ - transportManager: localTM, - addressSorter: addressSort.publicAddressesFirst, - maxParallelDials: 3, - peerStore, - connectionGater - }) - - // Inject data in the AddressBook - await peerStore.addressBook.add(peerId, peerMultiaddrs) - - // Perform 3 multiaddr dials - await dialer.connectToPeer(peerId) - - expect(addressSort.publicAddressesFirst.callCount).to.eql(1) - - const sortedAddresses = addressSort.publicAddressesFirst(peerMultiaddrs.map((m) => ({ multiaddr: m }))) - expect(localTM.dial.getCall(0).args[0].equals(sortedAddresses[0].multiaddr)) - expect(localTM.dial.getCall(1).args[0].equals(sortedAddresses[1].multiaddr)) - expect(localTM.dial.getCall(2).args[0].equals(sortedAddresses[2].multiaddr)) - }) - - it('should dial to the max concurrency', async () => { - const dialer = new Dialer({ - transportManager: localTM, - maxParallelDials: 2, - peerStore: { - addressBook: { - set: () => {}, - getMultiaddrsForPeer: () => [remoteAddr, remoteAddr, remoteAddr] - } - }, - connectionGater - }) - - expect(dialer.tokens).to.have.length(2) - - const deferredDial = pDefer() - sinon.stub(localTM, 'dial').callsFake(() => deferredDial.promise) - - // Perform 3 multiaddr dials - dialer.connectToPeer(peerId) - - // Let the call stack run - await delay(0) - - // We should have 2 in progress, and 1 waiting - expect(dialer.tokens).to.have.length(0) - expect(dialer._pendingDials.size).to.equal(1) // 1 dial request - - deferredDial.resolve(await createMockConnection()) - - // Let the call stack run - await delay(0) - - // Only two dials will be run, as the first two succeeded - expect(localTM.dial.callCount).to.equal(2) - expect(dialer.tokens).to.have.length(2) - expect(dialer._pendingDials.size).to.equal(0) - }) - - it('.destroy should abort pending dials', async () => { - const dialer = new Dialer({ - transportManager: localTM, - maxParallelDials: 2, - peerStore: { - addressBook: { - set: () => {}, - getMultiaddrsForPeer: () => [remoteAddr, remoteAddr, remoteAddr] - } - }, - connectionGater - }) - - expect(dialer.tokens).to.have.length(2) - - sinon.stub(localTM, 'dial').callsFake((_, options) => { - const deferredDial = pDefer() - const onAbort = () => { - options.signal.removeEventListener('abort', onAbort) - deferredDial.reject(new AbortError()) - } - options.signal.addEventListener('abort', onAbort) - return deferredDial.promise - }) - - // Perform 3 multiaddr dials - const dialPromise = dialer.connectToPeer(peerId) - - // Let the call stack run - await delay(0) - - // We should have 2 in progress, and 1 waiting - expect(dialer.tokens).to.have.length(0) - expect(dialer._pendingDials.size).to.equal(1) // 1 dial request - - try { - dialer.destroy() - await dialPromise - expect.fail('should have failed') - } catch (/** @type {any} */ err) { - expect(err).to.be.an.instanceof(AggregateError) - expect(dialer._pendingDials.size).to.equal(0) // 1 dial request - } - }) - - it('should cancel pending dial targets before proceeding', async () => { - const dialer = new Dialer({ - transportManager: localTM, - peerStore: { - addressBook: { - set: () => { } - } - }, - connectionGater - }) - - sinon.stub(dialer, '_createDialTarget').callsFake(() => { - const deferredDial = pDefer() - return deferredDial.promise - }) - - // Perform dial - const dialPromise = dialer.connectToPeer(peerId) - - // Let the call stack run - await delay(0) - - dialer.destroy() - - await expect(dialPromise) - .to.eventually.be.rejected() - .and.to.have.property('code', 'ABORT_ERR') - }) - - describe('libp2p.dialer', () => { - const transportKey = Transport.prototype[Symbol.toStringTag] - let libp2p - - afterEach(async () => { - sinon.restore() - libp2p && await libp2p.stop() - libp2p = null - }) - - it('should create a dialer', () => { - libp2p = new Libp2p({ - peerId, - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - }, - config: { - transport: { - [transportKey]: { - filter: filters.all - } - } - }, - connectionGater - }) - - expect(libp2p.dialer).to.exist() - expect(libp2p.dialer.maxParallelDials).to.equal(Constants.MAX_PARALLEL_DIALS) - expect(libp2p.dialer.maxDialsPerPeer).to.equal(Constants.MAX_PER_PEER_DIALS) - expect(libp2p.dialer.timeout).to.equal(Constants.DIAL_TIMEOUT) - // Ensure the dialer also has the transport manager - expect(libp2p.transportManager).to.equal(libp2p.dialer.transportManager) - }) - - it('should be able to override dialer options', async () => { - const config = { - peerId, - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - }, - dialer: { - maxParallelDials: 10, - maxDialsPerPeer: 1, - dialTimeout: 1e3 // 30 second dial timeout per peer - }, - config: { - transport: { - [transportKey]: { - filter: filters.all - } - } - } - } - libp2p = await Libp2p.create(config) - - expect(libp2p.dialer).to.exist() - expect(libp2p.dialer.maxParallelDials).to.equal(config.dialer.maxParallelDials) - expect(libp2p.dialer.maxDialsPerPeer).to.equal(config.dialer.maxDialsPerPeer) - expect(libp2p.dialer.timeout).to.equal(config.dialer.dialTimeout) - }) - - it('should use the dialer for connecting', async () => { - libp2p = new Libp2p({ - peerId, - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - }, - config: { - transport: { - [transportKey]: { - filter: filters.all - } - } - } - }) - - sinon.spy(libp2p.dialer, 'connectToPeer') - sinon.spy(libp2p.peerStore.addressBook, 'add') - - await libp2p.start() - - const connection = await libp2p.dial(remoteAddr) - expect(connection).to.exist() - const { stream, protocol } = await connection.newStream('/echo/1.0.0') - expect(stream).to.exist() - expect(protocol).to.equal('/echo/1.0.0') - await connection.close() - expect(libp2p.dialer.connectToPeer.callCount).to.be.at.least(1) - expect(libp2p.peerStore.addressBook.add.callCount).to.be.at.least(1) - - await libp2p.stop() - }) - - it('should run identify automatically after connecting', async () => { - libp2p = new Libp2p({ - peerId, - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - }, - config: { - transport: { - [transportKey]: { - filter: filters.all - } - } - } - }) - - sinon.spy(libp2p.identifyService, 'identify') - sinon.spy(libp2p.upgrader, 'onConnection') - - await libp2p.start() - - const connection = await libp2p.dial(remoteAddr) - expect(connection).to.exist() - - sinon.spy(libp2p.peerStore.protoBook, 'set') - - // Wait for onConnection to be called - await pWaitFor(() => libp2p.upgrader.onConnection.callCount === 1) - - expect(libp2p.identifyService.identify.callCount).to.equal(1) - await libp2p.identifyService.identify.firstCall.returnValue - - expect(libp2p.peerStore.protoBook.set.callCount).to.equal(1) - - await libp2p.stop() - }) - - it('should be able to use hangup to close connections', async () => { - libp2p = new Libp2p({ - peerId, - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - }, - config: { - transport: { - [transportKey]: { - filter: filters.all - } - } - } - }) - - await libp2p.start() - - const connection = await libp2p.dial(remoteAddr) - expect(connection).to.exist() - expect(connection.stat.timeline.close).to.not.exist() - await libp2p.hangUp(connection.remotePeer) - expect(connection.stat.timeline.close).to.exist() - - await libp2p.stop() - }) - - it('should be able to use hangup when no connection exists', async () => { - libp2p = new Libp2p({ - peerId, - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - }, - config: { - transport: { - [transportKey]: { - filter: filters.all - } - } - } - }) - - await libp2p.hangUp(remoteAddr) - }) - - it('should cancel pending dial targets and stop', async () => { - const [, remotePeerId] = await createPeerId({ number: 2 }) - - libp2p = new Libp2p({ - peerId, - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - }, - config: { - transport: { - [transportKey]: { - filter: filters.all - } - } - } - }) - - sinon.stub(libp2p.dialer, '_createDialTarget').callsFake(() => { - const deferredDial = pDefer() - return deferredDial.promise - }) - - // Perform dial - const dialPromise = libp2p.dial(remotePeerId) - - // Let the call stack run - await delay(0) - - await libp2p.stop() - await expect(dialPromise) - .to.eventually.be.rejected() - .and.to.have.property('code', 'ABORT_ERR') - }) - - it('should abort pending dials on stop', async () => { - libp2p = new Libp2p({ - peerId, - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - }, - config: { - transport: { - [transportKey]: { - filter: filters.all - } - } - } - }) - - sinon.spy(libp2p.dialer, 'destroy') - - await libp2p.stop() - - expect(libp2p.dialer.destroy).to.have.property('callCount', 1) - }) - - it('should fail to dial self', async () => { - libp2p = new Libp2p({ - peerId, - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - } - }) - - await expect(libp2p.dial(peerId)) - .to.eventually.be.rejected() - .and.to.have.property('code', ErrorCodes.ERR_DIALED_SELF) - }) - }) -}) diff --git a/test/dialing/direct.spec.ts b/test/dialing/direct.spec.ts new file mode 100644 index 0000000000..b6e698da87 --- /dev/null +++ b/test/dialing/direct.spec.ts @@ -0,0 +1,589 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import sinon from 'sinon' +import pDefer from 'p-defer' +import delay from 'delay' +import { WebSockets } from '@libp2p/websockets' +import * as filters from '@libp2p/websockets/filters' +import { Mplex } from '@libp2p/mplex' +import { NOISE } from '@chainsafe/libp2p-noise' +import { Multiaddr } from '@multiformats/multiaddr' +import { AbortError } from '@libp2p/interfaces/errors' +import { MemoryDatastore } from 'datastore-core/memory' +import { codes as ErrorCodes } from '../../src/errors.js' +import * as Constants from '../../src/constants.js' +import { DefaultDialer, DialTarget } from '../../src/dialer/index.js' +import { publicAddressesFirst } from '@libp2p/utils/address-sort' +import { PersistentPeerStore } from '@libp2p/peer-store' +import { DefaultTransportManager } from '../../src/transport-manager.js' +import { mockConnectionGater, mockDuplex, mockMultiaddrConnection, mockUpgrader, mockConnection } from '@libp2p/interface-compliance-tests/mocks' +import { createPeerId } from '../utils/creators/peer.js' +import type { TransportManager } from '@libp2p/interfaces/transport' +import { Components } from '@libp2p/interfaces/components' +import { peerIdFromString } from '@libp2p/peer-id' +import type { Connection } from '@libp2p/interfaces/connection' +import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' +import { DefaultConnectionManager } from '../../src/connection-manager/index.js' +import { createFromJSON } from '@libp2p/peer-id-factory' +import Peers from '../fixtures/peers.js' +import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' +import type { PeerId } from '@libp2p/interfaces/peer-id' + +const unsupportedAddr = new Multiaddr('/ip4/127.0.0.1/tcp/9999') + +describe('Dialing (direct, WebSockets)', () => { + let localTM: TransportManager + let localComponents: Components + let remoteAddr: Multiaddr + let remoteComponents: Components + + beforeEach(async () => { + localComponents = new Components({ + peerId: await createFromJSON(Peers[0]), + datastore: new MemoryDatastore(), + upgrader: mockUpgrader(), + connectionGater: mockConnectionGater() + }) + localComponents.setPeerStore(new PersistentPeerStore(localComponents, { + addressFilter: localComponents.getConnectionGater().filterMultiaddrForPeer + })) + localComponents.setConnectionManager(new DefaultConnectionManager(localComponents)) + + localTM = new DefaultTransportManager(localComponents) + localTM.add(new WebSockets({ filter: filters.all })) + localComponents.setTransportManager(localTM) + + // this peer is spun up in .aegir.cjs + remoteAddr = MULTIADDRS_WEBSOCKETS[0] + remoteComponents = new Components({ + peerId: peerIdFromString(remoteAddr.getPeerId() ?? '') + }) + }) + + afterEach(async () => { + sinon.restore() + }) + + it('should limit the number of tokens it provides', () => { + const dialer = new DefaultDialer(localComponents) + + const maxPerPeer = Constants.MAX_PER_PEER_DIALS + expect(dialer.tokens).to.have.lengthOf(Constants.MAX_PARALLEL_DIALS) + const tokens = dialer.getTokens(maxPerPeer + 1) + expect(tokens).to.have.length(maxPerPeer) + expect(dialer.tokens).to.have.lengthOf(Constants.MAX_PARALLEL_DIALS - maxPerPeer) + }) + + it('should not return tokens if none are left', () => { + const dialer = new DefaultDialer(localComponents, { + maxDialsPerPeer: Infinity + }) + + const maxTokens = dialer.tokens.length + + const tokens = dialer.getTokens(maxTokens) + + expect(tokens).to.have.lengthOf(maxTokens) + expect(dialer.getTokens(1)).to.be.empty() + }) + + it('should NOT be able to return a token twice', () => { + const dialer = new DefaultDialer(localComponents) + + const tokens = dialer.getTokens(1) + expect(tokens).to.have.length(1) + expect(dialer.tokens).to.have.lengthOf(Constants.MAX_PARALLEL_DIALS - 1) + dialer.releaseToken(tokens[0]) + dialer.releaseToken(tokens[0]) + expect(dialer.tokens).to.have.lengthOf(Constants.MAX_PARALLEL_DIALS) + }) + + it('should be able to connect to a remote node via its multiaddr', async () => { + const dialer = new DefaultDialer(localComponents) + const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') + await localComponents.getPeerStore().addressBook.set(remotePeerId, [remoteAddr]) + + const connection = await dialer.dial(remoteAddr) + expect(connection).to.exist() + await connection.close() + }) + + it('should fail to connect to an unsupported multiaddr', async () => { + const dialer = new DefaultDialer(localComponents) + + await expect(dialer.dial(unsupportedAddr.encapsulate(`/p2p/${remoteComponents.getPeerId().toString()}`))) + .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', async () => { + const dialer = new DefaultDialer(localComponents) + const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') + await localComponents.getPeerStore().addressBook.set(remotePeerId, [remoteAddr]) + + const connection = await dialer.dial(remotePeerId) + expect(connection).to.exist() + await connection.close() + }) + + it('should fail to connect to a given peer with unsupported addresses', async () => { + const dialer = new DefaultDialer(localComponents) + const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') + await localComponents.getPeerStore().addressBook.set(remotePeerId, [unsupportedAddr]) + + await expect(dialer.dial(remotePeerId)) + .to.eventually.be.rejectedWith(Error) + .and.to.have.nested.property('.code', ErrorCodes.ERR_NO_VALID_ADDRESSES) + }) + + it('should abort dials on queue task timeout', async () => { + const dialer = new DefaultDialer(localComponents, { + dialTimeout: 50 + }) + const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') + await localComponents.getPeerStore().addressBook.set(remotePeerId, [remoteAddr]) + + sinon.stub(localTM, 'dial').callsFake(async (addr, options) => { + expect(options.signal).to.exist() + expect(options.signal.aborted).to.equal(false) + expect(addr.toString()).to.eql(remoteAddr.toString()) + await delay(60) + expect(options.signal.aborted).to.equal(true) + throw new AbortError() + }) + + await expect(dialer.dial(remoteAddr)) + .to.eventually.be.rejected() + .and.to.have.property('code', ErrorCodes.ERR_TIMEOUT) + }) + + it('should throw when a peer advertises more than the allowed number of peers', async () => { + const dialer = new DefaultDialer(localComponents, { + maxAddrsToDial: 10 + }) + const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') + await localComponents.getPeerStore().addressBook.set(remotePeerId, Array.from({ length: 11 }, (_, i) => new Multiaddr(`/ip4/127.0.0.1/tcp/1500${i}/ws/p2p/12D3KooWHFKTMzwerBtsVmtz4ZZEQy2heafxzWw6wNn5PPYkBxJ5`))) + + await expect(dialer.dial(remoteAddr)) + .to.eventually.be.rejected() + .and.to.have.property('code', ErrorCodes.ERR_TOO_MANY_ADDRESSES) + }) + + it('should sort addresses on dial', async () => { + const peerMultiaddrs = [ + new Multiaddr('/ip4/127.0.0.1/tcp/15001/ws'), + new Multiaddr('/ip4/20.0.0.1/tcp/15001/ws'), + new Multiaddr('/ip4/30.0.0.1/tcp/15001/ws') + ] + + const publicAddressesFirstSpy = sinon.spy(publicAddressesFirst) + const localTMDialStub = sinon.stub(localTM, 'dial').callsFake(async (ma) => mockConnection(mockMultiaddrConnection(mockDuplex(), peerIdFromString(ma.getPeerId() ?? '')))) + + const dialer = new DefaultDialer(localComponents, { + addressSorter: publicAddressesFirstSpy, + maxParallelDials: 3 + }) + + // Inject data in the AddressBook + await localComponents.getPeerStore().addressBook.add(remoteComponents.getPeerId(), peerMultiaddrs) + + // Perform 3 multiaddr dials + await dialer.dial(remoteComponents.getPeerId()) + + const sortedAddresses = peerMultiaddrs + .map((m) => ({ multiaddr: m, isCertified: false })) + .sort(publicAddressesFirst) + + expect(localTMDialStub.getCall(0).args[0].equals(sortedAddresses[0].multiaddr)) + expect(localTMDialStub.getCall(1).args[0].equals(sortedAddresses[1].multiaddr)) + expect(localTMDialStub.getCall(2).args[0].equals(sortedAddresses[2].multiaddr)) + }) + + it('should dial to the max concurrency', async () => { + const addrs = [ + new Multiaddr('/ip4/0.0.0.0/tcp/8000/ws'), + new Multiaddr('/ip4/0.0.0.0/tcp/8001/ws'), + new Multiaddr('/ip4/0.0.0.0/tcp/8002/ws') + ] + const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') + + const dialer = new DefaultDialer(localComponents, { + maxParallelDials: 2 + }) + + // Inject data in the AddressBook + await localComponents.getPeerStore().addressBook.add(remotePeerId, addrs) + + expect(dialer.tokens).to.have.lengthOf(2) + + const deferredDial = pDefer() + const localTMDialStub = sinon.stub(localTM, 'dial').callsFake(async () => await deferredDial.promise) + + // Perform 3 multiaddr dials + void dialer.dial(remotePeerId) + + // Let the call stack run + await delay(0) + + // We should have 2 in progress, and 1 waiting + expect(dialer.tokens).to.have.lengthOf(0) + + deferredDial.resolve(mockConnection(mockMultiaddrConnection(mockDuplex(), remotePeerId))) + + // Let the call stack run + await delay(0) + + // Only two dials will be run, as the first two succeeded + expect(localTMDialStub.callCount).to.equal(2) + expect(dialer.tokens).to.have.lengthOf(2) + expect(dialer.pendingDials.size).to.equal(0) + }) + + it('.destroy should abort pending dials', async () => { + const addrs = [ + new Multiaddr('/ip4/0.0.0.0/tcp/8000/ws'), + new Multiaddr('/ip4/0.0.0.0/tcp/8001/ws'), + new Multiaddr('/ip4/0.0.0.0/tcp/8002/ws') + ] + const dialer = new DefaultDialer(localComponents, { + maxParallelDials: 2 + }) + + // Inject data in the AddressBook + await localComponents.getPeerStore().addressBook.add(remoteComponents.getPeerId(), addrs) + + expect(dialer.tokens).to.have.lengthOf(2) + + sinon.stub(localTM, 'dial').callsFake(async (_, options) => { + const deferredDial = pDefer() + const onAbort = () => { + options.signal.removeEventListener('abort', onAbort) + deferredDial.reject(new AbortError()) + } + options.signal.addEventListener('abort', onAbort) + return await deferredDial.promise + }) + + // Perform 3 multiaddr dials + const dialPromise = dialer.dial(remoteComponents.getPeerId()) + + // Let the call stack run + await delay(0) + + // We should have 2 in progress, and 1 waiting + expect(dialer.tokens).to.have.length(0) + expect(dialer.pendingDials.size).to.equal(1) // 1 dial request + + try { + await dialer.stop() + await dialPromise + expect.fail('should have failed') + } catch (err: any) { + expect(err).to.have.property('name', 'AggregateError') + expect(dialer.pendingDials.size).to.equal(0) // 1 dial request + } + }) + + it('should cancel pending dial targets before proceeding', async () => { + const dialer = new DefaultDialer(localComponents) + + sinon.stub(dialer, '_createDialTarget').callsFake(async () => { + const deferredDial = pDefer() + return await deferredDial.promise + }) + + // Perform dial + const dialPromise = dialer.dial(remoteComponents.getPeerId()) + + // Let the call stack run + await delay(0) + + await dialer.stop() + + await expect(dialPromise) + .to.eventually.be.rejected() + .and.to.have.property('code', 'ABORT_ERR') + }) +}) + +describe('libp2p.dialer (direct, WebSockets)', () => { + const connectionGater = mockConnectionGater() + let libp2p: Libp2pNode + let peerId: PeerId + + beforeEach(async () => { + peerId = await createPeerId() + }) + + afterEach(async () => { + sinon.restore() + + if (libp2p != null) { + await libp2p.stop() + } + }) + + it('should create a dialer', async () => { + libp2p = await createLibp2pNode({ + peerId, + transports: [ + new WebSockets({ + filter: filters.all + }) + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ], + connectionGater + }) + + expect(libp2p.components.getDialer()).to.exist() + expect(libp2p.components.getDialer()).to.have.property('tokens').with.lengthOf(Constants.MAX_PARALLEL_DIALS) + expect(libp2p.components.getDialer()).to.have.property('maxDialsPerPeer', Constants.MAX_PER_PEER_DIALS) + expect(libp2p.components.getDialer()).to.have.property('timeout', Constants.DIAL_TIMEOUT) + }) + + it('should be able to override dialer options', async () => { + const config = { + peerId, + transports: [ + new WebSockets({ + filter: filters.all + }) + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ], + dialer: { + maxParallelDials: 10, + maxDialsPerPeer: 1, + dialTimeout: 1e3 // 30 second dial timeout per peer + } + } + libp2p = await createLibp2pNode(config) + + expect(libp2p.components.getDialer()).to.exist() + expect(libp2p.components.getDialer()).to.have.property('tokens').with.lengthOf(config.dialer.maxParallelDials) + expect(libp2p.components.getDialer()).to.have.property('maxDialsPerPeer', config.dialer.maxDialsPerPeer) + expect(libp2p.components.getDialer()).to.have.property('timeout', config.dialer.dialTimeout) + }) + + it('should use the dialer for connecting', async () => { + libp2p = await createLibp2pNode({ + peerId, + transports: [ + new WebSockets({ + filter: filters.all + }) + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + + const dialerDialSpy = sinon.spy(libp2p.components.getDialer(), 'dial') + const addressBookAddSpy = sinon.spy(libp2p.components.getPeerStore().addressBook, 'add') + + await libp2p.start() + + const connection = await libp2p.dial(MULTIADDRS_WEBSOCKETS[0]) + expect(connection).to.exist() + const { stream, protocol } = await connection.newStream('/echo/1.0.0') + expect(stream).to.exist() + expect(protocol).to.equal('/echo/1.0.0') + await connection.close() + expect(dialerDialSpy.callCount).to.be.at.least(1) + expect(addressBookAddSpy.callCount).to.be.at.least(1) + + await libp2p.stop() + }) + + it('should run identify automatically after connecting', async () => { + libp2p = await createLibp2pNode({ + peerId, + transports: [ + new WebSockets({ + filter: filters.all + }) + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + + if (libp2p.identifyService == null) { + throw new Error('Identify service missing') + } + + const identifySpy = sinon.spy(libp2p.identifyService, 'identify') + const protobookSetSpy = sinon.spy(libp2p.components.getPeerStore().protoBook, 'set') + const connectionPromise = pDefer() + + await libp2p.start() + + libp2p.components.getUpgrader().addEventListener('connection', () => { + connectionPromise.resolve() + }, { + once: true + }) + + const connection = await libp2p.dial(MULTIADDRS_WEBSOCKETS[0]) + expect(connection).to.exist() + + // Wait for connection event to be emitted + await connectionPromise.promise + + expect(identifySpy.callCount).to.equal(1) + await identifySpy.firstCall.returnValue + + expect(protobookSetSpy.callCount).to.equal(1) + + await libp2p.stop() + }) + + it('should be able to use hangup to close connections', async () => { + libp2p = await createLibp2pNode({ + peerId, + transports: [ + new WebSockets({ + filter: filters.all + }) + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + + await libp2p.start() + + const connection = await libp2p.dial(MULTIADDRS_WEBSOCKETS[0]) + expect(connection).to.exist() + expect(connection.stat.timeline.close).to.not.exist() + + await libp2p.hangUp(connection.remotePeer) + expect(connection.stat.timeline.close).to.exist() + + await libp2p.stop() + }) + + it('should be able to use hangup when no connection exists', async () => { + libp2p = await createLibp2pNode({ + peerId, + transports: [ + new WebSockets({ + filter: filters.all + }) + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + + await libp2p.hangUp(MULTIADDRS_WEBSOCKETS[0]) + }) + + it('should cancel pending dial targets and stop', async () => { + const remotePeerId = await createPeerId() + + libp2p = await createLibp2pNode({ + peerId, + transports: [ + new WebSockets({ + filter: filters.all + }) + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + + sinon.stub(libp2p.components.getDialer() as DefaultDialer, '_createDialTarget').callsFake(async () => { + const deferredDial = pDefer() + return await deferredDial.promise + }) + + await libp2p.start() + + // Perform dial + const dialPromise = libp2p.dial(remotePeerId) + + // Let the call stack run + await delay(0) + + await libp2p.stop() + + await expect(dialPromise) + .to.eventually.be.rejected() + .and.to.have.property('code', 'ABORT_ERR') + }) + + it('should abort pending dials on stop', async () => { + libp2p = await createLibp2pNode({ + peerId, + transports: [ + new WebSockets({ + filter: filters.all + }) + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + + await libp2p.start() + + const dialerDestroyStub = sinon.spy(libp2p.components.getDialer() as DefaultDialer, 'stop') + + await libp2p.stop() + + expect(dialerDestroyStub.callCount).to.equal(1) + }) + + it('should fail to dial self', async () => { + libp2p = await createLibp2pNode({ + peerId, + transports: [ + new WebSockets({ + filter: filters.all + }) + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + + await libp2p.start() + + await expect(libp2p.dial(new Multiaddr(`/ip4/127.0.0.1/tcp/1234/ws/p2p/${peerId.toString()}`))) + .to.eventually.be.rejected() + .and.to.have.property('code', ErrorCodes.ERR_DIALED_SELF) + }) +}) diff --git a/test/dialing/resolver.spec.js b/test/dialing/resolver.spec.js deleted file mode 100644 index ab36881fb8..0000000000 --- a/test/dialing/resolver.spec.js +++ /dev/null @@ -1,180 +0,0 @@ -'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: { - ...baseOptions, - addresses: { - listen: [new Multiaddr(`${relayAddr}/p2p-circuit`)] - }, - config: { - ...baseOptions.config, - peerDiscovery: { - autoDial: false - } - } - }, - started: true, - populateAddressBooks: false - }) - - await libp2p.start() - await remoteLibp2p.start() - }) - - 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 = new Multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId}`) - const relayedAddrFetched = new 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 = new Multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId}`) - const relayedAddrFetched = new 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 = new Multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId}`) - - // Stub resolver - const dnsMa = new 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 = new Multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId}`) - const relayedAddrFetched = new 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 = new 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) - }) -}) diff --git a/test/dialing/resolver.spec.ts b/test/dialing/resolver.spec.ts new file mode 100644 index 0000000000..9c0fc2707b --- /dev/null +++ b/test/dialing/resolver.spec.ts @@ -0,0 +1,226 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import sinon from 'sinon' +import { Multiaddr } from '@multiformats/multiaddr' +import { codes as ErrorCodes } from '../../src/errors.js' +import { createNode } from '../utils/creators/peer.js' +import { createBaseOptions } from '../utils/base-options.browser.js' +import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { Libp2pNode } from '../../src/libp2p.js' +import { Circuit } from '../../src/circuit/transport.js' +import pDefer from 'p-defer' +import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-compliance-tests/mocks' +import { peerIdFromString } from '@libp2p/peer-id' +import { WebSockets } from '@libp2p/websockets' + +const relayAddr = MULTIADDRS_WEBSOCKETS[0] + +const getDnsaddrStub = (peerId: PeerId) => [ + `/dnsaddr/ams-1.bootstrap.libp2p.io/p2p/${peerId.toString()}`, + `/dnsaddr/ams-2.bootstrap.libp2p.io/p2p/${peerId.toString()}`, + `/dnsaddr/lon-1.bootstrap.libp2p.io/p2p/${peerId.toString()}`, + `/dnsaddr/nrt-1.bootstrap.libp2p.io/p2p/${peerId.toString()}`, + `/dnsaddr/nyc-1.bootstrap.libp2p.io/p2p/${peerId.toString()}`, + `/dnsaddr/sfo-2.bootstrap.libp2p.io/p2p/${peerId.toString()}` +] + +const relayedAddr = (peerId: PeerId) => `${relayAddr.toString()}/p2p-circuit/p2p/${peerId.toString()}` + +const getDnsRelayedAddrStub = (peerId: PeerId) => [ + `${relayedAddr(peerId)}` +] + +describe('Dialing (resolvable addresses)', () => { + let libp2p: Libp2pNode, remoteLibp2p: Libp2pNode + let resolver: sinon.SinonStub<[Multiaddr], Promise> + + beforeEach(async () => { + resolver = sinon.stub<[Multiaddr], Promise>(); + + [libp2p, remoteLibp2p] = await Promise.all([ + createNode({ + config: createBaseOptions({ + addresses: { + listen: [`${relayAddr.toString()}/p2p-circuit`] + }, + connectionManager: { + autoDial: false + }, + relay: { + enabled: true, + hop: { + enabled: false + } + }, + dialer: { + resolvers: { + dnsaddr: resolver + } + } + }), + started: true + }), + createNode({ + config: createBaseOptions({ + addresses: { + listen: [`${relayAddr.toString()}/p2p-circuit`] + }, + connectionManager: { + autoDial: false + }, + relay: { + enabled: true, + hop: { + enabled: false + } + }, + dialer: { + resolvers: { + dnsaddr: resolver + } + } + }), + started: true + }) + ]) + }) + + afterEach(async () => { + sinon.restore() + await Promise.all([libp2p, remoteLibp2p].map(async n => await n.stop())) + }) + + it('resolves dnsaddr to ws local address', async () => { + const remoteId = remoteLibp2p.peerId + const dialAddr = new Multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId.toString()}`) + const relayedAddrFetched = new Multiaddr(relayedAddr(remoteId)) + + // Transport spy + const transport = getTransport(libp2p, Circuit.prototype[Symbol.toStringTag]) + const transportDialSpy = sinon.spy(transport, 'dial') + + // Resolver stub + resolver.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 = transportDialSpy.firstCall.args + expect(dialArgs[0].equals(relayedAddrFetched)).to.eql(true) + }) + + it('resolves a dnsaddr recursively', async () => { + const remoteId = remoteLibp2p.peerId + const dialAddr = new Multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId.toString()}`) + const relayedAddrFetched = new Multiaddr(relayedAddr(remoteId)) + + // Transport spy + const transport = getTransport(libp2p, Circuit.prototype[Symbol.toStringTag]) + const transportDialSpy = sinon.spy(transport, 'dial') + + // Resolver stub + let firstCall = false + resolver.callsFake(async () => { + if (!firstCall) { + firstCall = true + // Return an array of dnsaddr + return await Promise.resolve(getDnsaddrStub(remoteId)) + } + return await 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 = transportDialSpy.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 + const dialAddr = new Multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId.toString()}`) + + // Stub resolver + const dnsMa = new Multiaddr(`/dns4/ams-1.remote.libp2p.io/tcp/443/wss/p2p/${remoteId.toString()}`) + resolver.returns(Promise.resolve([ + `${dnsMa.toString()}` + ])) + + const deferred = pDefer() + + // Stub transport + const transport = getTransport(libp2p, WebSockets.prototype[Symbol.toStringTag]) + const stubTransport = sinon.stub(transport, 'dial') + stubTransport.callsFake(async (multiaddr) => { + expect(multiaddr.equals(dnsMa)).to.equal(true) + + deferred.resolve() + + return mockConnection(mockMultiaddrConnection(mockDuplex(), peerIdFromString(multiaddr.getPeerId() ?? ''))) + }) + + void libp2p.dial(dialAddr) + + await deferred.promise + }) + + it('resolves a dnsaddr recursively not failing if one address fails to resolve', async () => { + const remoteId = remoteLibp2p.peerId + const dialAddr = new Multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId.toString()}`) + const relayedAddrFetched = new Multiaddr(relayedAddr(remoteId)) + + // Transport spy + const transport = getTransport(libp2p, Circuit.prototype[Symbol.toStringTag]) + const transportDialSpy = sinon.spy(transport, 'dial') + + // Resolver stub + resolver.onCall(0).callsFake(async () => await Promise.resolve(getDnsaddrStub(remoteId))) + resolver.onCall(1).callsFake(async () => await Promise.reject(new Error())) + resolver.callsFake(async () => await 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 = transportDialSpy.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 + const dialAddr = new Multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId.toString()}`) + + // Stub resolver + resolver.returns(Promise.reject(new Error())) + + // Stub transport + const transport = getTransport(libp2p, WebSockets.prototype[Symbol.toStringTag]) + 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) + }) +}) + +function getTransport (libp2p: Libp2pNode, tag: string) { + const transport = libp2p.components.getTransportManager().getTransports().find(t => { + return t[Symbol.toStringTag] === tag + }) + + if (transport != null) { + return transport + } + + throw new Error(`No transport found for ${tag}`) +} diff --git a/test/fetch/fetch.node.js b/test/fetch/fetch.node.ts similarity index 72% rename from test/fetch/fetch.node.js rename to test/fetch/fetch.node.ts index da78495951..2da2cd8118 100644 --- a/test/fetch/fetch.node.js +++ b/test/fetch/fetch.node.ts @@ -1,46 +1,45 @@ -'use strict' /* eslint-env mocha */ -const { expect } = require('aegir/utils/chai') -const Libp2p = require('../../src') -const TCP = require('libp2p-tcp') -const Mplex = require('libp2p-mplex') -const { NOISE } = require('@chainsafe/libp2p-noise') -const MDNS = require('libp2p-mdns') -const { createPeerId } = require('../utils/creators/peer') -const { codes } = require('../../src/errors') -const { Multiaddr } = require('multiaddr') - -async function createLibp2pNode (peerId) { - return await Libp2p.create({ +import { expect } from 'aegir/utils/chai.js' +import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { NOISE } from '@chainsafe/libp2p-noise' +import { createPeerId } from '../utils/creators/peer.js' +import { codes } from '../../src/errors.js' +import type { PeerId } from '@libp2p/interfaces/peer-id' + +async function createNode (peerId: PeerId) { + return await createLibp2pNode({ peerId, addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - modules: { - transport: [TCP], - streamMuxer: [Mplex], - connEncryption: [NOISE], - peerDiscovery: [MDNS] - } + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] }) } describe('Fetch', () => { - /** @type {Libp2p} */ - let sender - /** @type {Libp2p} */ - let receiver + let sender: Libp2pNode + let receiver: Libp2pNode const PREFIX_A = '/moduleA/' const PREFIX_B = '/moduleB/' const DATA_A = { foobar: 'hello world' } const DATA_B = { foobar: 'goodnight moon' } - const generateLookupFunction = function (prefix, data) { - return async function (key) { + const generateLookupFunction = function (prefix: string, data: Record) { + return async function (key: string): Promise { key = key.slice(prefix.length) // strip prefix from key const val = data[key] - if (val) { + if (val != null) { return (new TextEncoder()).encode(val) } return null @@ -48,16 +47,17 @@ describe('Fetch', () => { } beforeEach(async () => { - const [peerIdA, peerIdB] = await createPeerId({ number: 2 }) - sender = await createLibp2pNode(peerIdA) - receiver = await createLibp2pNode(peerIdB) + const peerIdA = await createPeerId() + const peerIdB = await createPeerId() + sender = await createNode(peerIdA) + receiver = await createNode(peerIdB) await sender.start() await receiver.start() await Promise.all([ - ...sender.multiaddrs.map(addr => receiver.dial(addr.encapsulate(new Multiaddr(`/p2p/${sender.peerId}`)))), - ...receiver.multiaddrs.map(addr => sender.dial(addr.encapsulate(new Multiaddr(`/p2p/${receiver.peerId}`)))) + ...sender.getMultiaddrs().map(async addr => await receiver.dial(addr)), + ...receiver.getMultiaddrs().map(async addr => await sender.dial(addr)) ]) }) @@ -73,6 +73,11 @@ describe('Fetch', () => { receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) const rawData = await sender.fetch(receiver.peerId, '/moduleA/foobar') + + if (rawData == null) { + throw new Error('Value was not found') + } + const value = (new TextDecoder()).decode(rawData) expect(value).to.equal('hello world') }) @@ -82,12 +87,22 @@ describe('Fetch', () => { receiver.fetchService.registerLookupFunction(PREFIX_B, generateLookupFunction(PREFIX_B, DATA_B)) const rawDataA = await sender.fetch(receiver.peerId, '/moduleA/foobar') + + if (rawDataA == null) { + throw new Error('Value was not found') + } + const valueA = (new TextDecoder()).decode(rawDataA) expect(valueA).to.equal('hello world') // Different lookup functions can be registered on different prefixes, and have different // values for the same key underneath the different prefix. const rawDataB = await sender.fetch(receiver.peerId, '/moduleB/foobar') + + if (rawDataB == null) { + throw new Error('Value was not found') + } + const valueB = (new TextDecoder()).decode(rawDataB) expect(valueB).to.equal('goodnight moon') }) @@ -117,6 +132,11 @@ describe('Fetch', () => { const lookupFunction = generateLookupFunction(PREFIX_A, DATA_A) receiver.fetchService.registerLookupFunction(PREFIX_A, lookupFunction) const rawDataA = await sender.fetch(receiver.peerId, '/moduleA/foobar') + + if (rawDataA == null) { + throw new Error('Value was not found') + } + const valueA = (new TextDecoder()).decode(rawDataA) expect(valueA).to.equal('hello world') @@ -130,6 +150,11 @@ describe('Fetch', () => { const lookupFunction = generateLookupFunction(PREFIX_A, DATA_A) receiver.fetchService.registerLookupFunction(PREFIX_A, lookupFunction) const rawDataA = await sender.fetch(receiver.peerId, '/moduleA/foobar') + + if (rawDataA == null) { + throw new Error('Value was not found') + } + const valueA = (new TextDecoder()).decode(rawDataA) expect(valueA).to.equal('hello world') @@ -143,12 +168,22 @@ describe('Fetch', () => { const lookupFunction = generateLookupFunction(PREFIX_A, DATA_A) receiver.fetchService.registerLookupFunction(PREFIX_A, lookupFunction) const rawDataA = await sender.fetch(receiver.peerId, '/moduleA/foobar') + + if (rawDataA == null) { + throw new Error('Value was not found') + } + const valueA = (new TextDecoder()).decode(rawDataA) expect(valueA).to.equal('hello world') - receiver.fetchService.unregisterLookupFunction(PREFIX_A, () => {}) + receiver.fetchService.unregisterLookupFunction(PREFIX_A, async () => { return null }) const rawDataB = await sender.fetch(receiver.peerId, '/moduleA/foobar') + + if (rawDataB == null) { + throw new Error('Value was not found') + } + const valueB = (new TextDecoder()).decode(rawDataB) expect(valueB).to.equal('hello world') }) diff --git a/test/fixtures/browser.js b/test/fixtures/browser.ts similarity index 52% rename from test/fixtures/browser.js rename to test/fixtures/browser.ts index 8bcec6a116..6e0a56529b 100644 --- a/test/fixtures/browser.js +++ b/test/fixtures/browser.ts @@ -1,7 +1,6 @@ -'use strict' -const { Multiaddr } = require('multiaddr') +import { Multiaddr } from '@multiformats/multiaddr' -module.exports.MULTIADDRS_WEBSOCKETS = [ +export const MULTIADDRS_WEBSOCKETS = [ new Multiaddr('/ip4/127.0.0.1/tcp/15001/ws/p2p/12D3KooWHFKTMzwerBtsVmtz4ZZEQy2heafxzWw6wNn5PPYkBxJ5') ] diff --git a/test/fixtures/peers.js b/test/fixtures/peers.ts similarity index 98% rename from test/fixtures/peers.js rename to test/fixtures/peers.ts index 27150e5e84..656e791085 100644 --- a/test/fixtures/peers.js +++ b/test/fixtures/peers.ts @@ -1,6 +1,4 @@ -'use strict' - -module.exports = [{ +export default [{ id: '12D3KooWNvSZnPi3RrhrTwEY4LuuBeB6K6facKUCJcyWG1aoDd2p', privKey: 'CAESYHyCgD+3HtEHm6kzPO6fuwP+BAr/PxfJKlvAOWhc/IqAwrZjCNn0jz93sSl81cP6R6x/g+iVYmR5Wxmn4ZtzJFnCtmMI2fSPP3exKXzVw/pHrH+D6JViZHlbGafhm3MkWQ==', pubKey: 'CAESIMK2YwjZ9I8/d7EpfNXD+kesf4PolWJkeVsZp+GbcyRZ' diff --git a/test/fixtures/swarm.key.js b/test/fixtures/swarm.key.ts similarity index 60% rename from test/fixtures/swarm.key.js rename to test/fixtures/swarm.key.ts index 184f47b6cb..713318010c 100644 --- a/test/fixtures/swarm.key.js +++ b/test/fixtures/swarm.key.ts @@ -1,5 +1,4 @@ -'use strict' -module.exports = '/key/swarm/psk/1.0.0/\n' + +export default '/key/swarm/psk/1.0.0/\n' + '/base16/\n' + '411f0a244cbbc25ecbb2b070d00a1832516ded521eb3ee3aa13189efe2e2b9a2' diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js deleted file mode 100644 index 8b8269d558..0000000000 --- a/test/identify/index.spec.js +++ /dev/null @@ -1,605 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') - -const { EventEmitter } = require('events') -const PeerId = require('peer-id') -const duplexPair = require('it-pair/duplex') -const { Multiaddr } = require('multiaddr') -const pWaitFor = require('p-wait-for') -const { toString: unit8ArrayToString } = require('uint8arrays/to-string') -const { codes: Errors } = require('../../src/errors') -const IdentifyService = require('../../src/identify') -const multicodecs = IdentifyService.multicodecs -const Peers = require('../fixtures/peers') -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 AddressManager = require('../../src/address-manager') -const { MemoryDatastore } = require('datastore-core/memory') -const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser') -const { mockConnectionGater } = require('../utils/mock-connection-gater') -const remoteAddr = MULTIADDRS_WEBSOCKETS[0] -const listenMaddrs = [new Multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] - -describe('Identify', () => { - const connectionGater = mockConnectionGater() - let localPeer, localPeerStore, localAddressManager - let remotePeer, remotePeerStore, remoteAddressManager - 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, - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - await localPeerStore.protoBook.set(localPeer, protocols) - - remotePeerStore = new PeerStore({ - peerId: remotePeer, - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - await remotePeerStore.protoBook.set(remotePeer, protocols) - - localAddressManager = new AddressManager(localPeer) - remoteAddressManager = new AddressManager(remotePeer) - }) - - afterEach(() => { - sinon.restore() - }) - - it('should be able to identify another peer', async () => { - const localIdentify = new IdentifyService({ - libp2p: { - peerId: localPeer, - connectionManager: new EventEmitter(), - peerStore: localPeerStore, - multiaddrs: listenMaddrs, - isStarted: () => true, - _options: { host: {} }, - _config: { protocolPrefix: 'ipfs' } - } - }) - const remoteIdentify = new IdentifyService({ - libp2p: { - peerId: remotePeer, - connectionManager: new EventEmitter(), - peerStore: remotePeerStore, - multiaddrs: listenMaddrs, - isStarted: () => true, - _options: { host: {} }, - _config: { protocolPrefix: 'ipfs' } - } - }) - - const observedAddr = new Multiaddr('/ip4/127.0.0.1/tcp/1234') - const localConnectionMock = { newStream: () => {}, remotePeer } - const remoteConnectionMock = { remoteAddr: observedAddr } - - const [local, remote] = duplexPair() - sinon.stub(localConnectionMock, 'newStream').returns({ stream: local, protocol: multicodecs.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), - remoteIdentify.handleMessage({ - connection: remoteConnectionMock, - stream: remote, - protocol: multicodecs.IDENTIFY - }) - ]) - - expect(localIdentify.peerStore.addressBook.consumePeerRecord.callCount).to.equal(1) - expect(localIdentify.peerStore.protoBook.set.callCount).to.equal(1) - - // Validate the remote peer gets updated in the peer store - const addresses = await localIdentify.peerStore.addressBook.get(remotePeer) - expect(addresses).to.exist() - expect(addresses).have.lengthOf(listenMaddrs.length) - expect(addresses.map((a) => a.multiaddr)[0].equals(listenMaddrs[0])) - expect(addresses.map((a) => a.isCertified)[0]).to.eql(true) - }) - - // LEGACY - it('should be able to identify another peer with no certified peer records support', async () => { - const agentVersion = `js-libp2p/${pkg.version}` - const localIdentify = new IdentifyService({ - libp2p: { - peerId: localPeer, - connectionManager: new EventEmitter(), - addressManager: localAddressManager, - peerStore: localPeerStore, - multiaddrs: listenMaddrs, - isStarted: () => true, - _options: { host: { agentVersion } }, - _config: { protocolPrefix: 'ipfs' } - } - }) - - const remoteIdentify = new IdentifyService({ - libp2p: { - peerId: remotePeer, - connectionManager: new EventEmitter(), - addressManager: remoteAddressManager, - peerStore: remotePeerStore, - multiaddrs: listenMaddrs, - isStarted: () => true, - _options: { host: { agentVersion } }, - _config: { protocolPrefix: 'ipfs' } - } - }) - - const observedAddr = new Multiaddr('/ip4/127.0.0.1/tcp/1234') - const localConnectionMock = { newStream: () => {}, remotePeer } - const remoteConnectionMock = { remoteAddr: observedAddr } - - const [local, remote] = duplexPair() - sinon.stub(localConnectionMock, 'newStream').returns({ stream: local, protocol: multicodecs.IDENTIFY }) - sinon.stub(Envelope, 'openAndCertify').throws() - - sinon.spy(localIdentify.peerStore.addressBook, 'set') - sinon.spy(localIdentify.peerStore.protoBook, 'set') - sinon.spy(localIdentify.peerStore.metadataBook, 'setValue') - - // Run identify - await Promise.all([ - localIdentify.identify(localConnectionMock), - remoteIdentify.handleMessage({ - connection: remoteConnectionMock, - stream: remote, - protocol: multicodecs.IDENTIFY - }) - ]) - - expect(localIdentify.peerStore.addressBook.set.callCount).to.equal(1) - expect(localIdentify.peerStore.protoBook.set.callCount).to.equal(1) - - const metadataArgs = localIdentify.peerStore.metadataBook.setValue.firstCall.args - expect(metadataArgs[0].id.bytes).to.equal(remotePeer.bytes) - expect(metadataArgs[1]).to.equal('AgentVersion') - expect(unit8ArrayToString(metadataArgs[2])).to.equal(agentVersion) - - // 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) - expect(call.args[1]).to.exist() - expect(call.args[1]).have.lengthOf(listenMaddrs.length) - expect(call.args[1][0].equals(listenMaddrs[0])) - }) - - it('should throw if identified peer is the wrong peer', async () => { - const localIdentify = new IdentifyService({ - libp2p: { - peerId: localPeer, - connectionManager: new EventEmitter(), - peerStore: localPeerStore, - multiaddrs: [], - _options: { host: {} }, - _config: { protocolPrefix: 'ipfs' } - } - }) - const remoteIdentify = new IdentifyService({ - libp2p: { - peerId: remotePeer, - connectionManager: new EventEmitter(), - peerStore: remotePeerStore, - multiaddrs: [], - _options: { host: {} }, - _config: { protocolPrefix: 'ipfs' } - } - }) - - const observedAddr = new Multiaddr('/ip4/127.0.0.1/tcp/1234') - const localConnectionMock = { newStream: () => {}, remotePeer: localPeer } - const remoteConnectionMock = { remoteAddr: observedAddr } - - const [local, remote] = duplexPair() - sinon.stub(localConnectionMock, 'newStream').returns({ stream: local, protocol: multicodecs.IDENTIFY }) - - // Run identify - const identifyPromise = Promise.all([ - localIdentify.identify(localConnectionMock, localPeer), - remoteIdentify.handleMessage({ - connection: remoteConnectionMock, - stream: remote, - protocol: multicodecs.IDENTIFY - }) - ]) - - await expect(identifyPromise) - .to.eventually.be.rejected() - .and.to.have.property('code', Errors.ERR_INVALID_PEER) - }) - - it('should store host data and protocol version into metadataBook', async () => { - const agentVersion = 'js-project/1.0.0' - const peerStore = new PeerStore({ - peerId: localPeer, - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - - sinon.spy(peerStore.metadataBook, 'setValue') - - const service = new IdentifyService({ // eslint-disable-line no-new - libp2p: { - peerId: localPeer, - connectionManager: new EventEmitter(), - peerStore, - multiaddrs: listenMaddrs, - _options: { - host: { - agentVersion - } - }, - _config: { protocolPrefix: 'ipfs' } - }, - protocols - }) - - await service.start() - - expect(peerStore.metadataBook.setValue.callCount).to.eql(2) - - const storedAgentVersion = await peerStore.metadataBook.getValue(localPeer, 'AgentVersion') - const storedProtocolVersion = await peerStore.metadataBook.getValue(localPeer, 'ProtocolVersion') - - expect(agentVersion).to.eql(unit8ArrayToString(storedAgentVersion)) - expect(storedProtocolVersion).to.exist() - - await service.stop() - }) - - 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'].sort() - const connectionManager = new EventEmitter() - connectionManager.getConnection = () => { } - - const localPeerStore = new PeerStore({ - peerId: localPeer, - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - await localPeerStore.protoBook.set(localPeer, storedProtocols) - - const localIdentify = new IdentifyService({ - libp2p: { - peerId: localPeer, - connectionManager: new EventEmitter(), - peerStore: localPeerStore, - multiaddrs: listenMaddrs, - isStarted: () => true, - _options: { host: {} }, - _config: { protocolPrefix: 'ipfs' } - } - }) - - const remotePeerStore = new PeerStore({ - peerId: remotePeer, - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - await remotePeerStore.protoBook.set(remotePeer, storedProtocols) - - const remoteIdentify = new IdentifyService({ - libp2p: { - peerId: remotePeer, - connectionManager, - peerStore: remotePeerStore, - multiaddrs: [], - isStarted: () => true, - _options: { host: {} }, - _config: { protocolPrefix: 'ipfs' } - } - }) - - // Setup peer protocols and multiaddrs - const localProtocols = new Set(storedProtocols) - const localConnectionMock = { newStream: () => { } } - const remoteConnectionMock = { remotePeer: localPeer } - - const [local, remote] = duplexPair() - sinon.stub(localConnectionMock, 'newStream').returns({ stream: local, protocol: multicodecs.IDENTIFY_PUSH }) - - 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]), - remoteIdentify.handleMessage({ - connection: remoteConnectionMock, - stream: remote, - protocol: multicodecs.IDENTIFY_PUSH - }) - ]) - - expect(remoteIdentify.peerStore.addressBook.consumePeerRecord.callCount).to.equal(2) - expect(remoteIdentify.peerStore.protoBook.set.callCount).to.equal(1) - - const addresses = await localIdentify.peerStore.addressBook.get(localPeer) - expect(addresses).to.exist() - expect(addresses).have.lengthOf(listenMaddrs.length) - expect(addresses.map((a) => a.multiaddr)).to.eql(listenMaddrs) - - const [peerId2, protocols] = remoteIdentify.peerStore.protoBook.set.firstCall.args - expect(peerId2.bytes).to.eql(localPeer.bytes) - expect(protocols).to.eql(Array.from(localProtocols)) - }) - - // 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'].sort() - const connectionManager = new EventEmitter() - connectionManager.getConnection = () => { } - - const localPeerStore = new PeerStore({ - peerId: localPeer, - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - await localPeerStore.protoBook.set(localPeer, storedProtocols) - - const localIdentify = new IdentifyService({ - libp2p: { - peerId: localPeer, - connectionManager: new EventEmitter(), - peerStore: localPeerStore, - multiaddrs: listenMaddrs, - isStarted: () => true, - _options: { host: {} }, - _config: { protocolPrefix: 'ipfs' } - } - }) - - const remotePeerStore = new PeerStore({ - peerId: remotePeer, - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - await remotePeerStore.protoBook.set(remotePeer, storedProtocols) - - const remoteIdentify = new IdentifyService({ - libp2p: { - peerId: remotePeer, - connectionManager, - peerStore: remotePeerStore, - multiaddrs: [], - _options: { host: {} }, - _config: { protocolPrefix: 'ipfs' }, - isStarted: () => true - } - }) - - // Setup peer protocols and multiaddrs - const localProtocols = new Set(storedProtocols) - const localConnectionMock = { newStream: () => {} } - const remoteConnectionMock = { remotePeer: localPeer } - - const [local, remote] = duplexPair() - sinon.stub(localConnectionMock, 'newStream').returns({ stream: local, protocol: multicodecs.IDENTIFY_PUSH }) - sinon.stub(Envelope, 'openAndCertify').throws() - - sinon.spy(remoteIdentify.peerStore.addressBook, 'set') - sinon.spy(remoteIdentify.peerStore.protoBook, 'set') - - // Run identify - await Promise.all([ - localIdentify.push([localConnectionMock]), - remoteIdentify.handleMessage({ - connection: remoteConnectionMock, - stream: remote, - protocol: multicodecs.IDENTIFY_PUSH - }) - ]) - - expect(remoteIdentify.peerStore.addressBook.set.callCount).to.equal(1) - expect(remoteIdentify.peerStore.protoBook.set.callCount).to.equal(1) - - const [peerId, multiaddrs] = remoteIdentify.peerStore.addressBook.set.firstCall.args - expect(peerId.bytes).to.eql(localPeer.bytes) - expect(multiaddrs).to.eql(listenMaddrs) - - const [peerId2, protocols] = remoteIdentify.peerStore.protoBook.set.firstCall.args - expect(peerId2.bytes).to.eql(localPeer.bytes) - expect(protocols).to.eql(Array.from(localProtocols)) - }) - }) - - describe('libp2p.dialer.identifyService', () => { - let peerId - let libp2p - let remoteLibp2p - - before(async () => { - peerId = await PeerId.createFromJSON(Peers[0]) - }) - - afterEach(async () => { - sinon.restore() - libp2p && await libp2p.stop() - libp2p = null - }) - - after(async () => { - remoteLibp2p && await remoteLibp2p.stop() - }) - - it('should run identify automatically after connecting', async () => { - libp2p = new Libp2p({ - ...baseOptions, - peerId - }) - - await libp2p.start() - - sinon.spy(libp2p.identifyService, 'identify') - const peerStoreSpyConsumeRecord = sinon.spy(libp2p.peerStore.addressBook, 'consumePeerRecord') - const peerStoreSpyAdd = sinon.spy(libp2p.peerStore.addressBook, 'add') - - const connection = await libp2p.dialer.connectToPeer(remoteAddr) - expect(connection).to.exist() - - // Wait for peer store to be updated - // 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 - await pWaitFor(() => connection.streams.length === 0) - await connection.close() - }) - - it('should store remote agent and protocol versions in metadataBook after connecting', async () => { - libp2p = new Libp2p({ - ...baseOptions, - peerId - }) - - await libp2p.start() - - sinon.spy(libp2p.identifyService, 'identify') - const peerStoreSpyConsumeRecord = sinon.spy(libp2p.peerStore.addressBook, 'consumePeerRecord') - const peerStoreSpyAdd = sinon.spy(libp2p.peerStore.addressBook, 'add') - - const connection = await libp2p.dialer.connectToPeer(remoteAddr) - expect(connection).to.exist() - - // Wait for peer store to be updated - // 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 - await pWaitFor(() => connection.streams.length === 0) - await connection.close() - - const remotePeer = PeerId.createFromB58String(remoteAddr.getPeerId()) - - const storedAgentVersion = libp2p.peerStore.metadataBook.getValue(remotePeer, 'AgentVersion') - const storedProtocolVersion = libp2p.peerStore.metadataBook.getValue(remotePeer, 'ProtocolVersion') - - expect(storedAgentVersion).to.exist() - expect(storedProtocolVersion).to.exist() - }) - - it('should push protocol 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) - - await libp2p.handle('/echo/2.0.0', () => {}) - await libp2p.unhandle('/echo/2.0.0') - - // the protocol change event listener in the identity service is async - await pWaitFor(() => libp2p.identifyService.push.callCount === 2) - - // Verify the remote peer is notified of both changes - expect(libp2p.identifyService.push.callCount).to.equal(2) - - 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) - }) - - it('should store host data and protocol version into metadataBook', async () => { - const agentVersion = 'js-project/1.0.0' - - libp2p = new Libp2p({ - ...baseOptions, - peerId, - host: { - agentVersion - } - }) - await libp2p.start() - - const storedAgentVersion = await libp2p.peerStore.metadataBook.getValue(localPeer, 'AgentVersion') - const storedProtocolVersion = await libp2p.peerStore.metadataBook.getValue(localPeer, 'ProtocolVersion') - - expect(agentVersion).to.eql(unit8ArrayToString(storedAgentVersion)) - expect(storedProtocolVersion).to.exist() - }) - - 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) - - await libp2p.peerStore.addressBook.add(libp2p.peerId, [new Multiaddr('/ip4/180.0.0.1/tcp/15001/ws')]) - - // the protocol change event listener in the identity service is async - await pWaitFor(() => libp2p.identifyService.push.callCount === 1) - - // 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) - }) - }) -}) diff --git a/test/identify/index.spec.ts b/test/identify/index.spec.ts new file mode 100644 index 0000000000..0297a5aff6 --- /dev/null +++ b/test/identify/index.spec.ts @@ -0,0 +1,619 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import sinon from 'sinon' +import { Multiaddr } from '@multiformats/multiaddr' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { codes } from '../../src/errors.js' +import { IdentifyService, Message } from '../../src/identify/index.js' +import Peers from '../fixtures/peers.js' +import { createLibp2pNode } from '../../src/libp2p.js' +import { PersistentPeerStore } from '@libp2p/peer-store' +import { createBaseOptions } from '../utils/base-options.browser.js' +import { DefaultAddressManager } from '../../src/address-manager/index.js' +import { MemoryDatastore } from 'datastore-core/memory' +import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' +import * as lp from 'it-length-prefixed' +import drain from 'it-drain' +import { pipe } from 'it-pipe' +import { mockConnectionGater, mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-compliance-tests/mocks' +import { createFromJSON } from '@libp2p/peer-id-factory' +import { Components } from '@libp2p/interfaces/components' +import { PeerRecordUpdater } from '../../src/peer-record-updater.js' +import { + MULTICODEC_IDENTIFY, + MULTICODEC_IDENTIFY_PUSH +} from '../../src/identify/consts.js' +import { DefaultConnectionManager } from '../../src/connection-manager/index.js' +import { DefaultTransportManager } from '../../src/transport-manager.js' +import { CustomEvent, Startable } from '@libp2p/interfaces' +import delay from 'delay' +import pWaitFor from 'p-wait-for' +import { peerIdFromString } from '@libp2p/peer-id' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { Libp2pNode } from '../../src/libp2p.js' +import { pEvent } from 'p-event' + +const listenMaddrs = [new Multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] + +const defaultInit = { + protocolPrefix: 'ipfs', + host: { + agentVersion: 'v1.0.0' + } +} + +const protocols = [MULTICODEC_IDENTIFY, MULTICODEC_IDENTIFY_PUSH] + +async function createComponents (index: number, services: Startable[]) { + const peerId = await createFromJSON(Peers[index]) + + const components = new Components({ + peerId, + datastore: new MemoryDatastore(), + registrar: mockRegistrar(), + upgrader: mockUpgrader(), + connectionGater: mockConnectionGater() + }) + const peerStore = new PersistentPeerStore(components, { + addressFilter: components.getConnectionGater().filterMultiaddrForPeer + }) + components.setPeerStore(peerStore) + components.setAddressManager(new DefaultAddressManager(components, { + announce: listenMaddrs.map(ma => ma.toString()) + })) + + const connectionManager = new DefaultConnectionManager(components) + services.push(connectionManager) + components.setConnectionManager(connectionManager) + + const transportManager = new DefaultTransportManager(components) + services.push(transportManager) + components.setTransportManager(transportManager) + + await peerStore.protoBook.set(peerId, protocols) + + return components +} + +describe('Identify', () => { + let localComponents: Components + let remoteComponents: Components + + let localPeerRecordUpdater: PeerRecordUpdater + let remotePeerRecordUpdater: PeerRecordUpdater + let services: Startable[] + + beforeEach(async () => { + services = [] + + localComponents = await createComponents(0, services) + remoteComponents = await createComponents(1, services) + + localPeerRecordUpdater = new PeerRecordUpdater(localComponents) + remotePeerRecordUpdater = new PeerRecordUpdater(remoteComponents) + + await Promise.all( + services.map(s => s.start()) + ) + }) + + afterEach(async () => { + sinon.restore() + + await Promise.all( + services.map(s => s.stop()) + ) + }) + + it('should be able to identify another peer', async () => { + const localIdentify = new IdentifyService(localComponents, defaultInit) + const remoteIdentify = new IdentifyService(remoteComponents, defaultInit) + + await localIdentify.start() + await remoteIdentify.start() + + const [localToRemote] = connectionPair({ + peerId: localComponents.getPeerId(), + registrar: localComponents.getRegistrar() + }, { + peerId: remoteComponents.getPeerId(), + registrar: remoteComponents.getRegistrar() + }) + + const localAddressBookConsumePeerRecordSpy = sinon.spy(localComponents.getPeerStore().addressBook, 'consumePeerRecord') + const localProtoBookSetSpy = sinon.spy(localComponents.getPeerStore().protoBook, 'set') + + // Make sure the remote peer has a peer record to share during identify + await remotePeerRecordUpdater.update() + + // Run identify + await localIdentify.identify(localToRemote) + + expect(localAddressBookConsumePeerRecordSpy.callCount).to.equal(1) + expect(localProtoBookSetSpy.callCount).to.equal(1) + + // Validate the remote peer gets updated in the peer store + const addresses = await localComponents.getPeerStore().addressBook.get(remoteComponents.getPeerId()) + expect(addresses).to.exist() + + expect(addresses).have.lengthOf(listenMaddrs.length) + expect(addresses.map((a) => a.multiaddr)[0].equals(listenMaddrs[0])) + expect(addresses.map((a) => a.isCertified)[0]).to.be.true() + }) + + // LEGACY + it('should be able to identify another peer with no certified peer records support', async () => { + const agentVersion = 'js-libp2p/5.0.0' + const localIdentify = new IdentifyService(localComponents, { + protocolPrefix: 'ipfs', + host: { + agentVersion: agentVersion + } + }) + await localIdentify.start() + const remoteIdentify = new IdentifyService(remoteComponents, { + protocolPrefix: 'ipfs', + host: { + agentVersion: agentVersion + } + }) + await remoteIdentify.start() + + const [localToRemote] = connectionPair({ + peerId: localComponents.getPeerId(), + registrar: localComponents.getRegistrar() + }, { + peerId: remoteComponents.getPeerId(), + registrar: remoteComponents.getRegistrar() + }) + + sinon.stub(localComponents.getPeerStore().addressBook, 'consumePeerRecord').throws() + + const localProtoBookSetSpy = sinon.spy(localComponents.getPeerStore().protoBook, 'set') + + // Run identify + await localIdentify.identify(localToRemote) + + expect(localProtoBookSetSpy.callCount).to.equal(1) + + // Validate the remote peer gets updated in the peer store + const addresses = await localComponents.getPeerStore().addressBook.get(remoteComponents.getPeerId()) + expect(addresses).to.exist() + + expect(addresses).have.lengthOf(listenMaddrs.length) + expect(addresses.map((a) => a.multiaddr)[0].equals(listenMaddrs[0])) + expect(addresses.map((a) => a.isCertified)[0]).to.be.false() + }) + + it('should throw if identified peer is the wrong peer', async () => { + const localIdentify = new IdentifyService(localComponents, defaultInit) + const remoteIdentify = new IdentifyService(remoteComponents, defaultInit) + + await localIdentify.start() + await remoteIdentify.start() + + const [localToRemote] = connectionPair({ + peerId: localComponents.getPeerId(), + registrar: localComponents.getRegistrar() + }, { + peerId: remoteComponents.getPeerId(), + registrar: remoteComponents.getRegistrar() + }) + + // send an invalid message + await remoteComponents.getRegistrar().unhandle(MULTICODEC_IDENTIFY) + await remoteComponents.getRegistrar().handle(MULTICODEC_IDENTIFY, (data) => { + void Promise.resolve().then(async () => { + const { connection, stream } = data + const signedPeerRecord = await remoteComponents.getPeerStore().addressBook.getRawEnvelope(remoteComponents.getPeerId()) + + const message = Message.Identify.encode({ + protocolVersion: '123', + agentVersion: '123', + // send bad public key + publicKey: localComponents.getPeerId().publicKey ?? new Uint8Array(0), + listenAddrs: [], + signedPeerRecord, + observedAddr: connection.remoteAddr.bytes, + protocols: [] + }).finish() + + await pipe( + [message], + lp.encode(), + stream, + drain + ) + }) + }) + + // Run identify + await expect(localIdentify.identify(localToRemote)) + .to.eventually.be.rejected() + .and.to.have.property('code', codes.ERR_INVALID_PEER) + }) + + it('should store own host data and protocol version into metadataBook on start', async () => { + const agentVersion = 'js-project/1.0.0' + const localIdentify = new IdentifyService(localComponents, { + protocolPrefix: 'ipfs', + host: { + agentVersion + } + }) + + await expect(localComponents.getPeerStore().metadataBook.getValue(localComponents.getPeerId(), 'AgentVersion')) + .to.eventually.be.undefined() + await expect(localComponents.getPeerStore().metadataBook.getValue(localComponents.getPeerId(), 'ProtocolVersion')) + .to.eventually.be.undefined() + + await localIdentify.start() + + await expect(localComponents.getPeerStore().metadataBook.getValue(localComponents.getPeerId(), 'AgentVersion')) + .to.eventually.deep.equal(uint8ArrayFromString(agentVersion)) + await expect(localComponents.getPeerStore().metadataBook.getValue(localComponents.getPeerId(), 'ProtocolVersion')) + .to.eventually.be.ok() + + await localIdentify.stop() + }) + + describe('push', () => { + it('should be able to push identify updates to another peer', async () => { + const localIdentify = new IdentifyService(localComponents, defaultInit) + const remoteIdentify = new IdentifyService(remoteComponents, defaultInit) + + await localIdentify.start() + await remoteIdentify.start() + + const [localToRemote, remoteToLocal] = connectionPair({ + peerId: localComponents.getPeerId(), + registrar: localComponents.getRegistrar() + }, { + peerId: remoteComponents.getPeerId(), + registrar: remoteComponents.getRegistrar() + }) + + // ensure connections are registered by connection manager + localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { + detail: localToRemote + })) + remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { + detail: remoteToLocal + })) + + // identify both ways + await localIdentify.identify(localToRemote) + await remoteIdentify.identify(remoteToLocal) + + const updatedProtocol = '/special-new-protocol/1.0.0' + const updatedAddress = new Multiaddr('/ip4/127.0.0.1/tcp/48322') + + // should have protocols but not our new one + const identifiedProtocols = await remoteComponents.getPeerStore().protoBook.get(localComponents.getPeerId()) + expect(identifiedProtocols).to.not.be.empty() + expect(identifiedProtocols).to.not.include(updatedProtocol) + + // should have addresses but not our new one + const identifiedAddresses = await remoteComponents.getPeerStore().addressBook.get(localComponents.getPeerId()) + expect(identifiedAddresses).to.not.be.empty() + expect(identifiedAddresses.map(a => a.multiaddr.toString())).to.not.include(updatedAddress.toString()) + + // update local data - change event will trigger push + await localComponents.getPeerStore().protoBook.add(localComponents.getPeerId(), [updatedProtocol]) + await localComponents.getPeerStore().addressBook.add(localComponents.getPeerId(), [updatedAddress]) + + // needed to update the peer record and send our supported addresses + const addressManager = localComponents.getAddressManager() + addressManager.getAddresses = () => { + return [updatedAddress] + } + + // ensure sequence number of peer record we are about to create is different + await delay(1000) + + // make sure we have a peer record to send + await localPeerRecordUpdater.update() + + // wait for the remote peer store to notice the changes + const eventPromise = pEvent(remoteComponents.getPeerStore(), 'change:multiaddrs') + + // push updated peer record to connections + await localIdentify.pushToPeerStore() + + await eventPromise + + // should have new protocol + const updatedProtocols = await remoteComponents.getPeerStore().protoBook.get(localComponents.getPeerId()) + expect(updatedProtocols).to.not.be.empty() + expect(updatedProtocols).to.include(updatedProtocol) + + // should have new address + const updatedAddresses = await remoteComponents.getPeerStore().addressBook.get(localComponents.getPeerId()) + expect(updatedAddresses.map(a => { + return { + multiaddr: a.multiaddr.toString(), + isCertified: a.isCertified + } + })).to.deep.equal([{ + multiaddr: updatedAddress.toString(), + isCertified: true + }]) + + await localIdentify.stop() + await remoteIdentify.stop() + }) + + // LEGACY + it('should be able to push identify updates to another peer with no certified peer records support', async () => { + const localIdentify = new IdentifyService(localComponents, defaultInit) + const remoteIdentify = new IdentifyService(remoteComponents, defaultInit) + + await localIdentify.start() + await remoteIdentify.start() + + const [localToRemote, remoteToLocal] = connectionPair({ + peerId: localComponents.getPeerId(), + registrar: localComponents.getRegistrar() + }, { + peerId: remoteComponents.getPeerId(), + registrar: remoteComponents.getRegistrar() + }) + + // ensure connections are registered by connection manager + localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { + detail: localToRemote + })) + remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { + detail: remoteToLocal + })) + + // identify both ways + await localIdentify.identify(localToRemote) + await remoteIdentify.identify(remoteToLocal) + + const updatedProtocol = '/special-new-protocol/1.0.0' + const updatedAddress = new Multiaddr('/ip4/127.0.0.1/tcp/48322') + + // should have protocols but not our new one + const identifiedProtocols = await remoteComponents.getPeerStore().protoBook.get(localComponents.getPeerId()) + expect(identifiedProtocols).to.not.be.empty() + expect(identifiedProtocols).to.not.include(updatedProtocol) + + // should have addresses but not our new one + const identifiedAddresses = await remoteComponents.getPeerStore().addressBook.get(localComponents.getPeerId()) + expect(identifiedAddresses).to.not.be.empty() + expect(identifiedAddresses.map(a => a.multiaddr.toString())).to.not.include(updatedAddress.toString()) + + // update local data - change event will trigger push + await localComponents.getPeerStore().protoBook.add(localComponents.getPeerId(), [updatedProtocol]) + await localComponents.getPeerStore().addressBook.add(localComponents.getPeerId(), [updatedAddress]) + + // needed to send our supported addresses + const addressManager = localComponents.getAddressManager() + addressManager.getAddresses = () => { + return [updatedAddress] + } + + // wait until remote peer store notices protocol list update + const waitForUpdate = pEvent(remoteComponents.getPeerStore(), 'change:protocols') + + await localIdentify.pushToPeerStore() + + await waitForUpdate + + // should have new protocol + const updatedProtocols = await remoteComponents.getPeerStore().protoBook.get(localComponents.getPeerId()) + expect(updatedProtocols).to.not.be.empty() + expect(updatedProtocols).to.include(updatedProtocol) + + // should have new address + const updatedAddresses = await remoteComponents.getPeerStore().addressBook.get(localComponents.getPeerId()) + expect(updatedAddresses.map(a => { + return { + multiaddr: a.multiaddr.toString(), + isCertified: a.isCertified + } + })).to.deep.equal([{ + multiaddr: updatedAddress.toString(), + isCertified: false + }]) + + await localIdentify.stop() + await remoteIdentify.stop() + }) + }) + + describe('libp2p.dialer.identifyService', () => { + let peerId: PeerId + let libp2p: Libp2pNode + let remoteLibp2p: Libp2pNode + const remoteAddr = MULTIADDRS_WEBSOCKETS[0] + + before(async () => { + peerId = await createFromJSON(Peers[0]) + }) + + afterEach(async () => { + sinon.restore() + + if (libp2p != null) { + await libp2p.stop() + } + }) + + after(async () => { + if (remoteLibp2p != null) { + await remoteLibp2p.stop() + } + }) + + it('should run identify automatically after connecting', async () => { + libp2p = await createLibp2pNode(createBaseOptions({ + peerId + })) + + await libp2p.start() + + if (libp2p.identifyService == null) { + throw new Error('Identity service was not configured') + } + + const identityServiceIdentifySpy = sinon.spy(libp2p.identifyService, 'identify') + const peerStoreSpyConsumeRecord = sinon.spy(libp2p.peerStore.addressBook, 'consumePeerRecord') + const peerStoreSpyAdd = sinon.spy(libp2p.peerStore.addressBook, 'add') + + const connection = await libp2p.dial(remoteAddr) + expect(connection).to.exist() + + // Wait for peer store to be updated + // Dialer._createDialTarget (add), Identify (consume) + await pWaitFor(() => peerStoreSpyConsumeRecord.callCount === 1 && peerStoreSpyAdd.callCount === 1) + expect(identityServiceIdentifySpy.callCount).to.equal(1) + + // The connection should have no open streams + await pWaitFor(() => connection.streams.length === 0) + await connection.close() + }) + + it('should store remote agent and protocol versions in metadataBook after connecting', async () => { + libp2p = await createLibp2pNode(createBaseOptions({ + peerId + })) + + await libp2p.start() + + if (libp2p.identifyService == null) { + throw new Error('Identity service was not configured') + } + + const identityServiceIdentifySpy = sinon.spy(libp2p.identifyService, 'identify') + const peerStoreSpyConsumeRecord = sinon.spy(libp2p.peerStore.addressBook, 'consumePeerRecord') + const peerStoreSpyAdd = sinon.spy(libp2p.peerStore.addressBook, 'add') + + const connection = await libp2p.dial(remoteAddr) + expect(connection).to.exist() + + // Wait for peer store to be updated + // Dialer._createDialTarget (add), Identify (consume) + await pWaitFor(() => peerStoreSpyConsumeRecord.callCount === 1 && peerStoreSpyAdd.callCount === 1) + expect(identityServiceIdentifySpy.callCount).to.equal(1) + + // The connection should have no open streams + await pWaitFor(() => connection.streams.length === 0) + await connection.close() + + const remotePeer = peerIdFromString(remoteAddr.getPeerId() ?? '') + + const storedAgentVersion = await libp2p.peerStore.metadataBook.getValue(remotePeer, 'AgentVersion') + const storedProtocolVersion = await libp2p.peerStore.metadataBook.getValue(remotePeer, 'ProtocolVersion') + + expect(storedAgentVersion).to.exist() + expect(storedProtocolVersion).to.exist() + }) + + it('should push protocol updates to an already connected peer', async () => { + libp2p = await createLibp2pNode(createBaseOptions({ + peerId + })) + + await libp2p.start() + + if (libp2p.identifyService == null) { + throw new Error('Identity service was not configured') + } + + const identityServiceIdentifySpy = sinon.spy(libp2p.identifyService, 'identify') + const identityServicePushSpy = sinon.spy(libp2p.identifyService, 'push') + + const connection = await libp2p.dial(remoteAddr) + expect(connection).to.exist() + + // Wait for identify to finish + await identityServiceIdentifySpy.firstCall.returnValue + sinon.stub(libp2p, 'isStarted').returns(true) + + await libp2p.handle('/echo/2.0.0', () => {}) + await libp2p.unhandle('/echo/2.0.0') + + // the protocol change event listener in the identity service is async + await pWaitFor(() => identityServicePushSpy.callCount === 2) + + // Verify the remote peer is notified of both changes + expect(identityServicePushSpy.callCount).to.equal(2) + + for (const call of identityServicePushSpy.getCalls()) { + const [connections] = call.args + expect(connections.length).to.equal(1) + expect(connections[0].remotePeer.toString()).to.equal(remoteAddr.getPeerId()) + await call.returnValue + } + + // Verify the streams close + await pWaitFor(() => connection.streams.length === 0) + }) + + it('should store host data and protocol version into metadataBook', async () => { + const agentVersion = 'js-project/1.0.0' + + libp2p = await createLibp2pNode(createBaseOptions({ + peerId, + host: { + agentVersion + } + })) + + await libp2p.start() + + if (libp2p.identifyService == null) { + throw new Error('Identity service was not configured') + } + + const storedAgentVersion = await libp2p.peerStore.metadataBook.getValue(peerId, 'AgentVersion') + const storedProtocolVersion = await libp2p.peerStore.metadataBook.getValue(peerId, 'ProtocolVersion') + + expect(agentVersion).to.equal(uint8ArrayToString(storedAgentVersion ?? new Uint8Array())) + expect(storedProtocolVersion).to.exist() + }) + + it('should push multiaddr updates to an already connected peer', async () => { + libp2p = await createLibp2pNode(createBaseOptions({ + peerId + })) + + await libp2p.start() + + if (libp2p.identifyService == null) { + throw new Error('Identity service was not configured') + } + + const identityServiceIdentifySpy = sinon.spy(libp2p.identifyService, 'identify') + const identityServicePushSpy = sinon.spy(libp2p.identifyService, 'push') + + const connection = await libp2p.dial(remoteAddr) + expect(connection).to.exist() + + // Wait for identify to finish + await identityServiceIdentifySpy.firstCall.returnValue + sinon.stub(libp2p, 'isStarted').returns(true) + + await libp2p.peerStore.addressBook.add(libp2p.peerId, [new Multiaddr('/ip4/180.0.0.1/tcp/15001/ws')]) + + // the protocol change event listener in the identity service is async + await pWaitFor(() => identityServicePushSpy.callCount === 1) + + // Verify the remote peer is notified of change + expect(identityServicePushSpy.callCount).to.equal(1) + for (const call of identityServicePushSpy.getCalls()) { + const [connections] = call.args + expect(connections.length).to.equal(1) + expect(connections[0].remotePeer.toString()).to.equal(remoteAddr.getPeerId()) + await call.returnValue + } + + // Verify the streams close + await pWaitFor(() => connection.streams.length === 0) + }) + }) +}) diff --git a/test/insecure/compliance.spec.js b/test/insecure/compliance.spec.js deleted file mode 100644 index f124dde9fa..0000000000 --- a/test/insecure/compliance.spec.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const tests = require('libp2p-interfaces-compliance-tests/src/crypto') -const plaintext = require('../../src/insecure/plaintext') - -describe('plaintext compliance', () => { - tests({ - setup () { - return plaintext - } - }) -}) diff --git a/test/insecure/compliance.spec.ts b/test/insecure/compliance.spec.ts new file mode 100644 index 0000000000..2c919f2df7 --- /dev/null +++ b/test/insecure/compliance.spec.ts @@ -0,0 +1,15 @@ +/* eslint-env mocha */ + +import suite from '@libp2p/interface-compliance-tests/connection-encrypter' +import { Plaintext } from '../../src/insecure/index.js' + +describe('plaintext compliance', () => { + suite({ + async setup () { + return new Plaintext() + }, + async teardown () { + + } + }) +}) diff --git a/test/insecure/plaintext.spec.js b/test/insecure/plaintext.spec.js deleted file mode 100644 index 41f10c5a5b..0000000000 --- a/test/insecure/plaintext.spec.js +++ /dev/null @@ -1,67 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') - -const PeerId = require('peer-id') -const duplexPair = require('it-pair/duplex') - -const peers = require('../fixtures/peers') -const plaintext = require('../../src/insecure/plaintext') -const { - InvalidCryptoExchangeError, - UnexpectedPeerError -} = require('libp2p-interfaces/src/crypto/errors') - -describe('plaintext', () => { - let localPeer - let remotePeer - let wrongPeer - - before(async () => { - [localPeer, remotePeer, wrongPeer] = await Promise.all([ - PeerId.createFromJSON(peers[0]), - PeerId.createFromJSON(peers[1]), - PeerId.createFromJSON(peers[2]) - ]) - }) - - afterEach(() => { - sinon.restore() - }) - - it('should verify the public key and id match', () => { - const [localConn, remoteConn] = duplexPair() - - // When we attempt to get the remote peer key, return the wrong peers pub key - sinon.stub(remotePeer, 'marshalPubKey').callsFake(() => { - return wrongPeer.marshalPubKey() - }) - - return Promise.all([ - plaintext.secureInbound(remotePeer, localConn), - plaintext.secureOutbound(localPeer, remoteConn, remotePeer) - ]).then(() => expect.fail('should have failed'), (err) => { - expect(err).to.exist() - expect(err).to.have.property('code', UnexpectedPeerError.code) - }) - }) - - it('should fail if the peer does not provide its public key', () => { - const [localConn, remoteConn] = duplexPair() - - // When we attempt to get the remote peer key, return the wrong peers pub key - sinon.stub(remotePeer, 'marshalPubKey').callsFake(() => { - return new Uint8Array(0) - }) - - return Promise.all([ - plaintext.secureInbound(remotePeer, localConn), - plaintext.secureOutbound(localPeer, remoteConn, remotePeer) - ]).then(() => expect.fail('should have failed'), (err) => { - expect(err).to.exist() - expect(err).to.have.property('code', InvalidCryptoExchangeError.code) - }) - }) -}) diff --git a/test/insecure/plaintext.spec.ts b/test/insecure/plaintext.spec.ts new file mode 100644 index 0000000000..d4198dabbe --- /dev/null +++ b/test/insecure/plaintext.spec.ts @@ -0,0 +1,74 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import sinon from 'sinon' +import Peers from '../fixtures/peers.js' +import { Plaintext } from '../../src/insecure/index.js' +import { + InvalidCryptoExchangeError, + UnexpectedPeerError +} from '@libp2p/interfaces/connection-encrypter/errors' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import { createFromJSON, createRSAPeerId } from '@libp2p/peer-id-factory' +import type { ConnectionEncrypter } from '@libp2p/interfaces/connection-encrypter' +import { mockMultiaddrConnPair } from '@libp2p/interface-compliance-tests/mocks' +import { Multiaddr } from '@multiformats/multiaddr' +import { peerIdFromBytes } from '@libp2p/peer-id' + +describe('plaintext', () => { + let localPeer: PeerId + let remotePeer: PeerId + let wrongPeer: PeerId + let plaintext: ConnectionEncrypter + + beforeEach(async () => { + [localPeer, remotePeer, wrongPeer] = await Promise.all([ + createFromJSON(Peers[0]), + createFromJSON(Peers[1]), + createFromJSON(Peers[2]) + ]) + + plaintext = new Plaintext() + }) + + afterEach(() => { + sinon.restore() + }) + + it('should verify the public key and id match', async () => { + const { inbound, outbound } = mockMultiaddrConnPair({ + remotePeer, + addrs: [ + new Multiaddr('/ip4/127.0.0.1/tcp/1234'), + new Multiaddr('/ip4/127.0.0.1/tcp/1235') + ] + }) + + await Promise.all([ + plaintext.secureInbound(remotePeer, inbound), + plaintext.secureOutbound(localPeer, outbound, wrongPeer) + ]).then(() => expect.fail('should have failed'), (err) => { + expect(err).to.exist() + expect(err).to.have.property('code', UnexpectedPeerError.code) + }) + }) + + it('should fail if the peer does not provide its public key', async () => { + const peer = await createRSAPeerId() + remotePeer = peerIdFromBytes(peer.toBytes()) + + const { inbound, outbound } = mockMultiaddrConnPair({ + remotePeer, + addrs: [ + new Multiaddr('/ip4/127.0.0.1/tcp/1234'), + new Multiaddr('/ip4/127.0.0.1/tcp/1235') + ] + }) + + await expect(Promise.all([ + plaintext.secureInbound(localPeer, inbound), + plaintext.secureOutbound(remotePeer, outbound, localPeer) + ])) + .to.eventually.be.rejected.with.property('code', InvalidCryptoExchangeError.code) + }) +}) diff --git a/test/interop.ts b/test/interop.ts new file mode 100644 index 0000000000..e457dc360b --- /dev/null +++ b/test/interop.ts @@ -0,0 +1,159 @@ +import { interopTests } from '@libp2p/interop' +import type { SpawnOptions, Daemon, DaemonFactory } from '@libp2p/interop' +import { createServer } from '@libp2p/daemon-server' +import { createClient } from '@libp2p/daemon-client' +import { createLibp2p, Libp2pOptions } from '../src/index.js' +import { Noise } from '@chainsafe/libp2p-noise' +import { TCP } from '@libp2p/tcp' +import { Multiaddr } from '@multiformats/multiaddr' +import { KadDHT } from '@libp2p/kad-dht' +import { path as p2pd } from 'go-libp2p' +import execa from 'execa' +import pDefer from 'p-defer' +import { logger } from '@libp2p/logger' +import { Mplex } from '@libp2p/mplex' +import fs from 'fs' +import { unmarshalPrivateKey } from '@libp2p/crypto/keys' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import { peerIdFromKeys } from '@libp2p/peer-id' +import { FloodSub } from '@libp2p/floodsub' +import { Gossipsub } from '@achingbrain/libp2p-gossipsub' + +// IPFS_LOGGING=debug DEBUG=libp2p*,go-libp2p:* npm run test:interop + +async function createGoPeer (options: SpawnOptions): Promise { + const controlPort = Math.floor(Math.random() * (50000 - 10000 + 1)) + 10000 + const apiAddr = new Multiaddr(`/ip4/0.0.0.0/tcp/${controlPort}`) + + const log = logger(`go-libp2p:${controlPort}`) + + const opts = [ + `-listen=${apiAddr.toString()}`, + '-hostAddrs=/ip4/0.0.0.0/tcp/0' + ] + + if (options.noise === true) { + opts.push('-noise=true') + } + + if (options.dht === true) { + opts.push('-dhtServer') + } + + if (options.pubsub === true) { + opts.push('-pubsub') + } + + if (options.pubsubRouter != null) { + opts.push(`-pubsubRouter=${options.pubsubRouter}`) + } + + if (options.key != null) { + opts.push(`-id=${options.key}`) + } + + const deferred = pDefer() + const proc = execa(p2pd(), opts) + + proc.stdout?.on('data', (buf: Buffer) => { + const str = buf.toString() + log(str) + + // daemon has started + if (str.includes('Control socket:')) { + deferred.resolve() + } + }) + + proc.stderr?.on('data', (buf) => { + log.error(buf.toString()) + }) + + await deferred.promise + + return { + client: createClient(apiAddr), + stop: async () => { + await proc.kill() + } + } +} + +async function createJsPeer (options: SpawnOptions): Promise { + let peerId: PeerId | undefined + + if (options.key != null) { + const keyFile = fs.readFileSync(options.key) + const privateKey = await unmarshalPrivateKey(keyFile) + peerId = await peerIdFromKeys(privateKey.public.bytes, privateKey.bytes) + } + + const opts: Libp2pOptions = { + peerId, + addresses: { + listen: ['/ip4/0.0.0.0/tcp/0'] + }, + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()] + } + + if (options.dht === true) { + // go-libp2p-daemon only has the older single-table DHT instead of the dual + // lan/wan version found in recent go-ipfs versions. unfortunately it's been + // abandoned so here we simulate the older config with the js implementation + const dht = new KadDHT({ + clientMode: false + }) + const lan = dht.lan + + const protocol = '/ipfs/kad/1.0.0' + lan.protocol = protocol + // @ts-expect-error + lan.network.protocol = protocol + // @ts-expect-error + lan.topologyListener.protocol = protocol + + // @ts-expect-error + opts.dht = lan + } + + if (options.pubsub === true) { + if (options.pubsubRouter === 'floodsub') { + opts.pubsub = new FloodSub() + } else { + opts.pubsub = new Gossipsub() + } + } + + const node = await createLibp2p(opts) + const server = await createServer(new Multiaddr('/ip4/0.0.0.0/tcp/0'), node) + await server.start() + + return { + client: createClient(server.getMultiaddr()), + stop: async () => { + await server.stop() + await node.stop() + } + } +} + +async function main () { + const factory: DaemonFactory = { + async spawn (options: SpawnOptions) { + if (options.type === 'go') { + return await createGoPeer(options) + } + + return await createJsPeer(options) + } + } + + await interopTests(factory) +} + +main().catch(err => { + console.error(err) // eslint-disable-line no-console + process.exit(1) +}) diff --git a/test/keychain/cms-interop.spec.js b/test/keychain/cms-interop.spec.ts similarity index 84% rename from test/keychain/cms-interop.spec.js rename to test/keychain/cms-interop.spec.ts index bd2f09e7a0..32748accf3 100644 --- a/test/keychain/cms-interop.spec.js +++ b/test/keychain/cms-interop.spec.ts @@ -1,21 +1,21 @@ /* eslint max-nested-callbacks: ["error", 8] */ /* eslint-env mocha */ -'use strict' -const { expect } = require('aegir/utils/chai') -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') -const { MemoryDatastore } = require('datastore-core/memory') -const Keychain = require('../../src/keychain') +import { expect } from 'aegir/utils/chai.js' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { MemoryDatastore } from 'datastore-core/memory' +import { KeyChain } from '../../src/keychain/index.js' +import { Components } from '@libp2p/interfaces/components' describe('cms interop', () => { const passPhrase = 'this is not a secure phrase' const aliceKeyName = 'cms-interop-alice' - let ks + let ks: KeyChain before(() => { const datastore = new MemoryDatastore() - ks = new Keychain(datastore, { passPhrase: passPhrase }) + ks = new KeyChain(new Components({ datastore }), { pass: passPhrase }) }) const plainData = uint8ArrayFromString('This is a message from Alice to Bob') diff --git a/test/keychain/keychain.spec.js b/test/keychain/keychain.spec.ts similarity index 52% rename from test/keychain/keychain.spec.js rename to test/keychain/keychain.spec.ts index 070a233da4..6a176ea45c 100644 --- a/test/keychain/keychain.spec.js +++ b/test/keychain/keychain.spec.ts @@ -1,35 +1,35 @@ /* eslint max-nested-callbacks: ["error", 8] */ /* eslint-env mocha */ -'use strict' -const { expect } = require('aegir/utils/chai') -const fail = expect.fail -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') - -const peerUtils = require('../utils/creators/peer') - -const { Key } = require('interface-datastore/key') -const { MemoryDatastore } = require('datastore-core/memory') -const Keychain = require('../../src/keychain') -const PeerId = require('peer-id') -const crypto = require('libp2p-crypto') +import { expect } from 'aegir/utils/chai.js' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { createNode } from '../utils/creators/peer.js' +import { Key } from 'interface-datastore/key' +import { MemoryDatastore } from 'datastore-core/memory' +import { KeyChain, KeyChainInit, KeyInfo } from '../../src/keychain/index.js' +import { pbkdf2 } from '@libp2p/crypto' +import { Components } from '@libp2p/interfaces/components' +import type { Datastore } from 'interface-datastore' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import { createFromPrivKey } from '@libp2p/peer-id-factory' +import { unmarshalPrivateKey } from '@libp2p/crypto/keys' describe('keychain', () => { const passPhrase = 'this is not a secure phrase' const rsaKeyName = 'tajné jméno' const renamedRsaKeyName = 'ชื่อลับ' - let rsaKeyInfo - let emptyKeystore - let ks - let datastore1, datastore2 + let rsaKeyInfo: KeyInfo + let emptyKeystore: KeyChain + let ks: KeyChain + let datastore1: Datastore, datastore2: Datastore before(async () => { datastore1 = new MemoryDatastore() datastore2 = new MemoryDatastore() - ks = new Keychain(datastore2, { pass: passPhrase }) - emptyKeystore = new Keychain(datastore1, { pass: passPhrase }) + ks = new KeyChain(new Components({ datastore: datastore2 }), { pass: passPhrase }) + emptyKeystore = new KeyChain(new Components({ datastore: datastore1 }), { pass: passPhrase }) await datastore1.open() await datastore2.open() @@ -41,78 +41,77 @@ describe('keychain', () => { }) it('can start without a password', () => { - expect(() => new Keychain(datastore2)).to.not.throw() + expect(() => new KeyChain(new Components({ datastore: datastore2 }), {})).to.not.throw() }) it('needs a NIST SP 800-132 non-weak pass phrase', () => { - expect(() => new Keychain(datastore2, { pass: '< 20 character' })).to.throw() - }) - - it('needs a store to persist a key', () => { - expect(() => new Keychain(null, { pass: passPhrase })).to.throw() + expect(() => new KeyChain(new Components({ datastore: datastore2 }), { pass: '< 20 character' })).to.throw() }) it('has default options', () => { - expect(Keychain.options).to.exist() + expect(KeyChain.options).to.exist() }) it('supports supported hashing alorithms', () => { - const ok = new Keychain(datastore2, { pass: passPhrase, dek: { hash: 'sha2-256' } }) + const ok = new KeyChain(new Components({ datastore: datastore2 }), { pass: passPhrase, dek: { hash: 'sha2-256', salt: 'salt-salt-salt-salt', iterationCount: 1000, keyLength: 14 } }) expect(ok).to.exist() }) it('does not support unsupported hashing alorithms', () => { - expect(() => new Keychain(datastore2, { pass: passPhrase, dek: { hash: 'my-hash' } })).to.throw() + expect(() => new KeyChain(new Components({ datastore: datastore2 }), { pass: passPhrase, dek: { hash: 'my-hash', salt: 'salt-salt-salt-salt', iterationCount: 1000, keyLength: 14 } })).to.throw() }) it('can list keys without a password', async () => { - const keychain = new Keychain(datastore2) + const keychain = new KeyChain(new Components({ datastore: 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, { pass: `hello-${Date.now()}-${Date.now()}` }) + const keychain = new KeyChain(new Components({ datastore: datastore2 }), {}) + const keychainWithPassword = new KeyChain(new Components({ datastore: datastore2 }), { pass: `hello-${Date.now()}-${Date.now()}` }) const name = `key-${Math.random()}` - const { id } = await keychainWithPassword.createKey(name, 'ed25519') + const { id } = await keychainWithPassword.createKey(name, 'Ed25519') 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, { pass: `hello-${Date.now()}-${Date.now()}` }) + const keychainWithoutPassword = new KeyChain(new Components({ datastore: datastore2 }), {}) + const keychainWithPassword = new KeyChain(new Components({ datastore: datastore2 }), { pass: `hello-${Date.now()}-${Date.now()}` }) const name = `key-${Math.random()}` - expect(await keychainWithPassword.createKey(name, 'ed25519')).to.have.property('name', name) + expect(await keychainWithPassword.createKey(name, 'Ed25519')).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) + it('requires a name to create a password', async () => { + const keychain = new KeyChain(new Components({ datastore: datastore2 }), {}) - await expect(keychain.createKey('derp')).to.be.rejected() + // @ts-expect-error invalid parameters + await expect(keychain.createKey(undefined, 'derp')).to.be.rejected() }) it('can generate options', () => { - const options = Keychain.generateOptions() + const options = KeyChain.generateOptions() options.pass = passPhrase - const chain = new Keychain(datastore2, options) + const chain = new KeyChain(new Components({ datastore: datastore2 }), options) expect(chain).to.exist() }) describe('key name', () => { it('is a valid filename and non-ASCII', async () => { const errors = await Promise.all([ - ks.removeKey('../../nasty').then(fail, err => err), - ks.removeKey('').then(fail, err => err), - ks.removeKey(' ').then(fail, err => err), - ks.removeKey(null).then(fail, err => err), - ks.removeKey(undefined).then(fail, err => err) + ks.removeKey('../../nasty').catch(err => err), + ks.removeKey('').catch(err => err), + ks.removeKey(' ').catch(err => err), + // @ts-expect-error invalid parameters + ks.removeKey(null).catch(err => err), + // @ts-expect-error invalid parameters + ks.removeKey(undefined).catch(err => err) ]) expect(errors).to.have.length(5) @@ -124,85 +123,73 @@ describe('keychain', () => { describe('key', () => { it('can be an RSA key', async () => { - rsaKeyInfo = await ks.createKey(rsaKeyName, 'rsa', 2048) + rsaKeyInfo = await ks.createKey(rsaKeyName, 'RSA', 2048) expect(rsaKeyInfo).to.exist() expect(rsaKeyInfo).to.have.property('name', rsaKeyName) expect(rsaKeyInfo).to.have.property('id') }) it('is encrypted PEM encoded PKCS #8', async () => { - const pem = await ks._getPrivateKey(rsaKeyName) + const pem = await ks.getPrivateKey(rsaKeyName) return expect(pem).to.startsWith('-----BEGIN ENCRYPTED PRIVATE KEY-----') }) it('throws if an invalid private key name is given', async () => { - const err = await ks._getPrivateKey(undefined).then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME') + // @ts-expect-error invalid parameters + await expect(ks.getPrivateKey(undefined)).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_NAME') }) it('throws if a private key cant be found', async () => { - const err = await ks._getPrivateKey('not real').then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_KEY_NOT_FOUND') + await expect(ks.getPrivateKey('not real')).to.eventually.be.rejected.with.property('code', 'ERR_KEY_NOT_FOUND') }) it('does not overwrite existing key', async () => { - const err = await ks.createKey(rsaKeyName, 'rsa', 2048).then(fail, err => err) - expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS') + await expect(ks.createKey(rsaKeyName, 'RSA', 2048)).to.eventually.be.rejected.with.property('code', 'ERR_KEY_ALREADY_EXISTS') }) it('cannot create the "self" key', async () => { - const err = await ks.createKey('self', 'rsa', 2048).then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME') + await expect(ks.createKey('self', 'RSA', 2048)).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_NAME') }) it('should validate name is string', async () => { - const err = await ks.createKey(5, 'rsa', 2048).then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME') + // @ts-expect-error invalid parameters + await expect(ks.createKey(5, 'rsa', 2048)).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_NAME') }) it('should validate type is string', async () => { - const err = await ks.createKey('TEST' + Date.now(), null, 2048).then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_INVALID_KEY_TYPE') + // @ts-expect-error invalid parameters + await expect(ks.createKey(`TEST-${Date.now()}`, null, 2048)).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_TYPE') }) it('should validate size is integer', async () => { - const err = await ks.createKey('TEST' + Date.now(), 'rsa', 'string').then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_INVALID_KEY_SIZE') + // @ts-expect-error invalid parameters + await expect(ks.createKey(`TEST-${Date.now()}`, 'RSA', 'string')).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_SIZE') }) describe('implements NIST SP 800-131A', () => { it('disallows RSA length < 2048', async () => { - const err = await ks.createKey('bad-nist-rsa', 'rsa', 1024).then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_INVALID_KEY_SIZE') + await expect(ks.createKey('bad-nist-rsa', 'RSA', 1024)).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_SIZE') }) }) }) - describe('ed25519 keys', () => { + describe('Ed25519 keys', () => { const keyName = 'my custom key' - it('can be an ed25519 key', async () => { - const keyInfo = await ks.createKey(keyName, 'ed25519') + 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') + await expect(ks.createKey(keyName, 'Ed25519')).to.eventually.be.rejected.with.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 keyInfo = await ks.createKey(keyName, 'Ed25519') const exportedKey = await ks.exportKey(keyName, password) // remove it so we can import it await ks.removeKey(keyName) @@ -211,41 +198,7 @@ describe('keychain', () => { }) 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') + await expect(ks.createKey('self', 'Ed25519')).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_NAME') }) }) @@ -281,22 +234,19 @@ describe('keychain', () => { describe('CMS protected data', () => { const plainData = uint8ArrayFromString('This is a message from Alice to Bob') - let cms + let cms: Uint8Array it('service is available', () => { expect(ks).to.have.property('cms') }) it('requires a key', async () => { - const err = await ks.cms.encrypt('no-key', plainData).then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_KEY_NOT_FOUND') + await expect(ks.cms.encrypt('no-key', plainData)).to.eventually.be.rejected.with.property('code', 'ERR_KEY_NOT_FOUND') }) it('requires plain data as a Uint8Array', async () => { - const err = await ks.cms.encrypt(rsaKeyName, 'plain data').then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_INVALID_PARAMETERS') + // @ts-expect-error invalid parameters + await expect(ks.cms.encrypt(rsaKeyName, 'plain data')).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_PARAMETERS') }) it('encrypts', async () => { @@ -306,23 +256,16 @@ describe('keychain', () => { }) it('is a PKCS #7 message', async () => { - const err = await ks.cms.decrypt('not CMS').then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_INVALID_PARAMETERS') + // @ts-expect-error invalid parameters + await expect(ks.cms.decrypt('not CMS')).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_PARAMETERS') }) it('is a PKCS #7 binary message', async () => { - const err = await ks.cms.decrypt(plainData).then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_INVALID_CMS') + await expect(ks.cms.decrypt(plainData)).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_CMS') }) it('cannot be read without the key', async () => { - const err = await emptyKeystore.cms.decrypt(cms).then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('missingKeys') - expect(err.missingKeys).to.eql([rsaKeyInfo.id]) - expect(err).to.have.property('code', 'ERR_MISSING_KEYS') + await expect(emptyKeystore.cms.decrypt(cms)).to.eventually.be.rejected.with.property('code', 'ERR_MISSING_KEYS') }) it('can be read with the key', async () => { @@ -333,18 +276,16 @@ describe('keychain', () => { }) describe('exported key', () => { - let pemKey + let pemKey: string it('requires the password', async () => { - const err = await ks.exportKey(rsaKeyName).then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_PASSWORD_REQUIRED') + // @ts-expect-error invalid parameters + await expect(ks.exportKey(rsaKeyName)).to.eventually.be.rejected.with.property('code', 'ERR_PASSWORD_REQUIRED') }) it('requires the key name', async () => { - const err = await ks.exportKey(undefined, 'password').then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME') + // @ts-expect-error invalid parameters + await expect(ks.exportKey(undefined, 'password')).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_NAME') }) it('is a PKCS #8 encrypted pem', async () => { @@ -359,89 +300,75 @@ describe('keychain', () => { }) it('requires the pem', async () => { - const err = await ks.importKey('imported-key', undefined, 'password').then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_PEM_REQUIRED') + // @ts-expect-error invalid parameters + await expect(ks.importKey('imported-key', undefined, 'password')).to.eventually.be.rejected.with.property('code', 'ERR_PEM_REQUIRED') }) it('cannot be imported as an existing key name', async () => { - const err = await ks.importKey(rsaKeyName, pemKey, 'password').then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS') + await expect(ks.importKey(rsaKeyName, pemKey, 'password')).to.eventually.be.rejected.with.property('code', 'ERR_KEY_ALREADY_EXISTS') }) it('cannot be imported with the wrong password', async () => { - const err = await ks.importKey('a-new-name-for-import', pemKey, 'not the password').then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_CANNOT_READ_KEY') + await expect(ks.importKey('a-new-name-for-import', pemKey, 'not the password')).to.eventually.be.rejected.with.property('code', 'ERR_CANNOT_READ_KEY') }) }) describe('peer id', () => { const alicePrivKey = 'CAASpgkwggSiAgEAAoIBAQC2SKo/HMFZeBml1AF3XijzrxrfQXdJzjePBZAbdxqKR1Mc6juRHXij6HXYPjlAk01BhF1S3Ll4Lwi0cAHhggf457sMg55UWyeGKeUv0ucgvCpBwlR5cQ020i0MgzjPWOLWq1rtvSbNcAi2ZEVn6+Q2EcHo3wUvWRtLeKz+DZSZfw2PEDC+DGPJPl7f8g7zl56YymmmzH9liZLNrzg/qidokUv5u1pdGrcpLuPNeTODk0cqKB+OUbuKj9GShYECCEjaybJDl9276oalL9ghBtSeEv20kugatTvYy590wFlJkkvyl+nPxIH0EEYMKK9XRWlu9XYnoSfboiwcv8M3SlsjAgMBAAECggEAZtju/bcKvKFPz0mkHiaJcpycy9STKphorpCT83srBVQi59CdFU6Mj+aL/xt0kCPMVigJw8P3/YCEJ9J+rS8BsoWE+xWUEsJvtXoT7vzPHaAtM3ci1HZd302Mz1+GgS8Epdx+7F5p80XAFLDUnELzOzKftvWGZmWfSeDnslwVONkL/1VAzwKy7Ce6hk4SxRE7l2NE2OklSHOzCGU1f78ZzVYKSnS5Ag9YrGjOAmTOXDbKNKN/qIorAQ1bovzGoCwx3iGIatQKFOxyVCyO1PsJYT7JO+kZbhBWRRE+L7l+ppPER9bdLFxs1t5CrKc078h+wuUr05S1P1JjXk68pk3+kQKBgQDeK8AR11373Mzib6uzpjGzgNRMzdYNuExWjxyxAzz53NAR7zrPHvXvfIqjDScLJ4NcRO2TddhXAfZoOPVH5k4PJHKLBPKuXZpWlookCAyENY7+Pd55S8r+a+MusrMagYNljb5WbVTgN8cgdpim9lbbIFlpN6SZaVjLQL3J8TWH6wKBgQDSChzItkqWX11CNstJ9zJyUE20I7LrpyBJNgG1gtvz3ZMUQCn3PxxHtQzN9n1P0mSSYs+jBKPuoSyYLt1wwe10/lpgL4rkKWU3/m1Myt0tveJ9WcqHh6tzcAbb/fXpUFT/o4SWDimWkPkuCb+8j//2yiXk0a/T2f36zKMuZvujqQKBgC6B7BAQDG2H2B/ijofp12ejJU36nL98gAZyqOfpLJ+FeMz4TlBDQ+phIMhnHXA5UkdDapQ+zA3SrFk+6yGk9Vw4Hf46B+82SvOrSbmnMa+PYqKYIvUzR4gg34rL/7AhwnbEyD5hXq4dHwMNsIDq+l2elPjwm/U9V0gdAl2+r50HAoGALtsKqMvhv8HucAMBPrLikhXP/8um8mMKFMrzfqZ+otxfHzlhI0L08Bo3jQrb0Z7ByNY6M8epOmbCKADsbWcVre/AAY0ZkuSZK/CaOXNX/AhMKmKJh8qAOPRY02LIJRBCpfS4czEdnfUhYV/TYiFNnKRj57PPYZdTzUsxa/yVTmECgYBr7slQEjb5Onn5mZnGDh+72BxLNdgwBkhO0OCdpdISqk0F0Pxby22DFOKXZEpiyI9XYP1C8wPiJsShGm2yEwBPWXnrrZNWczaVuCbXHrZkWQogBDG3HGXNdU4MAWCyiYlyinIBpPpoAJZSzpGLmWbMWh28+RJS6AQX6KHrK1o2uw==' - let alice + let alice: PeerId before(async function () { const encoded = uint8ArrayFromString(alicePrivKey, 'base64pad') - alice = await PeerId.createFromPrivKey(encoded) + const privateKey = await unmarshalPrivateKey(encoded) + alice = await createFromPrivKey(privateKey) }) it('private key can be imported', async () => { const key = await ks.importPeer('alice', alice) expect(key.name).to.equal('alice') - expect(key.id).to.equal(alice.toB58String()) + expect(key.id).to.equal(alice.toString()) }) it('private key import requires a valid name', async () => { - const err = await ks.importPeer(undefined, alice).then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME') + // @ts-expect-error invalid parameters + await expect(ks.importPeer(undefined, alice)).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_NAME') }) it('private key import requires the peer', async () => { - const err = await ks.importPeer('alice').then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_MISSING_PRIVATE_KEY') + // @ts-expect-error invalid parameters + await expect(ks.importPeer('alice')).to.eventually.be.rejected.with.property('code', 'ERR_MISSING_PRIVATE_KEY') }) it('key id exists', async () => { - const key = await ks.findKeyById(alice.toB58String()) + const key = await ks.findKeyById(alice.toString()) expect(key).to.exist() expect(key).to.have.property('name', 'alice') - expect(key).to.have.property('id', alice.toB58String()) + expect(key).to.have.property('id', alice.toString()) }) it('key name exists', async () => { const key = await ks.findKeyByName('alice') expect(key).to.exist() expect(key).to.have.property('name', 'alice') - expect(key).to.have.property('id', alice.toB58String()) + expect(key).to.have.property('id', alice.toString()) }) }) describe('rename', () => { it('requires an existing key name', async () => { - const err = await ks.renameKey('not-there', renamedRsaKeyName).then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_NOT_FOUND') + await expect(ks.renameKey('not-there', renamedRsaKeyName)).to.eventually.be.rejected.with.property('code', 'ERR_NOT_FOUND') }) it('requires a valid new key name', async () => { - const err = await ks.renameKey(rsaKeyName, '..\not-valid').then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_NEW_KEY_NAME_INVALID') + await expect(ks.renameKey(rsaKeyName, '..\not-valid')).to.eventually.be.rejected.with.property('code', 'ERR_NEW_KEY_NAME_INVALID') }) it('does not overwrite existing key', async () => { - const err = await ks.renameKey(rsaKeyName, rsaKeyName).then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS') + await expect(ks.renameKey(rsaKeyName, rsaKeyName)).to.eventually.be.rejected.with.property('code', 'ERR_KEY_ALREADY_EXISTS') }) it('cannot create the "self" key', async () => { - const err = await ks.renameKey(rsaKeyName, 'self').then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_NEW_KEY_NAME_INVALID') + await expect(ks.renameKey(rsaKeyName, 'self')).to.eventually.be.rejected.with.property('code', 'ERR_NEW_KEY_NAME_INVALID') }) it('removes the existing key name', async () => { @@ -450,8 +377,7 @@ describe('keychain', () => { expect(key).to.have.property('name', renamedRsaKeyName) expect(key).to.have.property('id', rsaKeyInfo.id) // Try to find the changed key - const err = await ks.findKeyByName(rsaKeyName).then(fail, err => err) - expect(err).to.exist() + await expect(ks.findKeyByName(rsaKeyName)).to.eventually.be.rejected() }) it('creates the new key name', async () => { @@ -468,23 +394,18 @@ describe('keychain', () => { }) it('throws with invalid key names', async () => { - const err = await ks.findKeyByName(undefined).then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME') + // @ts-expect-error invalid parameters + await expect(ks.findKeyByName(undefined)).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_NAME') }) }) describe('key removal', () => { it('cannot remove the "self" key', async () => { - const err = await ks.removeKey('self').then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME') + await expect(ks.removeKey('self')).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_NAME') }) it('cannot remove an unknown key', async () => { - const err = await ks.removeKey('not-there').then(fail, err => err) - expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_KEY_NOT_FOUND') + await expect(ks.removeKey('not-there')).to.eventually.be.rejected.with.property('code', 'ERR_KEY_NOT_FOUND') }) it('can remove a known key', async () => { @@ -496,10 +417,10 @@ describe('keychain', () => { }) describe('rotate keychain passphrase', () => { - let oldPass - let kc - let options - let ds + let oldPass: string + let kc: KeyChain + let options: KeyChainInit + let ds: Datastore before(async () => { ds = new MemoryDatastore() oldPass = `hello-${Date.now()}-${Date.now()}` @@ -512,36 +433,30 @@ describe('keychain', () => { hash: 'sha2-512' } } - kc = new Keychain(ds, options) + kc = new KeyChain(new Components({ datastore: ds }), options) await ds.open() }) it('should validate newPass is a string', async () => { - try { - await kc.rotateKeychainPass(oldPass, 1234567890) - } catch (/** @type {any} */ err) { - expect(err).to.exist() - } + // @ts-expect-error invalid parameters + await expect(kc.rotateKeychainPass(oldPass, 1234567890)).to.eventually.be.rejected() }) it('should validate oldPass is a string', async () => { - try { - await kc.rotateKeychainPass(1234, 'newInsecurePassword1') - } catch (/** @type {any} */ err) { - expect(err).to.exist() - } + // @ts-expect-error invalid parameters + await expect(kc.rotateKeychainPass(1234, 'newInsecurePassword1')).to.eventually.be.rejected() }) it('should validate newPass is at least 20 characters', async () => { try { await kc.rotateKeychainPass(oldPass, 'not20Chars') - } catch (/** @type {any} */ err) { + } catch (err: any) { expect(err).to.exist() } }) it('can rotate keychain passphrase', async () => { - await kc.createKey('keyCreatedWithOldPassword', 'rsa', 2048) + await kc.createKey('keyCreatedWithOldPassword', 'RSA', 2048) await kc.rotateKeychainPass(oldPass, 'newInsecurePassphrase') // Get Key PEM from datastore @@ -549,24 +464,23 @@ describe('keychain', () => { const res = await ds.get(dsname) const pem = uint8ArrayToString(res) - const oldDek = options.pass - ? crypto.pbkdf2( + const oldDek = options.pass != null + ? pbkdf2( options.pass, - options.dek.salt, - options.dek.iterationCount, - options.dek.keyLength, - options.dek.hash) + options.dek?.salt ?? 'salt', + options.dek?.iterationCount ?? 0, + options.dek?.keyLength ?? 0, + options.dek?.hash ?? 'sha2-256' + ) : '' - // eslint-disable-next-line no-constant-condition - const newDek = 'newInsecurePassphrase' - ? crypto.pbkdf2( - 'newInsecurePassphrase', - options.dek.salt, - options.dek.iterationCount, - options.dek.keyLength, - options.dek.hash) - : '' + const newDek = pbkdf2( + 'newInsecurePassphrase', + options.dek?.salt ?? 'salt', + options.dek?.iterationCount ?? 0, + options.dek?.keyLength ?? 0, + options.dek?.hash ?? 'sha2-256' + ) // Dek with old password should not work: await expect(kc.importKey('keyWhosePassChanged', pem, oldDek)) @@ -579,26 +493,20 @@ describe('keychain', () => { }) describe('libp2p.keychain', () => { - it('needs a passphrase to be used, otherwise throws an error', async () => { - const [libp2p] = await peerUtils.createPeer({ + it.skip('needs a passphrase to be used, otherwise throws an error', async () => { + const libp2p = await createNode({ started: false }) - try { - await libp2p.keychain.createKey('keyName', 'rsa', 2048) - } catch (/** @type {any} */ err) { - expect(err).to.exist() - return - } - throw new Error('should throw an error using the keychain if no passphrase provided') + await expect(libp2p.keychain.createKey('keyName', 'RSA', 2048)).to.be.rejected() }) it('can be used when a passphrase is provided', async () => { - const [libp2p] = await peerUtils.createPeer({ + const libp2p = await createNode({ started: false, config: { + datastore: new MemoryDatastore(), keychain: { - datastore: new MemoryDatastore(), pass: '12345678901234567890' } } @@ -606,47 +514,45 @@ describe('libp2p.keychain', () => { await libp2p.loadKeychain() - const kInfo = await libp2p.keychain.createKey('keyName', 'ed25519') + const kInfo = await libp2p.keychain.createKey('keyName', 'Ed25519') expect(kInfo).to.exist() }) it('does not require a keychain passphrase', async () => { - const [libp2p] = await peerUtils.createPeer({ + const libp2p = await createNode({ started: false, config: { - keychain: { - datastore: new MemoryDatastore() - } + datastore: new MemoryDatastore() } }) await libp2p.loadKeychain() - const kInfo = await libp2p.keychain.createKey('keyName', 'ed25519') + 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({ + const libp2p = await createNode({ started: false, config: { + datastore, keychain: { - datastore, pass: '12345678901234567890' } } }) await libp2p.loadKeychain() - const kInfo = await libp2p.keychain.createKey('keyName', 'ed25519') + const kInfo = await libp2p.keychain.createKey('keyName', 'Ed25519') expect(kInfo).to.exist() - const [libp2p2] = await peerUtils.createPeer({ + const libp2p2 = await createNode({ started: false, config: { + datastore, keychain: { - datastore, pass: '12345678901234567890' } } diff --git a/test/keychain/peerid.spec.js b/test/keychain/peerid.spec.ts similarity index 71% rename from test/keychain/peerid.spec.js rename to test/keychain/peerid.spec.ts index 9d9592f396..6d5d135419 100644 --- a/test/keychain/peerid.spec.js +++ b/test/keychain/peerid.spec.ts @@ -1,13 +1,11 @@ /* eslint-env mocha */ -'use strict' -const { expect } = require('aegir/utils/chai') -const PeerId = require('peer-id') -const { base58btc } = require('multiformats/bases/base58') -const crypto = require('libp2p-crypto') -const rsaUtils = require('libp2p-crypto/src/keys/rsa-utils') -const rsaClass = require('libp2p-crypto/src/keys/rsa-class') -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') +import { expect } from 'aegir/utils/chai.js' +import { base58btc } from 'multiformats/bases/base58' +import { supportedKeys, unmarshalPrivateKey, unmarshalPublicKey } from '@libp2p/crypto/keys' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import { createFromPrivKey } from '@libp2p/peer-id-factory' const sample = { id: '122019318b6e5e0cf93a2314bf01269a2cc23cd3dcd452d742cdb9379d8646f6e4a9', @@ -16,32 +14,39 @@ const sample = { } describe('peer ID', () => { - let peer - let publicKeyDer // a buffer + let peer: PeerId + let publicKeyDer: Uint8Array // a buffer before(async () => { const encoded = uint8ArrayFromString(sample.privKey, 'base64pad') - peer = await PeerId.createFromPrivKey(encoded) + peer = await createFromPrivKey(await unmarshalPrivateKey(encoded)) }) it('decoded public key', async () => { + if (peer.publicKey == null) { + throw new Error('PublicKey missing from PeerId') + } + + if (peer.privateKey == null) { + throw new Error('PrivateKey missing from PeerId') + } + // get protobuf version of the public key - const publicKeyProtobuf = peer.marshalPubKey() - const publicKey = crypto.keys.unmarshalPublicKey(publicKeyProtobuf) + const publicKeyProtobuf = peer.publicKey + const publicKey = unmarshalPublicKey(publicKeyProtobuf) publicKeyDer = publicKey.marshal() // get protobuf version of the private key - const privateKeyProtobuf = peer.marshalPrivKey() - const key = await crypto.keys.unmarshalPrivateKey(privateKeyProtobuf) + const privateKeyProtobuf = peer.privateKey + const key = await unmarshalPrivateKey(privateKeyProtobuf) expect(key).to.exist() }) it('encoded public key with DER', async () => { - const jwk = rsaUtils.pkixToJwk(publicKeyDer) - const rsa = new rsaClass.RsaPublicKey(jwk) + const rsa = await supportedKeys.rsa.unmarshalRsaPublicKey(publicKeyDer) const keyId = await rsa.hash() const kids = base58btc.encode(keyId).substring(1) - expect(kids).to.equal(peer.toB58String()) + expect(kids).to.equal(peer.toString()) }) it('encoded public key with JWT', async () => { @@ -52,16 +57,20 @@ describe('peer ID', () => { alg: 'RS256', kid: '2011-04-29' } - const rsa = new rsaClass.RsaPublicKey(jwk) + const rsa = new supportedKeys.rsa.RsaPublicKey(jwk) const keyId = await rsa.hash() const kids = base58btc.encode(keyId).substring(1) - expect(kids).to.equal(peer.toB58String()) + expect(kids).to.equal(peer.toString()) }) it('decoded private key', async () => { + if (peer.privateKey == null) { + throw new Error('PrivateKey missing from PeerId') + } + // get protobuf version of the private key - const privateKeyProtobuf = peer.marshalPrivKey() - const key = await crypto.keys.unmarshalPrivateKey(privateKeyProtobuf) + const privateKeyProtobuf = peer.privateKey + const key = await unmarshalPrivateKey(privateKeyProtobuf) expect(key).to.exist() }) }) diff --git a/test/metrics/index.node.js b/test/metrics/index.node.js deleted file mode 100644 index cdd43e7e88..0000000000 --- a/test/metrics/index.node.js +++ /dev/null @@ -1,146 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') - -const { randomBytes } = require('libp2p-crypto') -const pipe = require('it-pipe') -const concat = require('it-concat') -const delay = require('delay') - -const { createPeer } = require('../utils/creators/peer') -const baseOptions = require('../utils/base-options') - -describe('libp2p.metrics', () => { - let libp2p - - afterEach(async () => { - libp2p && await libp2p.stop() - }) - - it('should disable metrics by default', async () => { - [libp2p] = await createPeer({ - config: { - modules: baseOptions.modules - } - }) - - expect(libp2p.metrics).to.not.exist() - }) - - it('should start/stop metrics on startup/shutdown when enabled', async () => { - const config = { - ...baseOptions, - connectionManager: { - movingAverageIntervals: [10] - }, - metrics: { - enabled: true, - computeThrottleMaxQueueSize: 1, // compute after every message - movingAverageIntervals: [10] - } - } - ;[libp2p] = await createPeer({ started: false, config }) - - expect(libp2p.metrics).to.exist() - sinon.spy(libp2p.metrics, 'start') - sinon.spy(libp2p.metrics, 'stop') - - await libp2p.start() - expect(libp2p.metrics.start).to.have.property('callCount', 1) - - await libp2p.stop() - expect(libp2p.metrics.stop).to.have.property('callCount', 1) - }) - - it('should record metrics on connections and streams when enabled', async () => { - const config = { - ...baseOptions, - connectionManager: { - movingAverageIntervals: [10] - }, - metrics: { - enabled: true, - computeThrottleMaxQueueSize: 1, // compute after every message - movingAverageIntervals: [10] - } - } - let remoteLibp2p - ;[libp2p, remoteLibp2p] = await createPeer({ number: 2, config }) - - remoteLibp2p.handle('/echo/1.0.0', ({ stream }) => pipe(stream, stream)) - - const connection = await libp2p.dial(remoteLibp2p.peerId) - const { stream } = await connection.newStream('/echo/1.0.0') - - const bytes = randomBytes(512) - const result = await pipe( - [bytes], - stream, - concat - ) - - // Flush the call stack - await delay(0) - - expect(result).to.have.length(bytes.length) - // Protocol stats should equal the echo size - const protocolStats = libp2p.metrics.forProtocol('/echo/1.0.0').toJSON() - expect(Number(protocolStats.dataReceived)).to.equal(bytes.length) - expect(Number(protocolStats.dataSent)).to.equal(bytes.length) - - // A lot more traffic will be sent over the wire for the peer - const peerStats = libp2p.metrics.forPeer(connection.remotePeer).toJSON() - expect(Number(peerStats.dataReceived)).to.be.at.least(bytes.length) - await remoteLibp2p.stop() - }) - - it('should move disconnected peers to the old peers list', async () => { - const config = { - ...baseOptions, - connectionManager: { - movingAverageIntervals: [10] - }, - metrics: { - enabled: true, - computeThrottleMaxQueueSize: 1, // compute after every message - movingAverageIntervals: [10] - }, - config: { - peerDiscovery: { - autoDial: false - } - } - } - let remoteLibp2p - ;[libp2p, remoteLibp2p] = await createPeer({ number: 2, config }) - - remoteLibp2p.handle('/echo/1.0.0', ({ stream }) => pipe(stream, stream)) - - const connection = await libp2p.dial(remoteLibp2p.peerId) - const { stream } = await connection.newStream('/echo/1.0.0') - - const bytes = randomBytes(512) - await pipe( - [bytes], - stream, - concat - ) - - sinon.spy(libp2p.metrics, 'onPeerDisconnected') - await libp2p.hangUp(connection.remotePeer) - - // Flush call stack - await delay(0) - - expect(libp2p.metrics.onPeerDisconnected).to.have.property('callCount', 1) - expect(libp2p.metrics.peers).to.have.length(0) - - // forPeer should still give us the old peer stats, - // even though its not in the active peer list - const peerStats = libp2p.metrics.forPeer(connection.remotePeer).toJSON() - expect(Number(peerStats.dataReceived)).to.be.at.least(bytes.length) - await remoteLibp2p.stop() - }) -}) diff --git a/test/metrics/index.node.ts b/test/metrics/index.node.ts new file mode 100644 index 0000000000..96871db43c --- /dev/null +++ b/test/metrics/index.node.ts @@ -0,0 +1,187 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import sinon from 'sinon' +import { randomBytes } from '@libp2p/crypto' +import { pipe } from 'it-pipe' +import toBuffer from 'it-to-buffer' +import delay from 'delay' +import { createNode, populateAddressBooks } from '../utils/creators/peer.js' +import { createBaseOptions } from '../utils/base-options.js' +import type { Libp2pNode } from '../../src/libp2p.js' +import type { Libp2pOptions } from '../../src/index.js' +import type { DefaultMetrics } from '../../src/metrics/index.js' + +describe('libp2p.metrics', () => { + let libp2p: Libp2pNode + + afterEach(async () => { + if (libp2p != null) { + await libp2p.stop() + } + }) + + it('should disable metrics by default', async () => { + libp2p = await createNode({ + config: createBaseOptions() + }) + + expect(libp2p.components.getMetrics()).to.be.undefined() + }) + + it('should start/stop metrics on startup/shutdown when enabled', async () => { + const config: Libp2pOptions = createBaseOptions({ + metrics: { + enabled: true, + computeThrottleMaxQueueSize: 1, // compute after every message + movingAverageIntervals: [10] + } + }) + libp2p = await createNode({ started: false, config }) + + const metrics = libp2p.components.getMetrics() as DefaultMetrics + + if (metrics == null) { + throw new Error('Metrics not configured') + } + + const metricsStartSpy = sinon.spy(metrics, 'start') + const metricsStopSpy = sinon.spy(metrics, 'stop') + + await libp2p.start() + expect(metricsStartSpy).to.have.property('callCount', 1) + + await libp2p.stop() + expect(metricsStopSpy).to.have.property('callCount', 1) + }) + + it('should record metrics on connections and streams when enabled', async () => { + let remoteLibp2p: Libp2pNode + ;[libp2p, remoteLibp2p] = await Promise.all([ + createNode({ + config: createBaseOptions({ + metrics: { + enabled: true, + computeThrottleMaxQueueSize: 1, // compute after every message + movingAverageIntervals: [10] + } + }) + }), + createNode({ + config: createBaseOptions({ + metrics: { + enabled: true, + computeThrottleMaxQueueSize: 1, // compute after every message + movingAverageIntervals: [10] + } + }) + }) + ]) + + await populateAddressBooks([libp2p, remoteLibp2p]) + + void remoteLibp2p.handle('/echo/1.0.0', ({ stream }) => { + void pipe(stream, stream) + }) + + const connection = await libp2p.dial(remoteLibp2p.peerId) + const { stream } = await connection.newStream('/echo/1.0.0') + + const bytes = randomBytes(512) + const result = await pipe( + [bytes], + stream, + async (source) => await toBuffer(source) + ) + + // Flush the call stack + await delay(0) + + expect(result).to.have.length(bytes.length) + + const metrics = libp2p.components.getMetrics() + + if (metrics == null) { + throw new Error('Metrics not configured') + } + + // Protocol stats should equal the echo size + const protocolStats = metrics.forProtocol('/echo/1.0.0')?.getSnapshot() + expect(protocolStats?.dataReceived).to.equal(BigInt(bytes.length)) + expect(protocolStats?.dataSent).to.equal(BigInt(bytes.length)) + + // A lot more traffic will be sent over the wire for the peer + const peerStats = metrics.forPeer(connection.remotePeer)?.getSnapshot() + expect(parseInt(peerStats?.dataReceived.toString() ?? '0')).to.be.at.least(bytes.length) + await remoteLibp2p.stop() + }) + + it('should move disconnected peers to the old peers list', async () => { + let remoteLibp2p + ;[libp2p, remoteLibp2p] = await Promise.all([ + createNode({ + config: createBaseOptions({ + metrics: { + enabled: true, + computeThrottleMaxQueueSize: 1, // compute after every message + movingAverageIntervals: [10] + }, + connectionManager: { + autoDial: false + } + }) + }), + createNode({ + config: createBaseOptions({ + metrics: { + enabled: true, + computeThrottleMaxQueueSize: 1, // compute after every message + movingAverageIntervals: [10] + }, + connectionManager: { + autoDial: false + } + }) + }) + ]) + await populateAddressBooks([libp2p, remoteLibp2p]) + + void remoteLibp2p.handle('/echo/1.0.0', ({ stream }) => { + void pipe(stream, stream) + }) + + const connection = await libp2p.dial(remoteLibp2p.peerId) + const { stream } = await connection.newStream('/echo/1.0.0') + + const bytes = randomBytes(512) + await pipe( + [bytes], + stream, + async (source) => await toBuffer(source) + ) + + const metrics = libp2p.components.getMetrics() + + if (metrics == null) { + throw new Error('Metrics not configured') + } + + const peerStats = metrics.forPeer(connection.remotePeer)?.getSnapshot() + expect(parseInt(peerStats?.dataReceived.toString() ?? '0')).to.be.at.least(bytes.length) + + const metricsOnPeerDisconnectedSpy = sinon.spy(metrics, 'onPeerDisconnected') + await libp2p.hangUp(connection.remotePeer) + + // Flush call stack + await delay(0) + + expect(metricsOnPeerDisconnectedSpy).to.have.property('callCount', 1) + + // forPeer should still give us the old peer stats, + // even though its not in the active peer list + const peerStatsAfterHangup = metrics.forPeer(connection.remotePeer)?.getSnapshot() + expect(parseInt(peerStatsAfterHangup?.dataReceived.toString() ?? '0')).to.be.at.least(bytes.length) + + await remoteLibp2p.stop() + }) +}) diff --git a/test/metrics/index.spec.js b/test/metrics/index.spec.js deleted file mode 100644 index 7107e97b38..0000000000 --- a/test/metrics/index.spec.js +++ /dev/null @@ -1,275 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') -const { randomBytes } = require('libp2p-crypto') -const duplexPair = require('it-pair/duplex') -const pipe = require('it-pipe') -const concat = require('it-concat') -const pushable = require('it-pushable') -const { consume } = require('streaming-iterables') -const delay = require('delay') - -const Metrics = require('../../src/metrics') -const Stats = require('../../src/metrics/stats') -const { createPeerId } = require('../utils/creators/peer') - -describe('Metrics', () => { - let peerId - let peerId2 - - before(async () => { - [peerId, peerId2] = await createPeerId({ number: 2 }) - }) - - afterEach(() => { - sinon.restore() - }) - - it('should not track data if not started', async () => { - const [local, remote] = duplexPair() - const metrics = new Metrics({ - computeThrottleMaxQueueSize: 1, // compute after every message - movingAverageIntervals: [10, 100, 1000] - }) - - metrics.trackStream({ - stream: local, - remotePeer: peerId - }) - - // Echo back - pipe(remote, remote) - - const bytes = randomBytes(1024) - - const results = await pipe( - [bytes], - local, - concat - ) - - // Flush the call stack - await delay(0) - - expect(results.length).to.eql(bytes.length) - - expect(metrics.forPeer(peerId)).to.equal(undefined) - expect(metrics.peers).to.eql([]) - const globalStats = metrics.global - expect(globalStats.snapshot.dataReceived.toNumber()).to.equal(0) - expect(globalStats.snapshot.dataSent.toNumber()).to.equal(0) - }) - - it('should be able to track a duplex stream', async () => { - const [local, remote] = duplexPair() - const metrics = new Metrics({ - computeThrottleMaxQueueSize: 1, // compute after every message - movingAverageIntervals: [10, 100, 1000] - }) - - metrics.trackStream({ - stream: local, - remotePeer: peerId - }) - metrics.start() - - // Echo back - pipe(remote, remote) - - const bytes = randomBytes(1024) - const input = (async function * () { - let i = 0 - while (i < 10) { - await delay(10) - yield bytes - i++ - } - })() - - const results = await pipe( - input, - local, - concat - ) - - // Flush the call stack - await delay(0) - - expect(results.length).to.eql(bytes.length * 10) - - const stats = metrics.forPeer(peerId) - expect(metrics.peers).to.eql([peerId.toB58String()]) - expect(stats.snapshot.dataReceived.toNumber()).to.equal(results.length) - expect(stats.snapshot.dataSent.toNumber()).to.equal(results.length) - - const globalStats = metrics.global - expect(globalStats.snapshot.dataReceived.toNumber()).to.equal(results.length) - expect(globalStats.snapshot.dataSent.toNumber()).to.equal(results.length) - }) - - it('should properly track global stats', async () => { - const [local, remote] = duplexPair() - const [local2, remote2] = duplexPair() - const metrics = new Metrics({ - computeThrottleMaxQueueSize: 1, // compute after every message - movingAverageIntervals: [10, 100, 1000] - }) - const protocol = '/echo/1.0.0' - metrics.start() - - // Echo back remotes - pipe(remote, remote) - pipe(remote2, remote2) - - metrics.trackStream({ - stream: local, - remotePeer: peerId, - protocol - }) - metrics.trackStream({ - stream: local2, - remotePeer: peerId2, - protocol - }) - - const bytes = randomBytes(1024) - - await Promise.all([ - pipe([bytes], local, consume), - pipe([bytes], local2, consume) - ]) - - // Flush the call stack - await delay(0) - - expect(metrics.peers).to.eql([peerId.toB58String(), peerId2.toB58String()]) - // Verify global metrics - const globalStats = metrics.global - expect(globalStats.snapshot.dataReceived.toNumber()).to.equal(bytes.length * 2) - expect(globalStats.snapshot.dataSent.toNumber()).to.equal(bytes.length * 2) - - // Verify individual metrics - for (const peer of [peerId, peerId2]) { - const stats = metrics.forPeer(peer) - - expect(stats.snapshot.dataReceived.toNumber()).to.equal(bytes.length) - expect(stats.snapshot.dataSent.toNumber()).to.equal(bytes.length) - } - - // Verify protocol metrics - const protocolStats = metrics.forProtocol(protocol) - expect(metrics.protocols).to.eql([protocol]) - expect(protocolStats.snapshot.dataReceived.toNumber()).to.equal(bytes.length * 2) - expect(protocolStats.snapshot.dataSent.toNumber()).to.equal(bytes.length * 2) - }) - - it('should be able to replace an existing peer', async () => { - const [local, remote] = duplexPair() - const metrics = new Metrics({ - computeThrottleMaxQueueSize: 1, // compute after every message - movingAverageIntervals: [10, 100, 1000] - }) - metrics.start() - - // Echo back remotes - pipe(remote, remote) - - const mockPeer = { - toB58String: () => 'a temporary id' - } - metrics.trackStream({ - stream: local, - remotePeer: mockPeer - }) - - const bytes = randomBytes(1024) - const input = pushable() - - const deferredPromise = pipe(input, local, consume) - - input.push(bytes) - - await delay(0) - - metrics.updatePlaceholder(mockPeer, peerId) - mockPeer.toB58String = peerId.toB58String.bind(peerId) - - input.push(bytes) - input.end() - - await deferredPromise - await delay(0) - - expect(metrics.peers).to.eql([peerId.toB58String()]) - // Verify global metrics - const globalStats = metrics.global - expect(globalStats.snapshot.dataReceived.toNumber()).to.equal(bytes.length * 2) - expect(globalStats.snapshot.dataSent.toNumber()).to.equal(bytes.length * 2) - - // Verify individual metrics - const stats = metrics.forPeer(peerId) - - expect(stats.snapshot.dataReceived.toNumber()).to.equal(bytes.length * 2) - expect(stats.snapshot.dataSent.toNumber()).to.equal(bytes.length * 2) - }) - - it('should only keep track of a set number of disconnected peers', () => { - const spies = [] - const trackedPeers = new Map([...new Array(50)].map((_, index) => { - const stat = new Stats([], { movingAverageIntervals: [] }) - spies.push(sinon.spy(stat, 'stop')) - return [String(index), stat] - })) - - const metrics = new Metrics({ - maxOldPeersRetention: 5 // Only keep track of 5 - }) - - // Clone so trackedPeers isn't modified - metrics._peerStats = new Map(trackedPeers) - - // Disconnect every peer - for (const id of trackedPeers.keys()) { - metrics.onPeerDisconnected({ - toB58String: () => id - }) - } - - // Verify only the last 5 have been retained - expect(metrics.peers).to.have.length(0) - const retainedPeers = [] - for (const id of trackedPeers.keys()) { - const stat = metrics.forPeer({ - toB58String: () => id - }) - if (stat) retainedPeers.push(id) - } - expect(retainedPeers).to.eql(['45', '46', '47', '48', '49']) - - // Verify all stats were stopped - expect(spies).to.have.length(50) - for (const spy of spies) { - expect(spy).to.have.property('callCount', 1) - } - }) - - it('should allow components to track metrics', () => { - const metrics = new Metrics({ - maxOldPeersRetention: 5 // Only keep track of 5 - }) - - expect(metrics.getComponentMetrics()).to.be.empty() - - const component = 'my-component' - const metric = 'some-metric' - const value = 1 - - metrics.updateComponentMetric({ component, metric, value }) - - expect(metrics.getComponentMetrics()).to.have.lengthOf(1) - expect(metrics.getComponentMetrics().get('libp2p').get(component)).to.have.lengthOf(1) - expect(metrics.getComponentMetrics().get('libp2p').get(component).get(metric)).to.equal(value) - }) -}) diff --git a/test/metrics/index.spec.ts b/test/metrics/index.spec.ts new file mode 100644 index 0000000000..c63fe14dfa --- /dev/null +++ b/test/metrics/index.spec.ts @@ -0,0 +1,301 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import sinon from 'sinon' +import { randomBytes } from '@libp2p/crypto' +import { duplexPair } from 'it-pair/duplex' +import { pipe } from 'it-pipe' +import { pushable } from 'it-pushable' +import drain from 'it-drain' +import delay from 'delay' +import { DefaultMetrics } from '../../src/metrics/index.js' +import { DefaultStats } from '../../src/metrics/stats.js' +import { createPeerId } from '../utils/creators/peer.js' +import toBuffer from 'it-to-buffer' +import { createEd25519PeerId } from '@libp2p/peer-id-factory' +import { peerIdFromString } from '@libp2p/peer-id' +import type { PeerId } from '@libp2p/interfaces/peer-id' + +describe('Metrics', () => { + let peerId: PeerId + let peerId2: PeerId + + before(async () => { + peerId = await createPeerId() + peerId2 = await createPeerId() + }) + + afterEach(() => { + sinon.restore() + }) + + it('should not track data if not started', async () => { + const [local, remote] = duplexPair() + const metrics = new DefaultMetrics({ + enabled: true, + computeThrottleMaxQueueSize: 1, // compute after every message + movingAverageIntervals: [10, 100, 1000], + computeThrottleTimeout: 1000, + maxOldPeersRetention: 50 + }) + + metrics.trackStream({ + stream: local, + remotePeer: peerId + }) + + // Echo back + void pipe(remote, remote) + + const bytes = randomBytes(1024) + + const results = await pipe( + [bytes], + local, + async (source) => await toBuffer(source) + ) + + // Flush the call stack + await delay(0) + + expect(results.length).to.equal(bytes.length) + expect(metrics.getPeers()).to.be.empty() + + expect(metrics.forPeer(peerId)).to.equal(undefined) + const snapshot = metrics.globalStats.getSnapshot() + expect(snapshot.dataReceived).to.equal(0n) + expect(snapshot.dataSent).to.equal(0n) + }) + + it('should be able to track a duplex stream', async () => { + const [local, remote] = duplexPair() + const metrics = new DefaultMetrics({ + enabled: true, + computeThrottleMaxQueueSize: 1, // compute after every message + movingAverageIntervals: [10, 100, 1000], + computeThrottleTimeout: 1000, + maxOldPeersRetention: 50 + }) + + await metrics.start() + + metrics.trackStream({ + stream: local, + remotePeer: peerId + }) + + // Echo back + void pipe(remote, remote) + + const bytes = randomBytes(1024) + const input = (async function * () { + let i = 0 + while (i < 10) { + await delay(10) + yield bytes + i++ + } + })() + + const results = await pipe( + input, + local, + async (source) => await toBuffer(source) + ) + + // Flush the call stack + await delay(0) + + expect(results.length).to.eql(bytes.length * 10) + expect(metrics.getPeers()).to.include(peerId.toString()) + + const snapshot = metrics.forPeer(peerId)?.getSnapshot() + expect(snapshot?.dataReceived).to.equal(BigInt(results.length)) + expect(snapshot?.dataSent).to.equal(BigInt(results.length)) + + const globalSnapshot = metrics.globalStats.getSnapshot() + expect(globalSnapshot.dataReceived).to.equal(BigInt(results.length)) + expect(globalSnapshot.dataSent).to.equal(BigInt(results.length)) + }) + + it('should properly track global stats', async () => { + const [local, remote] = duplexPair() + const [local2, remote2] = duplexPair() + const metrics = new DefaultMetrics({ + enabled: true, + computeThrottleMaxQueueSize: 1, // compute after every message + movingAverageIntervals: [10, 100, 1000], + computeThrottleTimeout: 1000, + maxOldPeersRetention: 50 + }) + const protocol = '/echo/1.0.0' + await metrics.start() + + // Echo back remotes + void pipe(remote, remote) + void pipe(remote2, remote2) + + metrics.trackStream({ + stream: local, + remotePeer: peerId, + protocol + }) + metrics.trackStream({ + stream: local2, + remotePeer: peerId2, + protocol + }) + + const bytes = randomBytes(1024) + + await Promise.all([ + pipe([bytes], local, drain), + pipe([bytes], local2, drain) + ]) + + // Flush the call stack + await delay(0) + + expect(metrics.getPeers()).to.eql([peerId.toString(), peerId2.toString()]) + // Verify global metrics + const globalStats = metrics.globalStats.getSnapshot() + expect(globalStats.dataReceived).to.equal(BigInt(bytes.length * 2)) + expect(globalStats.dataSent).to.equal(BigInt(bytes.length * 2)) + + // Verify individual metrics + for (const peer of [peerId, peerId2]) { + const stats = metrics.forPeer(peer)?.getSnapshot() + + expect(stats?.dataReceived).to.equal(BigInt(bytes.length)) + expect(stats?.dataSent).to.equal(BigInt(bytes.length)) + } + + // Verify protocol metrics + const protocolStats = metrics.forProtocol(protocol)?.getSnapshot() + expect(metrics.getProtocols()).to.eql([protocol]) + expect(protocolStats?.dataReceived).to.equal(BigInt(bytes.length * 2)) + expect(protocolStats?.dataSent).to.equal(BigInt(bytes.length * 2)) + }) + + it('should be able to replace an existing peer', async () => { + const [local, remote] = duplexPair() + const metrics = new DefaultMetrics({ + enabled: true, + computeThrottleMaxQueueSize: 1, // compute after every message + movingAverageIntervals: [10, 100, 1000], + computeThrottleTimeout: 1000, + maxOldPeersRetention: 50 + }) + await metrics.start() + + // Echo back remotes + void pipe(remote, remote) + + const mockPeer = await createEd25519PeerId() + + metrics.trackStream({ + stream: local, + remotePeer: mockPeer + }) + + const bytes = randomBytes(1024) + const input = pushable() + + const deferredPromise = pipe(input, local, drain) + + input.push(bytes) + + await delay(0) + + metrics.updatePlaceholder(mockPeer, peerId) + mockPeer.toString = peerId.toString.bind(peerId) + + input.push(bytes) + input.end() + + await deferredPromise + await delay(0) + + expect(metrics.getPeers()).to.eql([peerId.toString()]) + // Verify global metrics + const globalStats = metrics.globalStats.getSnapshot() + expect(globalStats.dataReceived).to.equal(BigInt(bytes.length * 2)) + expect(globalStats.dataSent).to.equal(BigInt(bytes.length * 2)) + + // Verify individual metrics + const stats = metrics.forPeer(peerId)?.getSnapshot() + + expect(stats?.dataReceived).to.equal(BigInt(bytes.length * 2)) + expect(stats?.dataSent).to.equal(BigInt(bytes.length * 2)) + }) + + it.skip('should only keep track of a set number of disconnected peers', async () => { + const spies: sinon.SinonSpy[] = [] + const peerIds = await Promise.all( + new Array(50).fill(0).map(async () => await createEd25519PeerId()) + ) + + const trackedPeers = new Map([...new Array(50)].fill(0).map((_, index) => { + const stat = new DefaultStats({ + enabled: true, + initialCounters: ['dataReceived', 'dataSent'], + computeThrottleMaxQueueSize: 1000, + computeThrottleTimeout: 5000, + movingAverageIntervals: [] + }) + spies.push(sinon.spy(stat, 'stop')) + return [peerIds[index].toString(), stat] + })) + + const metrics = new DefaultMetrics({ + enabled: true, + computeThrottleMaxQueueSize: 1, // compute after every message + movingAverageIntervals: [10, 100, 1000], + computeThrottleTimeout: 1000, + maxOldPeersRetention: 5 // Only keep track of 5 + }) + + // Disconnect every peer + for (const id of trackedPeers.keys()) { + metrics.onPeerDisconnected(peerIdFromString(id)) + } + + // Verify only the last 5 have been retained + expect(metrics.getPeers()).to.have.length(0) + const retainedPeers = [] + for (const id of trackedPeers.keys()) { + const stat = metrics.forPeer(peerIdFromString(id)) + if (stat != null) retainedPeers.push(id) + } + expect(retainedPeers).to.eql(['45', '46', '47', '48', '49']) + + // Verify all stats were stopped + expect(spies).to.have.length(50) + for (const spy of spies) { + expect(spy).to.have.property('callCount', 1) + } + }) + + it('should allow components to track metrics', () => { + const metrics = new DefaultMetrics({ + enabled: true, + computeThrottleMaxQueueSize: 1, // compute after every message + movingAverageIntervals: [10, 100, 1000], + computeThrottleTimeout: 1000, + maxOldPeersRetention: 50 + }) + + expect(metrics.getComponentMetrics()).to.be.empty() + + const system = 'libp2p' + const component = 'my-component' + const metric = 'some-metric' + const value = 1 + + metrics.updateComponentMetric({ system, component, metric, value }) + + expect(metrics.getComponentMetrics()).to.have.lengthOf(1) + expect(metrics.getComponentMetrics().get('libp2p')?.get(component)).to.have.lengthOf(1) + expect(metrics.getComponentMetrics().get('libp2p')?.get(component)?.get(metric)).to.equal(value) + }) +}) diff --git a/test/nat-manager/nat-manager.node.js b/test/nat-manager/nat-manager.node.js deleted file mode 100644 index 9dcf2b574e..0000000000 --- a/test/nat-manager/nat-manager.node.js +++ /dev/null @@ -1,295 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') -const { networkInterfaces } = require('os') -const AddressManager = require('../../src/address-manager') -const TransportManager = require('../../src/transport-manager') -const Transport = require('libp2p-tcp') -const mockUpgrader = require('../utils/mockUpgrader') -const NatManager = require('../../src/nat-manager') -const delay = require('delay') -const peers = require('../fixtures/peers') -const PeerId = require('peer-id') -const { - codes: { ERR_INVALID_PARAMETERS } -} = require('../../src/errors') - -const DEFAULT_ADDRESSES = [ - '/ip4/127.0.0.1/tcp/0', - '/ip4/0.0.0.0/tcp/0' -] - -describe('Nat Manager (TCP)', () => { - const teardown = [] - - async function createNatManager (addrs = DEFAULT_ADDRESSES, natManagerOptions = {}) { - const peerId = await PeerId.createFromJSON(peers[0]) - const addressManager = new AddressManager(peerId, { listen: addrs }) - const transportManager = new TransportManager({ - libp2p: { - peerId, - addressManager, - peerStore: { - addressBook: { - consumePeerRecord: sinon.stub() - } - } - }, - upgrader: mockUpgrader, - onConnection: () => {}, - faultTolerance: TransportManager.FaultTolerance.NO_FATAL - }) - const natManager = new NatManager({ - peerId, - addressManager, - transportManager, - enabled: true, - ...natManagerOptions - }) - - natManager._client = { - externalIp: sinon.stub().resolves('82.3.1.5'), - map: sinon.stub(), - destroy: sinon.stub() - } - - transportManager.add(Transport.prototype[Symbol.toStringTag], Transport) - await transportManager.listen(addressManager.getListenAddrs()) - - teardown.push(async () => { - await natManager.stop() - await transportManager.removeAll() - expect(transportManager._transports.size).to.equal(0) - }) - - return { - natManager, - addressManager, - transportManager - } - } - - afterEach(() => Promise.all(teardown.map(t => t()))) - - it('should map TCP connections to external ports', async () => { - const { - natManager, - addressManager, - transportManager - } = await createNatManager() - - let addressChangedEventFired = false - - addressManager.on('change:addresses', () => { - addressChangedEventFired = true - }) - - natManager._client = { - externalIp: sinon.stub().resolves('82.3.1.5'), - map: sinon.stub(), - destroy: sinon.stub() - } - - let observed = addressManager.getObservedAddrs().map(ma => ma.toString()) - expect(observed).to.be.empty() - - await natManager._start() - - observed = addressManager.getObservedAddrs().map(ma => ma.toString()) - expect(observed).to.not.be.empty() - - const internalPorts = transportManager.getAddrs() - .filter(ma => ma.isThinWaistAddress()) - .map(ma => ma.toOptions()) - .filter(({ host, transport }) => host !== '127.0.0.1' && transport === 'tcp') - .map(({ port }) => port) - - expect(natManager._client.map.called).to.be.true() - - internalPorts.forEach(port => { - expect(natManager._client.map.getCall(0).args[0]).to.include({ - privatePort: port, - protocol: 'TCP' - }) - }) - - expect(addressChangedEventFired).to.be.true() - }) - - it('should not map TCP connections when double-natted', async () => { - const { - natManager, - addressManager - } = await createNatManager() - - natManager._client.externalIp = sinon.stub().resolves('192.168.1.1') - - let observed = addressManager.getObservedAddrs().map(ma => ma.toString()) - expect(observed).to.be.empty() - - await expect(natManager._start()).to.eventually.be.rejectedWith(/double NAT/) - - observed = addressManager.getObservedAddrs().map(ma => ma.toString()) - expect(observed).to.be.empty() - - expect(natManager._client.map.called).to.be.false() - }) - - it('should do nothing when disabled', async () => { - const { - natManager - } = await createNatManager(DEFAULT_ADDRESSES, { - enabled: false - }) - - natManager.start() - - await delay(100) - - expect(natManager._client.externalIp.called).to.be.false() - expect(natManager._client.map.called).to.be.false() - }) - - it('should not map non-ipv4 connections to external ports', async () => { - const { - natManager, - addressManager - } = await createNatManager([ - '/ip6/::/tcp/0' - ]) - - let observed = addressManager.getObservedAddrs().map(ma => ma.toString()) - expect(observed).to.be.empty() - - await natManager._start() - - observed = addressManager.getObservedAddrs().map(ma => ma.toString()) - expect(observed).to.be.empty() - }) - - it('should not map non-ipv6 loopback connections to external ports', async () => { - const { - natManager, - addressManager - } = await createNatManager([ - '/ip6/::1/tcp/0' - ]) - - let observed = addressManager.getObservedAddrs().map(ma => ma.toString()) - expect(observed).to.be.empty() - - await natManager._start() - - observed = addressManager.getObservedAddrs().map(ma => ma.toString()) - expect(observed).to.be.empty() - }) - - it('should not map non-TCP connections to external ports', async () => { - const { - natManager, - addressManager - } = await createNatManager([ - '/ip4/0.0.0.0/utp' - ]) - - let observed = addressManager.getObservedAddrs().map(ma => ma.toString()) - expect(observed).to.be.empty() - - await natManager._start() - - observed = addressManager.getObservedAddrs().map(ma => ma.toString()) - expect(observed).to.be.empty() - }) - - it('should not map loopback connections to external ports', async () => { - const { - natManager, - addressManager - } = await createNatManager([ - '/ip4/127.0.0.1/tcp/0' - ]) - - let observed = addressManager.getObservedAddrs().map(ma => ma.toString()) - expect(observed).to.be.empty() - - await natManager._start() - - observed = addressManager.getObservedAddrs().map(ma => ma.toString()) - expect(observed).to.be.empty() - }) - - it('should not map non-thin-waist connections to external ports', async () => { - const { - natManager, - addressManager - } = await createNatManager([ - '/ip4/0.0.0.0/tcp/0/sctp/0' - ]) - - let observed = addressManager.getObservedAddrs().map(ma => ma.toString()) - expect(observed).to.be.empty() - - await natManager._start() - - observed = addressManager.getObservedAddrs().map(ma => ma.toString()) - expect(observed).to.be.empty() - }) - - it('should specify large enough TTL', () => { - expect(() => { - new NatManager({ ttl: 5 }) // eslint-disable-line no-new - }).to.throw().with.property('code', ERR_INVALID_PARAMETERS) - }) - - it('shuts the nat api down when stopping', async function () { - if (process.env.CI) { - return this.skip('CI environments will not let us map external ports') - } - - function findRoutableAddress () { - const interfaces = networkInterfaces() - - for (const name of Object.keys(interfaces)) { - for (const iface of interfaces[name]) { - // Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses - if (iface.family === 'IPv4' && !iface.internal) { - return iface.address - } - } - } - } - - const addr = findRoutableAddress() - - if (!addr) { - // skip test if no non-loopback address is found - return this.skip() - } - - const { - natManager - } = await createNatManager([ - `/ip4/${addr}/tcp/0` - ], { - // so we don't try to look up the current computer's external address - externalIp: '184.12.31.4' - }) - - // use the actual nat manager client not the stub - delete natManager._client - - await natManager._start() - - const client = natManager._client - expect(client).to.be.ok() - - // ensure the client was stopped - const spy = sinon.spy(client, 'destroy') - - await natManager.stop() - - expect(spy.called).to.be.true() - }) -}) diff --git a/test/nat-manager/nat-manager.node.ts b/test/nat-manager/nat-manager.node.ts new file mode 100644 index 0000000000..55b988499b --- /dev/null +++ b/test/nat-manager/nat-manager.node.ts @@ -0,0 +1,231 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import { DefaultAddressManager } from '../../src/address-manager/index.js' +import { DefaultTransportManager, FAULT_TOLERANCE } from '../../src/transport-manager.js' +import { TCP } from '@libp2p/tcp' +import { mockUpgrader } from '@libp2p/interface-compliance-tests/mocks' +import { NatManager } from '../../src/nat-manager.js' +import delay from 'delay' +import Peers from '../fixtures/peers.js' +import { codes } from '../../src/errors.js' +import { createFromJSON } from '@libp2p/peer-id-factory' +import { Components } from '@libp2p/interfaces/components' +import type { NatAPI } from '@achingbrain/nat-port-mapper' +import { StubbedInstance, stubInterface } from 'ts-sinon' + +const DEFAULT_ADDRESSES = [ + '/ip4/127.0.0.1/tcp/0', + '/ip4/0.0.0.0/tcp/0' +] + +describe('Nat Manager (TCP)', () => { + const teardown: Array<() => Promise> = [] + let client: StubbedInstance + + async function createNatManager (addrs = DEFAULT_ADDRESSES, natManagerOptions = {}) { + const components = new Components({ + peerId: await createFromJSON(Peers[0]), + upgrader: mockUpgrader() + }) + components.setAddressManager(new DefaultAddressManager(components, { listen: addrs })) + components.setTransportManager(new DefaultTransportManager(components, { + faultTolerance: FAULT_TOLERANCE.NO_FATAL + })) + + const natManager = new NatManager(components, { + enabled: true, + keepAlive: true, + ...natManagerOptions + }) + + client = stubInterface() + + natManager._getClient = async () => { + return client + } + + components.getTransportManager().add(new TCP()) + await components.getTransportManager().listen(components.getAddressManager().getListenAddrs()) + + teardown.push(async () => { + await natManager.stop() + await components.getTransportManager().removeAll() + }) + + return { + natManager, + components + } + } + + afterEach(async () => await Promise.all(teardown.map(async t => await t()))) + + it('should map TCP connections to external ports', async () => { + const { + natManager, + components + } = await createNatManager() + + let addressChangedEventFired = false + + components.getAddressManager().addEventListener('change:addresses', () => { + addressChangedEventFired = true + }) + + client.externalIp.resolves('82.3.1.5') + + let observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + + await natManager._start() + + observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.not.be.empty() + + const internalPorts = components.getTransportManager().getAddrs() + .filter(ma => ma.isThinWaistAddress()) + .map(ma => ma.toOptions()) + .filter(({ host, transport }) => host !== '127.0.0.1' && transport === 'tcp') + .map(({ port }) => port) + + expect(client.map.called).to.be.true() + + internalPorts.forEach(port => { + expect(client.map.getCall(0).args[0]).to.include({ + localPort: port, + protocol: 'TCP' + }) + }) + + expect(addressChangedEventFired).to.be.true() + }) + + it('should not map TCP connections when double-natted', async () => { + const { + natManager, + components + } = await createNatManager() + + client.externalIp.resolves('192.168.1.1') + + let observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + + await expect(natManager._start()).to.eventually.be.rejectedWith(/double NAT/) + + observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + + expect(client.map.called).to.be.false() + }) + + it('should do nothing when disabled', async () => { + const { + natManager + } = await createNatManager(DEFAULT_ADDRESSES, { + enabled: false + }) + + natManager.start() + + await delay(100) + + expect(client.externalIp.called).to.be.false() + expect(client.map.called).to.be.false() + }) + + it('should not map non-ipv4 connections to external ports', async () => { + const { + natManager, + components + } = await createNatManager([ + '/ip6/::/tcp/0' + ]) + + let observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + + await natManager._start() + + observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + }) + + it('should not map non-ipv6 loopback connections to external ports', async () => { + const { + natManager, + components + } = await createNatManager([ + '/ip6/::1/tcp/0' + ]) + + let observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + + await natManager._start() + + observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + }) + + it('should not map non-TCP connections to external ports', async () => { + const { + natManager, + components + } = await createNatManager([ + '/ip4/0.0.0.0/utp' + ]) + + let observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + + await natManager._start() + + observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + }) + + it('should not map loopback connections to external ports', async () => { + const { + natManager, + components + } = await createNatManager([ + '/ip4/127.0.0.1/tcp/0' + ]) + + let observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + + await natManager._start() + + observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + }) + + it('should not map non-thin-waist connections to external ports', async () => { + const { + natManager, + components + } = await createNatManager([ + '/ip4/0.0.0.0/tcp/0/sctp/0' + ]) + + let observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + + await natManager._start() + + observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + expect(observed).to.be.empty() + }) + + it('should specify large enough TTL', async () => { + const peerId = await createFromJSON(Peers[0]) + + expect(() => { + // @ts-expect-error invalid parameters + new NatManager(new Components({ peerId }), { ttl: 5 }) // eslint-disable-line no-new + }).to.throw().with.property('code', codes.ERR_INVALID_PARAMETERS) + }) +}) diff --git a/test/peer-discovery/index.node.js b/test/peer-discovery/index.node.js deleted file mode 100644 index cc5618bb4e..0000000000 --- a/test/peer-discovery/index.node.js +++ /dev/null @@ -1,206 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') -const defer = require('p-defer') -const mergeOptions = require('merge-options') - -const Bootstrap = require('libp2p-bootstrap') -const crypto = require('libp2p-crypto') -const KadDht = require('libp2p-kad-dht') -const MulticastDNS = require('libp2p-mdns') -const { Multiaddr } = require('multiaddr') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') - -const Libp2p = require('../../src') -const baseOptions = require('../utils/base-options') -const { createPeerId } = require('../utils/creators/peer') - -const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') - -describe('peer discovery scenarios', () => { - let peerId, remotePeerId1, remotePeerId2 - let libp2p - - before(async () => { - [peerId, remotePeerId1, remotePeerId2] = await createPeerId({ number: 3 }) - }) - - afterEach(async () => { - libp2p && await libp2p.stop() - }) - - it('should ignore self on discovery', async () => { - libp2p = new Libp2p(mergeOptions(baseOptions, { - peerId, - modules: { - peerDiscovery: [MulticastDNS] - } - })) - - await libp2p.start() - const discoverySpy = sinon.spy() - libp2p.on('peer:discovery', discoverySpy) - libp2p._discovery.get('mdns').emit('peer', { id: libp2p.peerId }) - - expect(discoverySpy.called).to.eql(false) - }) - - it('bootstrap should discover all peers in the list', async () => { - const deferred = defer() - - const bootstrappers = [ - `${listenAddr}/p2p/${remotePeerId1.toB58String()}`, - `${listenAddr}/p2p/${remotePeerId2.toB58String()}` - ] - - libp2p = new Libp2p(mergeOptions(baseOptions, { - peerId, - addresses: { - listen: [listenAddr] - }, - modules: { - peerDiscovery: [Bootstrap] - }, - config: { - peerDiscovery: { - autoDial: false, - bootstrap: { - enabled: true, - list: bootstrappers - } - } - } - })) - - const expectedPeers = new Set([ - remotePeerId1.toB58String(), - remotePeerId2.toB58String() - ]) - - libp2p.on('peer:discovery', (peerId) => { - expectedPeers.delete(peerId.toB58String()) - if (expectedPeers.size === 0) { - libp2p.removeAllListeners('peer:discovery') - deferred.resolve() - } - }) - - await libp2p.start() - - return deferred.promise - }) - - it('MulticastDNS should discover all peers on the local network', async () => { - const deferred = defer() - - const getConfig = (peerId) => mergeOptions(baseOptions, { - peerId, - addresses: { - listen: [listenAddr] - }, - modules: { - peerDiscovery: [MulticastDNS] - }, - config: { - peerDiscovery: { - autoDial: false, - mdns: { - enabled: true, - interval: 200, // discover quickly - // use a random tag to prevent CI collision - serviceTag: uint8ArrayToString(crypto.randomBytes(10), 'base16') - } - } - } - }) - - libp2p = new Libp2p(getConfig(peerId)) - const remoteLibp2p1 = new Libp2p(getConfig(remotePeerId1)) - const remoteLibp2p2 = new Libp2p(getConfig(remotePeerId2)) - - const expectedPeers = new Set([ - remotePeerId1.toB58String(), - remotePeerId2.toB58String() - ]) - - libp2p.on('peer:discovery', (peerId) => { - expectedPeers.delete(peerId.toB58String()) - if (expectedPeers.size === 0) { - libp2p.removeAllListeners('peer:discovery') - deferred.resolve() - } - }) - - await Promise.all([ - remoteLibp2p1.start(), - remoteLibp2p2.start(), - libp2p.start() - ]) - - await deferred.promise - - await remoteLibp2p1.stop() - await remoteLibp2p2.stop() - }) - - it('kad-dht should discover other peers', async () => { - const deferred = defer() - - const getConfig = (peerId) => mergeOptions(baseOptions, { - peerId, - addresses: { - listen: [listenAddr] - }, - modules: { - dht: KadDht - }, - config: { - peerDiscovery: { - autoDial: false - }, - dht: { - enabled: true - } - } - }) - - const localConfig = getConfig(peerId) - - libp2p = new Libp2p(localConfig) - - const remoteLibp2p1 = new Libp2p(getConfig(remotePeerId1)) - const remoteLibp2p2 = new Libp2p(getConfig(remotePeerId2)) - - libp2p.on('peer:discovery', (peerId) => { - if (peerId.toB58String() === remotePeerId1.toB58String()) { - libp2p.removeAllListeners('peer:discovery') - deferred.resolve() - } - }) - - await Promise.all([ - libp2p.start(), - remoteLibp2p1.start(), - remoteLibp2p2.start() - ]) - - await libp2p.peerStore.addressBook.set(remotePeerId1, remoteLibp2p1.multiaddrs) - await remoteLibp2p2.peerStore.addressBook.set(remotePeerId1, remoteLibp2p1.multiaddrs) - - // Topology: - // A -> B - // C -> B - await Promise.all([ - libp2p.dial(remotePeerId1), - remoteLibp2p2.dial(remotePeerId1) - ]) - - await deferred.promise - return Promise.all([ - remoteLibp2p1.stop(), - remoteLibp2p2.stop() - ]) - }) -}) diff --git a/test/peer-discovery/index.node.ts b/test/peer-discovery/index.node.ts new file mode 100644 index 0000000000..23af1eb79d --- /dev/null +++ b/test/peer-discovery/index.node.ts @@ -0,0 +1,217 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import sinon from 'sinon' +import defer from 'p-defer' +import { Bootstrap } from '@libp2p/bootstrap' +import { randomBytes } from '@libp2p/crypto' +import { KadDHT } from '@libp2p/kad-dht' +import { MulticastDNS } from '@libp2p/mdns' +import { Multiaddr } from '@multiformats/multiaddr' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { createBaseOptions } from '../utils/base-options.js' +import { createPeerId } from '../utils/creators/peer.js' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' +import { CustomEvent } from '@libp2p/interfaces' +import type { PeerInfo } from '@libp2p/interfaces/peer-info' + +const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') + +describe('peer discovery scenarios', () => { + let peerId: PeerId, remotePeerId1: PeerId, remotePeerId2: PeerId + let libp2p: Libp2pNode + + before(async () => { + [peerId, remotePeerId1, remotePeerId2] = await Promise.all([ + createPeerId(), + createPeerId(), + createPeerId() + ]) + }) + + afterEach(async () => { + if (libp2p != null) { + await libp2p.stop() + } + }) + + it('should ignore self on discovery', async () => { + libp2p = await createLibp2pNode(createBaseOptions({ + peerId, + peerDiscovery: [ + new MulticastDNS() + ] + })) + + await libp2p.start() + const discoverySpy = sinon.spy() + libp2p.addEventListener('peer:discovery', discoverySpy) + libp2p.onDiscoveryPeer(new CustomEvent('peer', { + detail: { + id: libp2p.peerId, + multiaddrs: [], + protocols: [] + } + })) + + expect(discoverySpy.called).to.eql(false) + }) + + it('bootstrap should discover all peers in the list', async () => { + const deferred = defer() + + const bootstrappers = [ + `${listenAddr.toString()}/p2p/${remotePeerId1.toString()}`, + `${listenAddr.toString()}/p2p/${remotePeerId2.toString()}` + ] + + libp2p = await createLibp2pNode(createBaseOptions({ + peerId, + addresses: { + listen: [ + listenAddr.toString() + ] + }, + connectionManager: { + autoDial: false + }, + peerDiscovery: [ + new Bootstrap({ + list: bootstrappers + }) + ] + })) + + const expectedPeers = new Set([ + remotePeerId1.toString(), + remotePeerId2.toString() + ]) + + libp2p.addEventListener('peer:discovery', (evt) => { + const { id } = evt.detail + + expectedPeers.delete(id.toString()) + if (expectedPeers.size === 0) { + libp2p.removeEventListener('peer:discovery') + deferred.resolve() + } + }) + + await libp2p.start() + + return await deferred.promise + }) + + it('MulticastDNS should discover all peers on the local network', async () => { + const deferred = defer() + + // use a random tag to prevent CI collision + const serviceTag = `libp2p-test-${uint8ArrayToString(randomBytes(4), 'base16')}.local` + + const getConfig = (peerId: PeerId) => createBaseOptions({ + peerId, + addresses: { + listen: [ + listenAddr.toString() + ] + }, + peerDiscovery: [ + new MulticastDNS({ + interval: 200, // discover quickly + serviceTag + }) + ], + connectionManager: { + autoDial: false + } + }) + + libp2p = await createLibp2pNode(getConfig(peerId)) + const remoteLibp2p1 = await createLibp2pNode(getConfig(remotePeerId1)) + const remoteLibp2p2 = await createLibp2pNode(getConfig(remotePeerId2)) + + const expectedPeers = new Set([ + remotePeerId1.toString(), + remotePeerId2.toString() + ]) + + libp2p.addEventListener('peer:discovery', (evt) => { + const { id } = evt.detail + + expectedPeers.delete(id.toString()) + + if (expectedPeers.size === 0) { + libp2p.removeEventListener('peer:discovery') + deferred.resolve() + } + }) + + await Promise.all([ + remoteLibp2p1.start(), + remoteLibp2p2.start(), + libp2p.start() + ]) + + await deferred.promise + + await remoteLibp2p1.stop() + await remoteLibp2p2.stop() + }) + + it('kad-dht should discover other peers', async () => { + const deferred = defer() + + const getConfig = (peerId: PeerId) => createBaseOptions({ + peerId, + addresses: { + listen: [ + listenAddr.toString() + ] + }, + connectionManager: { + autoDial: false + }, + dht: new KadDHT() + }) + + const localConfig = getConfig(peerId) + + libp2p = await createLibp2pNode(localConfig) + + const remoteLibp2p1 = await createLibp2pNode(getConfig(remotePeerId1)) + const remoteLibp2p2 = await createLibp2pNode(getConfig(remotePeerId2)) + + libp2p.addEventListener('peer:discovery', (evt) => { + const { id } = evt.detail + + if (id.equals(remotePeerId1)) { + libp2p.removeEventListener('peer:discovery') + deferred.resolve() + } + }) + + await Promise.all([ + libp2p.start(), + remoteLibp2p1.start(), + remoteLibp2p2.start() + ]) + + await libp2p.peerStore.addressBook.set(remotePeerId1, remoteLibp2p1.getMultiaddrs()) + await remoteLibp2p2.peerStore.addressBook.set(remotePeerId1, remoteLibp2p1.getMultiaddrs()) + + // Topology: + // A -> B + // C -> B + await Promise.all([ + libp2p.dial(remotePeerId1), + remoteLibp2p2.dial(remotePeerId1) + ]) + + await deferred.promise + return await Promise.all([ + remoteLibp2p1.stop(), + remoteLibp2p2.stop() + ]) + }) +}) diff --git a/test/peer-discovery/index.spec.js b/test/peer-discovery/index.spec.js deleted file mode 100644 index 1a43d9f7e8..0000000000 --- a/test/peer-discovery/index.spec.js +++ /dev/null @@ -1,137 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') - -const defer = require('p-defer') -const mergeOptions = require('merge-options') - -const { Multiaddr } = require('multiaddr') -const WebRTCStar = require('libp2p-webrtc-star') - -const Libp2p = require('../../src') -const baseOptions = require('../utils/base-options.browser') -const { createPeerId } = require('../utils/creators/peer') - -describe('peer discovery', () => { - describe('basic functions', () => { - let peerId - let remotePeerId - let libp2p - - before(async () => { - [peerId, remotePeerId] = await createPeerId({ number: 2 }) - }) - - afterEach(async () => { - libp2p && await libp2p.stop() - sinon.reset() - }) - - it('should dial know peers on startup below the minConnections watermark', async () => { - libp2p = new Libp2p({ - ...baseOptions, - peerId, - connectionManager: { - minConnections: 2 - } - }) - - await libp2p.peerStore.addressBook.set(remotePeerId, [new Multiaddr('/ip4/165.1.1.1/tcp/80')]) - - const deferred = defer() - sinon.stub(libp2p.dialer, 'connectToPeer').callsFake((remotePeerId) => { - expect(remotePeerId).to.equal(remotePeerId) - deferred.resolve() - }) - const spy = sinon.spy() - libp2p.on('peer:discovery', spy) - - libp2p.start() - await deferred.promise - - expect(spy.calledOnce).to.eql(true) - expect(spy.getCall(0).args[0].toString()).to.eql(remotePeerId.toString()) - }) - - it('should stop discovery on libp2p start/stop', async () => { - const mockDiscovery = { - tag: 'mock', - start: () => {}, - stop: () => {}, - on: () => {}, - removeListener: () => {} - } - const startSpy = sinon.spy(mockDiscovery, 'start') - const stopSpy = sinon.spy(mockDiscovery, 'stop') - - libp2p = new Libp2p(mergeOptions(baseOptions, { - peerId, - modules: { - peerDiscovery: [mockDiscovery] - } - })) - - await libp2p.start() - expect(startSpy).to.have.property('callCount', 1) - expect(stopSpy).to.have.property('callCount', 0) - await libp2p.stop() - expect(startSpy).to.have.property('callCount', 1) - expect(stopSpy).to.have.property('callCount', 1) - }) - }) - - describe('discovery modules from transports', () => { - let peerId, libp2p - - before(async () => { - [peerId] = await createPeerId() - }) - - afterEach(async () => { - libp2p && await libp2p.stop() - }) - - it('should add discovery module if present in transports and enabled', async () => { - libp2p = new Libp2p(mergeOptions(baseOptions, { - peerId, - modules: { - transport: [WebRTCStar] - }, - config: { - peerDiscovery: { - webRTCStar: { - enabled: true - } - } - } - })) - - await libp2p.start() - - expect(libp2p._discovery.size).to.eql(1) - expect(libp2p._discovery.has('webRTCStar')).to.eql(true) - }) - - it('should not add discovery module if present in transports but disabled', async () => { - libp2p = new Libp2p(mergeOptions(baseOptions, { - peerId, - modules: { - transport: [WebRTCStar] - }, - config: { - peerDiscovery: { - webRTCStar: { - enabled: false - } - } - } - })) - - await libp2p.start() - - expect(libp2p._discovery.size).to.eql(0) - }) - }) -}) diff --git a/test/peer-discovery/index.spec.ts b/test/peer-discovery/index.spec.ts new file mode 100644 index 0000000000..f58fded8e5 --- /dev/null +++ b/test/peer-discovery/index.spec.ts @@ -0,0 +1,101 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import sinon from 'sinon' +import defer from 'p-defer' +import { Multiaddr } from '@multiformats/multiaddr' +import { createBaseOptions } from '../utils/base-options.browser.js' +import { createPeerId } from '../utils/creators/peer.js' +import { isPeerId, PeerId } from '@libp2p/interfaces/peer-id' +import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' +import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-compliance-tests/mocks' + +describe('peer discovery', () => { + describe('basic functions', () => { + let peerId: PeerId + let remotePeerId: PeerId + let libp2p: Libp2pNode + + before(async () => { + [peerId, remotePeerId] = await Promise.all([ + createPeerId(), + createPeerId() + ]) + }) + + afterEach(async () => { + if (libp2p != null) { + await libp2p.stop() + } + + sinon.reset() + }) + + it('should dial known peers on startup below the minConnections watermark', async () => { + libp2p = await createLibp2pNode(createBaseOptions({ + peerId, + connectionManager: { + minConnections: 2 + } + })) + + await libp2p.peerStore.addressBook.set(remotePeerId, [new Multiaddr('/ip4/165.1.1.1/tcp/80')]) + + const deferred = defer() + sinon.stub(libp2p.components.getDialer(), 'dial').callsFake(async (id) => { + if (!isPeerId(id)) { + throw new Error('Tried to dial something that was not a peer ID') + } + + if (!remotePeerId.equals(id)) { + throw new Error('Tried to dial wrong peer ID') + } + + deferred.resolve() + return mockConnection(mockMultiaddrConnection(mockDuplex(), id)) + }) + + const spy = sinon.spy() + libp2p.addEventListener('peer:discovery', spy) + + await libp2p.start() + await deferred.promise + + expect(spy.calledOnce).to.equal(true) + expect(spy.getCall(0).args[0].detail.id.toString()).to.equal(remotePeerId.toString()) + }) + + it('should stop discovery on libp2p start/stop', async () => { + let started = 0 + let stopped = 0 + + class MockDiscovery { + static tag = 'mock' + start () { + started++ + } + + stop () { + stopped++ + } + + addEventListener () {} + removeEventListener () {} + } + + libp2p = await createLibp2pNode(createBaseOptions({ + peerId, + peerDiscovery: [ + new MockDiscovery() + ] + })) + + await libp2p.start() + expect(started).to.equal(1) + expect(stopped).to.equal(0) + await libp2p.stop() + expect(started).to.equal(1) + expect(stopped).to.equal(1) + }) + }) +}) diff --git a/test/peer-routing/peer-routing.node.js b/test/peer-routing/peer-routing.node.js deleted file mode 100644 index 29492bd6ce..0000000000 --- a/test/peer-routing/peer-routing.node.js +++ /dev/null @@ -1,665 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const nock = require('nock') -const sinon = require('sinon') -const intoStream = require('into-stream') - -const delay = require('delay') -const pDefer = require('p-defer') -const pWaitFor = require('p-wait-for') -const mergeOptions = require('merge-options') -const drain = require('it-drain') -const all = require('it-all') - -const ipfsHttpClient = require('ipfs-http-client') -const DelegatedPeerRouter = require('libp2p-delegated-peer-routing') -const { Multiaddr } = require('multiaddr') -const PeerId = require('peer-id') - -const peerUtils = require('../utils/creators/peer') -const { baseOptions, routingOptions } = require('./utils') - -describe('peer-routing', () => { - describe('no routers', () => { - let node - - before(async () => { - [node] = await peerUtils.createPeer({ - config: baseOptions - }) - }) - - after(() => node.stop()) - - it('.findPeer should return an error', async () => { - await expect(node.peerRouting.findPeer('a cid')) - .to.eventually.be.rejected() - .and.to.have.property('code', 'ERR_NO_ROUTERS_AVAILABLE') - }) - - it('.getClosestPeers should return an error', async () => { - try { - for await (const _ of node.peerRouting.getClosestPeers('a cid')) { } // eslint-disable-line - throw new Error('.getClosestPeers should return an error') - } catch (/** @type {any} */ err) { - expect(err).to.exist() - expect(err.code).to.equal('ERR_NO_ROUTERS_AVAILABLE') - } - }) - }) - - describe('via dht router', () => { - const number = 5 - let nodes - - before(async () => { - nodes = await peerUtils.createPeer({ - number, - config: routingOptions - }) - - // Ring dial - await Promise.all( - nodes.map((peer, i) => peer.dial(nodes[(i + 1) % number].peerId)) - ) - }) - - after(() => { - sinon.restore() - }) - - after(() => Promise.all(nodes.map((n) => n.stop()))) - - it('should use the nodes dht', async () => { - sinon.stub(nodes[0]._dht, 'findPeer').callsFake(async function * () { - yield { - name: 'FINAL_PEER', - peer: { - id: nodes[1].peerId, - multiaddrs: [] - } - } - }) - - expect(nodes[0]._dht.findPeer.called).to.be.false() - await nodes[0].peerRouting.findPeer(nodes[1].peerId) - expect(nodes[0]._dht.findPeer.called).to.be.true() - nodes[0]._dht.findPeer.restore() - }) - - it('should use the nodes dht to get the closest peers', async () => { - sinon.stub(nodes[0]._dht, 'getClosestPeers').callsFake(async function * () { - yield { - name: 'PEER_RESPONSE', - closer: [{ - id: nodes[1].peerId, - multiaddrs: [] - }] - } - }) - - expect(nodes[0]._dht.getClosestPeers.called).to.be.false() - await drain(nodes[0].peerRouting.getClosestPeers(nodes[1].peerId)) - expect(nodes[0]._dht.getClosestPeers.called).to.be.true() - nodes[0]._dht.getClosestPeers.restore() - }) - - it('should error when peer tries to find itself', async () => { - await expect(nodes[0].peerRouting.findPeer(nodes[0].peerId)) - .to.eventually.be.rejected() - .and.to.have.property('code', 'ERR_FIND_SELF') - }) - - it('should handle error thrown synchronously during find peer', async () => { - const unknownPeers = await peerUtils.createPeerId({ number: 1, fixture: false }) - - nodes[0].peerRouting._routers = [{ - findPeer () { - throw new Error('Thrown sync') - } - }] - - await expect(nodes[0].peerRouting.findPeer(unknownPeers[0])) - .to.eventually.be.rejected() - .and.to.have.property('code', 'ERR_NOT_FOUND') - }) - - it('should handle error thrown asynchronously during find peer', async () => { - const unknownPeers = await peerUtils.createPeerId({ number: 1, fixture: false }) - - nodes[0].peerRouting._routers = [{ - async findPeer () { - throw new Error('Thrown async') - } - }] - - await expect(nodes[0].peerRouting.findPeer(unknownPeers[0])) - .to.eventually.be.rejected() - .and.to.have.property('code', 'ERR_NOT_FOUND') - }) - - it('should handle error thrown asynchronously after delay during find peer', async () => { - const unknownPeers = await peerUtils.createPeerId({ number: 1, fixture: false }) - - nodes[0].peerRouting._routers = [{ - async findPeer () { - await delay(100) - throw new Error('Thrown async after delay') - } - }] - - await expect(nodes[0].peerRouting.findPeer(unknownPeers[0])) - .to.eventually.be.rejected() - .and.to.have.property('code', 'ERR_NOT_FOUND') - }) - - it('should return value when one router errors synchronously and another returns a value', async () => { - const [peer] = await peerUtils.createPeerId({ number: 1, fixture: false }) - - nodes[0].peerRouting._routers = [{ - findPeer () { - throw new Error('Thrown sync') - } - }, { - async findPeer () { - return Promise.resolve({ - id: peer, - multiaddrs: [] - }) - } - }] - - await expect(nodes[0].peerRouting.findPeer(peer)) - .to.eventually.deep.equal({ - id: peer, - multiaddrs: [] - }) - }) - - it('should return value when one router errors asynchronously and another returns a value', async () => { - const [peer] = await peerUtils.createPeerId({ number: 1, fixture: false }) - - nodes[0].peerRouting._routers = [{ - async findPeer () { - throw new Error('Thrown sync') - } - }, { - async findPeer () { - return Promise.resolve({ - id: peer, - multiaddrs: [] - }) - } - }] - - await expect(nodes[0].peerRouting.findPeer(peer)) - .to.eventually.deep.equal({ - id: peer, - multiaddrs: [] - }) - }) - }) - - describe('via delegate router', () => { - let node - let delegate - - beforeEach(async () => { - delegate = new DelegatedPeerRouter(ipfsHttpClient.create({ - host: '0.0.0.0', - protocol: 'http', - port: 60197 - })) - - ;[node] = await peerUtils.createPeer({ - config: mergeOptions(baseOptions, { - modules: { - peerRouting: [delegate] - }, - config: { - dht: { - enabled: false - } - } - }) - }) - }) - - afterEach(() => { - nock.cleanAll() - sinon.restore() - }) - - afterEach(() => node.stop()) - - it('should only have one router', () => { - expect(node.peerRouting._routers).to.have.lengthOf(1) - }) - - it('should use the delegate router to find peers', async () => { - const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) - - sinon.stub(delegate, 'findPeer').callsFake(() => { - return { - id: remotePeerId, - multiaddrs: [] - } - }) - - expect(delegate.findPeer.called).to.be.false() - await node.peerRouting.findPeer(remotePeerId) - expect(delegate.findPeer.called).to.be.true() - delegate.findPeer.restore() - }) - - it('should use the delegate router to get the closest peers', async () => { - const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) - - sinon.stub(delegate, 'getClosestPeers').callsFake(function * () { - yield { - id: remotePeerId, - multiaddrs: [] - } - }) - - expect(delegate.getClosestPeers.called).to.be.false() - await drain(node.peerRouting.getClosestPeers(remotePeerId)) - expect(delegate.getClosestPeers.called).to.be.true() - delegate.getClosestPeers.restore() - }) - - it('should be able to find a peer', async () => { - const peerKey = PeerId.createFromB58String('QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL') - const mockApi = nock('http://0.0.0.0:60197') - .post('/api/v0/dht/findpeer') - .query(true) - .reply(200, `{"Extra":"","ID":"some other id","Responses":null,"Type":0}\n{"Extra":"","ID":"","Responses":[{"Addrs":["/ip4/127.0.0.1/tcp/4001"],"ID":"${peerKey}"}],"Type":2}\n`, [ - 'Content-Type', 'application/json', - 'X-Chunked-Output', '1' - ]) - - const peer = await node.peerRouting.findPeer(peerKey) - - expect(peer.id).to.equal(peerKey) - expect(mockApi.isDone()).to.equal(true) - }) - - it('should error when peer tries to find itself', async () => { - await expect(node.peerRouting.findPeer(node.peerId)) - .to.eventually.be.rejected() - .and.to.have.property('code', 'ERR_FIND_SELF') - }) - - it('should error when a peer cannot be found', async () => { - const peerId = await PeerId.create({ keyType: 'ed25519' }) - const mockApi = nock('http://0.0.0.0:60197') - .post('/api/v0/dht/findpeer') - .query(true) - .reply(200, '{"Extra":"","ID":"some other id","Responses":null,"Type":6}\n{"Extra":"","ID":"yet another id","Responses":null,"Type":0}\n{"Extra":"routing:not found","ID":"","Responses":null,"Type":3}\n', [ - 'Content-Type', 'application/json', - 'X-Chunked-Output', '1' - ]) - - await expect(node.peerRouting.findPeer(peerId)) - .to.eventually.be.rejected() - - expect(mockApi.isDone()).to.equal(true) - }) - - it('should handle errors from the api', async () => { - const peerId = await PeerId.create({ keyType: 'ed25519' }) - const mockApi = nock('http://0.0.0.0:60197') - .post('/api/v0/dht/findpeer') - .query(true) - .reply(502) - - await expect(node.peerRouting.findPeer(peerId)) - .to.eventually.be.rejected() - - expect(mockApi.isDone()).to.equal(true) - }) - - it('should be able to get the closest peers', async () => { - const peerId = await PeerId.create({ keyType: 'ed25519' }) - const closest1 = '12D3KooWLewYMMdGWAtuX852n4rgCWkK7EBn4CWbwwBzhsVoKxk3' - const closest2 = '12D3KooWDtoQbpKhtnWddfj72QmpFvvLDTsBLTFkjvgQm6cde2AK' - - const mockApi = nock('http://0.0.0.0:60197') - .post('/api/v0/dht/query') - .query(true) - .reply(200, - () => intoStream([ - `{"extra":"","id":"${closest1}","responses":[{"ID":"${closest1}","Addrs":["/ip4/127.0.0.1/tcp/63930","/ip4/127.0.0.1/tcp/63930"]}],"type":1}\n`, - `{"extra":"","id":"${closest2}","responses":[{"ID":"${closest2}","Addrs":["/ip4/127.0.0.1/tcp/63506","/ip4/127.0.0.1/tcp/63506"]}],"type":1}\n`, - `{"Extra":"","ID":"${closest2}","Responses":[],"Type":2}\n`, - `{"Extra":"","ID":"${closest1}","Responses":[],"Type":2}\n` - ]), - [ - 'Content-Type', 'application/json', - 'X-Chunked-Output', '1' - ]) - - const closestPeers = await all(node.peerRouting.getClosestPeers(peerId.id, { timeout: 1000 })) - - expect(closestPeers).to.have.length(2) - expect(closestPeers[0].id.toB58String()).to.equal(closest1) - expect(closestPeers[0].multiaddrs).to.have.lengthOf(2) - expect(closestPeers[1].id.toB58String()).to.equal(closest2) - expect(closestPeers[1].multiaddrs).to.have.lengthOf(2) - expect(mockApi.isDone()).to.equal(true) - }) - - it('should handle errors when getting the closest peers', async () => { - const peerId = await PeerId.create({ keyType: 'ed25519' }) - - const mockApi = nock('http://0.0.0.0:60197') - .post('/api/v0/dht/query') - .query(true) - .reply(502, 'Bad Gateway', [ - 'X-Chunked-Output', '1' - ]) - - try { - for await (const _ of node.peerRouting.getClosestPeers(peerId.id)) { } // eslint-disable-line - throw new Error('should handle errors when getting the closest peers') - } catch (/** @type {any} */ err) { - expect(err).to.exist() - } - - expect(mockApi.isDone()).to.equal(true) - }) - }) - - describe('via dht and delegate routers', () => { - let node - let delegate - - beforeEach(async () => { - delegate = new DelegatedPeerRouter(ipfsHttpClient.create({ - host: '0.0.0.0', - protocol: 'http', - port: 60197 - })) - - ;[node] = await peerUtils.createPeer({ - config: mergeOptions(routingOptions, { - modules: { - peerRouting: [delegate] - } - }) - }) - }) - - afterEach(() => { - sinon.restore() - }) - - afterEach(() => node.stop()) - - it('should use the delegate if the dht fails to find the peer', async () => { - const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) - const results = { - id: remotePeerId, - multiaddrs: [] - } - - sinon.stub(node._dht, 'findPeer').callsFake(async function * () {}) - sinon.stub(delegate, 'findPeer').callsFake(() => { - return results - }) - - const peer = await node.peerRouting.findPeer(remotePeerId) - expect(peer).to.eql(results) - }) - - it('should not wait for the dht to return if the delegate does first', async () => { - const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) - const results = { - id: remotePeerId, - multiaddrs: [] - } - - const defer = pDefer() - - sinon.stub(node._dht, 'findPeer').callsFake(async function * () { - yield - await defer.promise - }) - sinon.stub(delegate, 'findPeer').callsFake(() => { - return results - }) - - const peer = await node.peerRouting.findPeer(remotePeerId) - expect(peer).to.eql(results) - - defer.resolve() - }) - - it('should not wait for the delegate to return if the dht does first', async () => { - const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) - const result = { - id: remotePeerId, - multiaddrs: [] - } - - const defer = pDefer() - - sinon.stub(node._dht, 'findPeer').callsFake(async function * () { - yield { - name: 'FINAL_PEER', - peer: result - } - }) - sinon.stub(delegate, 'findPeer').callsFake(async () => { - await defer.promise - }) - - const peer = await node.peerRouting.findPeer(remotePeerId) - expect(peer).to.eql(result) - - defer.resolve() - }) - - it('should store the addresses of the found peer', async () => { - const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) - const result = { - id: remotePeerId, - multiaddrs: [ - new Multiaddr('/ip4/123.123.123.123/tcp/38982') - ] - } - - const spy = sinon.spy(node.peerStore.addressBook, 'add') - - sinon.stub(node._dht, 'findPeer').callsFake(async function * () { - yield { - name: 'FINAL_PEER', - peer: result - } - }) - sinon.stub(delegate, 'findPeer').callsFake(() => {}) - - await node.peerRouting.findPeer(remotePeerId) - - expect(spy.calledWith(result.id, result.multiaddrs)).to.be.true() - }) - - it('should use the delegate if the dht fails to get the closest peer', async () => { - const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) - const results = [{ - id: remotePeerId, - multiaddrs: [] - }] - - sinon.stub(node._dht, 'getClosestPeers').callsFake(function * () { }) - - sinon.stub(delegate, 'getClosestPeers').callsFake(function * () { - yield results[0] - }) - - const closest = await all(node.peerRouting.getClosestPeers('a cid')) - - expect(closest).to.have.length.above(0) - expect(closest).to.eql(results) - }) - - it('should store the addresses of the closest peer', async () => { - const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) - const result = { - id: remotePeerId, - multiaddrs: [ - new Multiaddr('/ip4/123.123.123.123/tcp/38982') - ] - } - - const spy = sinon.spy(node.peerStore.addressBook, 'add') - - sinon.stub(node._dht, 'getClosestPeers').callsFake(function * () { }) - - sinon.stub(delegate, 'getClosestPeers').callsFake(function * () { - yield result - }) - - await drain(node.peerRouting.getClosestPeers('a cid')) - - expect(spy.calledWith(result.id, result.multiaddrs)).to.be.true() - }) - - it('should dedupe closest peers', async () => { - const [remotePeerId] = await peerUtils.createPeerId({ fixture: false }) - const results = [{ - id: remotePeerId, - multiaddrs: [ - new Multiaddr('/ip4/123.123.123.123/tcp/38982') - ] - }] - - sinon.stub(node._dht, 'getClosestPeers').callsFake(function * () { - yield * results - }) - - sinon.stub(delegate, 'getClosestPeers').callsFake(function * () { - yield * results - }) - - const peers = await all(node.peerRouting.getClosestPeers('a cid')) - - expect(peers).to.be.an('array').with.a.lengthOf(1).that.deep.equals(results) - }) - }) - - describe('peer routing refresh manager service', () => { - let node - let peerIds - - before(async () => { - peerIds = await peerUtils.createPeerId({ number: 2 }) - }) - - afterEach(() => { - sinon.restore() - - return node && node.stop() - }) - - it('should be enabled and start by default', async () => { - const results = [ - { id: peerIds[0], multiaddrs: [new Multiaddr('/ip4/30.0.0.1/tcp/2000')] }, - { id: peerIds[1], multiaddrs: [new Multiaddr('/ip4/32.0.0.1/tcp/2000')] } - ] - - ;[node] = await peerUtils.createPeer({ - config: mergeOptions(routingOptions, { - peerRouting: { - refreshManager: { - bootDelay: 100 - } - } - }), - started: false - }) - - sinon.spy(node.peerStore.addressBook, 'add') - sinon.stub(node._dht, 'getClosestPeers').callsFake(function * () { - yield { - name: 'PEER_RESPONSE', - closer: [ - results[0] - ] - } - yield { - name: 'PEER_RESPONSE', - closer: [ - results[1] - ] - } - }) - - await node.start() - - await pWaitFor(() => node._dht.getClosestPeers.callCount === 1) - await pWaitFor(() => node.peerStore.addressBook.add.callCount === results.length) - - const call0 = node.peerStore.addressBook.add.getCall(0) - expect(call0.args[0].equals(results[0].id)) - call0.args[1].forEach((m, index) => { - expect(m.equals(results[0].multiaddrs[index])) - }) - - const call1 = node.peerStore.addressBook.add.getCall(1) - expect(call1.args[0].equals(results[1].id)) - call0.args[1].forEach((m, index) => { - expect(m.equals(results[1].multiaddrs[index])) - }) - }) - - it('should support being disabled', async () => { - [node] = await peerUtils.createPeer({ - config: mergeOptions(routingOptions, { - peerRouting: { - refreshManager: { - bootDelay: 100, - enabled: false - } - } - }), - started: false - }) - - sinon.stub(node._dht, 'getClosestPeers').callsFake(async function * () { - yield - throw new Error('should not be called') - }) - - await node.start() - await delay(100) - - expect(node._dht.getClosestPeers.callCount === 0) - }) - - it('should start and run recurrently on interval', async () => { - [node] = await peerUtils.createPeer({ - config: mergeOptions(routingOptions, { - peerRouting: { - refreshManager: { - interval: 500, - bootDelay: 200 - } - } - }), - started: false - }) - - sinon.stub(node._dht, 'getClosestPeers').callsFake(function * () { - yield { id: peerIds[0], multiaddrs: [new Multiaddr('/ip4/30.0.0.1/tcp/2000')] } - }) - - await node.start() - - // should run more than once - await pWaitFor(() => node._dht.getClosestPeers.callCount === 2) - }) - }) -}) diff --git a/test/peer-routing/peer-routing.node.ts b/test/peer-routing/peer-routing.node.ts new file mode 100644 index 0000000000..db94d64375 --- /dev/null +++ b/test/peer-routing/peer-routing.node.ts @@ -0,0 +1,788 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import nock from 'nock' +import sinon from 'sinon' +import intoStream from 'into-stream' +import delay from 'delay' +import pDefer from 'p-defer' +import pWaitFor from 'p-wait-for' +import drain from 'it-drain' +import all from 'it-all' +import { create as createIpfsHttpClient } from 'ipfs-http-client' +import { DelegatedPeerRouting } from '@libp2p/delegated-peer-routing' +import { Multiaddr } from '@multiformats/multiaddr' +import { createNode, createPeerId, populateAddressBooks } from '../utils/creators/peer.js' +import type { Libp2pNode } from '../../src/libp2p.js' +import { createBaseOptions } from '../utils/base-options.js' +import { createRoutingOptions } from './utils.js' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import { createEd25519PeerId } from '@libp2p/peer-id-factory' +import { EventTypes, MessageType } from '@libp2p/interfaces/dht' +import { peerIdFromString } from '@libp2p/peer-id' +import type { PeerInfo } from '@libp2p/interfaces/peer-info' +import { KadDHT } from '@libp2p/kad-dht' + +describe('peer-routing', () => { + let peerId: PeerId + + beforeEach(async () => { + peerId = await createEd25519PeerId() + }) + + describe('no routers', () => { + let node: Libp2pNode + + before(async () => { + node = await createNode({ + config: createBaseOptions() + }) + }) + + after(async () => await node.stop()) + + it('.findPeer should return an error', async () => { + await expect(node.peerRouting.findPeer(peerId)) + .to.eventually.be.rejected() + .and.to.have.property('code', 'ERR_NO_ROUTERS_AVAILABLE') + }) + + it('.getClosestPeers should return an error', async () => { + try { + for await (const _ of node.peerRouting.getClosestPeers(peerId.toBytes())) { } // eslint-disable-line + throw new Error('.getClosestPeers should return an error') + } catch (err: any) { + expect(err).to.exist() + expect(err.code).to.equal('ERR_NO_ROUTERS_AVAILABLE') + } + }) + }) + + describe('via dht router', () => { + let nodes: Libp2pNode[] + + before(async () => { + nodes = await Promise.all([ + createNode({ config: createRoutingOptions() }), + createNode({ config: createRoutingOptions() }), + createNode({ config: createRoutingOptions() }), + createNode({ config: createRoutingOptions() }), + createNode({ config: createRoutingOptions() }) + ]) + await populateAddressBooks(nodes) + + // Ring dial + await Promise.all( + nodes.map(async (peer, i) => await peer.dial(nodes[(i + 1) % nodes.length].peerId)) + ) + }) + + after(() => { + sinon.restore() + }) + + after(async () => await Promise.all(nodes.map(async (n) => await n.stop()))) + + it('should use the nodes dht', async () => { + if (nodes[0].dht == null) { + throw new Error('DHT not configured') + } + + const dhtFindPeerStub = sinon.stub(nodes[0].dht, 'findPeer').callsFake(async function * () { + yield { + from: nodes[2].peerId, + type: EventTypes.FINAL_PEER, + name: 'FINAL_PEER', + peer: { + id: nodes[1].peerId, + multiaddrs: [], + protocols: [] + } + } + }) + + expect(dhtFindPeerStub.called).to.be.false() + await nodes[0].peerRouting.findPeer(nodes[1].peerId) + expect(dhtFindPeerStub.called).to.be.true() + dhtFindPeerStub.restore() + }) + + it('should use the nodes dht to get the closest peers', async () => { + if (nodes[0].dht == null) { + throw new Error('DHT not configured') + } + + const dhtGetClosestPeersStub = sinon.stub(nodes[0].dht, 'getClosestPeers').callsFake(async function * () { + yield { + from: nodes[2].peerId, + type: EventTypes.PEER_RESPONSE, + name: 'PEER_RESPONSE', + messageName: 'FIND_NODE', + messageType: MessageType.FIND_NODE, + closer: [{ + id: nodes[1].peerId, + multiaddrs: [], + protocols: [] + }], + providers: [] + } + }) + + expect(dhtGetClosestPeersStub.called).to.be.false() + await drain(nodes[0].peerRouting.getClosestPeers(nodes[1].peerId.toBytes())) + expect(dhtGetClosestPeersStub.called).to.be.true() + dhtGetClosestPeersStub.restore() + }) + + it('should error when peer tries to find itself', async () => { + await expect(nodes[0].peerRouting.findPeer(nodes[0].peerId)) + .to.eventually.be.rejected() + .and.to.have.property('code', 'ERR_FIND_SELF') + }) + + it('should handle error thrown synchronously during find peer', async () => { + const unknownPeer = await createPeerId() + + // @ts-expect-error private field + nodes[0].peerRouting.routers = [{ + findPeer () { + throw new Error('Thrown sync') + } + }] + + await expect(nodes[0].peerRouting.findPeer(unknownPeer)) + .to.eventually.be.rejected() + .and.to.have.property('code', 'ERR_NOT_FOUND') + }) + + it('should handle error thrown asynchronously during find peer', async () => { + const unknownPeer = await createPeerId() + + // @ts-expect-error private field + nodes[0].peerRouting.routers = [{ + async findPeer () { + throw new Error('Thrown async') + } + }] + + await expect(nodes[0].peerRouting.findPeer(unknownPeer)) + .to.eventually.be.rejected() + .and.to.have.property('code', 'ERR_NOT_FOUND') + }) + + it('should handle error thrown asynchronously after delay during find peer', async () => { + const unknownPeer = await createPeerId() + + // @ts-expect-error private field + nodes[0].peerRouting.routers = [{ + async findPeer () { + await delay(100) + throw new Error('Thrown async after delay') + } + }] + + await expect(nodes[0].peerRouting.findPeer(unknownPeer)) + .to.eventually.be.rejected() + .and.to.have.property('code', 'ERR_NOT_FOUND') + }) + + it('should return value when one router errors synchronously and another returns a value', async () => { + const peer = await createPeerId() + + // @ts-expect-error private field + nodes[0].peerRouting.routers = [{ + findPeer () { + throw new Error('Thrown sync') + } + }, { + async findPeer () { + return await Promise.resolve({ + id: peer, + multiaddrs: [] + }) + } + }] + + await expect(nodes[0].peerRouting.findPeer(peer)) + .to.eventually.deep.equal({ + id: peer, + multiaddrs: [] + }) + }) + + it('should return value when one router errors asynchronously and another returns a value', async () => { + const peer = await createPeerId() + + // @ts-expect-error private field + nodes[0].peerRouting.routers = [{ + async findPeer () { + throw new Error('Thrown sync') + } + }, { + async findPeer () { + return await Promise.resolve({ + id: peer, + multiaddrs: [] + }) + } + }] + + await expect(nodes[0].peerRouting.findPeer(peer)) + .to.eventually.deep.equal({ + id: peer, + multiaddrs: [] + }) + }) + }) + + describe('via delegate router', () => { + let node: Libp2pNode + let delegate: DelegatedPeerRouting + + beforeEach(async () => { + delegate = new DelegatedPeerRouting(createIpfsHttpClient({ + host: '0.0.0.0', + protocol: 'http', + port: 60197 + })) + + node = await createNode({ + config: createBaseOptions({ + peerRouters: [delegate] + }) + }) + }) + + afterEach(() => { + nock.cleanAll() + sinon.restore() + }) + + afterEach(async () => await node.stop()) + + it('should only have one router', () => { + // @ts-expect-error private field + expect(node.peerRouting.routers).to.have.lengthOf(1) + }) + + it('should use the delegate router to find peers', async () => { + const remotePeerId = await createPeerId() + + const delegateFindPeerStub = sinon.stub(delegate, 'findPeer').callsFake(async function () { + return { + id: remotePeerId, + multiaddrs: [], + protocols: [] + } + }) + + expect(delegateFindPeerStub.called).to.be.false() + await node.peerRouting.findPeer(remotePeerId) + expect(delegateFindPeerStub.called).to.be.true() + delegateFindPeerStub.restore() + }) + + it('should use the delegate router to get the closest peers', async () => { + const remotePeerId = await createPeerId() + + const delegateGetClosestPeersStub = sinon.stub(delegate, 'getClosestPeers').callsFake(async function * () { + yield { + id: remotePeerId, + multiaddrs: [], + protocols: [] + } + }) + + expect(delegateGetClosestPeersStub.called).to.be.false() + await drain(node.peerRouting.getClosestPeers(remotePeerId.toBytes())) + expect(delegateGetClosestPeersStub.called).to.be.true() + delegateGetClosestPeersStub.restore() + }) + + it('should be able to find a peer', async () => { + const peerKey = peerIdFromString('QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL') + const mockApi = nock('http://0.0.0.0:60197') + .post('/api/v0/dht/findpeer') + .query(true) + .reply(200, `{"Extra":"","ID":"some other id","Responses":null,"Type":0}\n{"Extra":"","ID":"","Responses":[{"Addrs":["/ip4/127.0.0.1/tcp/4001"],"ID":"${peerKey.toString()}"}],"Type":2}\n`, [ + 'Content-Type', 'application/json', + 'X-Chunked-Output', '1' + ]) + + const peer = await node.peerRouting.findPeer(peerKey) + + expect(peer.id.toString()).to.equal(peerKey.toString()) + expect(mockApi.isDone()).to.equal(true) + }) + + it('should error when peer tries to find itself', async () => { + await expect(node.peerRouting.findPeer(node.peerId)) + .to.eventually.be.rejected() + .and.to.have.property('code', 'ERR_FIND_SELF') + }) + + it('should error when a peer cannot be found', async () => { + const peerId = await createEd25519PeerId() + const mockApi = nock('http://0.0.0.0:60197') + .post('/api/v0/dht/findpeer') + .query(true) + .reply(200, '{"Extra":"","ID":"some other id","Responses":null,"Type":6}\n{"Extra":"","ID":"yet another id","Responses":null,"Type":0}\n{"Extra":"routing:not found","ID":"","Responses":null,"Type":3}\n', [ + 'Content-Type', 'application/json', + 'X-Chunked-Output', '1' + ]) + + await expect(node.peerRouting.findPeer(peerId)) + .to.eventually.be.rejected() + + expect(mockApi.isDone()).to.equal(true) + }) + + it('should handle errors from the api', async () => { + const peerId = await createEd25519PeerId() + const mockApi = nock('http://0.0.0.0:60197') + .post('/api/v0/dht/findpeer') + .query(true) + .reply(502) + + await expect(node.peerRouting.findPeer(peerId)) + .to.eventually.be.rejected() + + expect(mockApi.isDone()).to.equal(true) + }) + + it('should be able to get the closest peers', async () => { + const peerId = await createEd25519PeerId() + const closest1 = '12D3KooWLewYMMdGWAtuX852n4rgCWkK7EBn4CWbwwBzhsVoKxk3' + const closest2 = '12D3KooWDtoQbpKhtnWddfj72QmpFvvLDTsBLTFkjvgQm6cde2AK' + + const mockApi = nock('http://0.0.0.0:60197') + .post('/api/v0/dht/query') + .query(true) + .reply(200, + () => intoStream([ + `{"Extra":"","id":"${closest1}","Responses":[{"ID":"${closest1}","Addrs":["/ip4/127.0.0.1/tcp/63930","/ip4/127.0.0.1/tcp/63930"]}],"Type":1}\n`, + `{"Extra":"","id":"${closest2}","Responses":[{"ID":"${closest2}","Addrs":["/ip4/127.0.0.1/tcp/63506","/ip4/127.0.0.1/tcp/63506"]}],"Type":1}\n`, + `{"Extra":"","ID":"${closest2}","Responses":[],"Type":2}\n`, + `{"Extra":"","ID":"${closest1}","Responses":[],"Type":2}\n` + ]), + [ + 'Content-Type', 'application/json', + 'X-Chunked-Output', '1' + ]) + + const closestPeers = await all(node.peerRouting.getClosestPeers(peerId.toBytes())) + + expect(closestPeers).to.have.length(2) + expect(closestPeers[0].id.toString()).to.equal(closest1) + expect(closestPeers[0].multiaddrs).to.have.lengthOf(2) + expect(closestPeers[1].id.toString()).to.equal(closest2) + expect(closestPeers[1].multiaddrs).to.have.lengthOf(2) + expect(mockApi.isDone()).to.equal(true) + }) + + it('should handle errors when getting the closest peers', async () => { + const peerId = await createEd25519PeerId() + + const mockApi = nock('http://0.0.0.0:60197') + .post('/api/v0/dht/query') + .query(true) + .reply(502, 'Bad Gateway', [ + 'X-Chunked-Output', '1' + ]) + + await expect(drain(node.peerRouting.getClosestPeers(peerId.toBytes()))).to.eventually.be.rejected() + + expect(mockApi.isDone()).to.equal(true) + }) + }) + + describe('via dht and delegate routers', () => { + let node: Libp2pNode + let delegate: DelegatedPeerRouting + + beforeEach(async () => { + delegate = new DelegatedPeerRouting(createIpfsHttpClient({ + host: '0.0.0.0', + protocol: 'http', + port: 60197 + })) + + node = await createNode({ + config: createRoutingOptions({ + peerRouters: [delegate], + dht: new KadDHT() + }) + }) + }) + + afterEach(() => { + sinon.restore() + }) + + afterEach(async () => await node.stop()) + + it('should use the delegate if the dht fails to find the peer', async () => { + const remotePeerId = await createPeerId() + const results = { + id: remotePeerId, + multiaddrs: [], + protocols: [] + } + + if (node.dht == null) { + throw new Error('DHT not configured') + } + + sinon.stub(node.dht, 'findPeer').callsFake(async function * () {}) + sinon.stub(delegate, 'findPeer').callsFake(async () => { + return results + }) + + const peer = await node.peerRouting.findPeer(remotePeerId) + expect(peer).to.eql(results) + }) + + it('should not wait for the dht to return if the delegate does first', async () => { + const remotePeerId = await createPeerId() + const results = { + id: remotePeerId, + multiaddrs: [], + protocols: [] + } + + if (node.dht == null) { + throw new Error('DHT not configured') + } + + const defer = pDefer() + + sinon.stub(node.dht, 'findPeer').callsFake(async function * () { + yield { + name: 'SENDING_QUERY', + type: EventTypes.SENDING_QUERY, + to: remotePeerId, + messageName: 'FIND_NODE', + messageType: MessageType.FIND_NODE + } + await defer.promise + }) + sinon.stub(delegate, 'findPeer').callsFake(async () => { + return results + }) + + const peer = await node.peerRouting.findPeer(remotePeerId) + expect(peer).to.eql(results) + + defer.resolve() + }) + + it('should not wait for the delegate to return if the dht does first', async () => { + const remotePeerId = await createPeerId() + const result = { + id: remotePeerId, + multiaddrs: [], + protocols: [] + } + + if (node.dht == null) { + throw new Error('DHT not configured') + } + + const defer = pDefer() + + sinon.stub(node.dht, 'findPeer').callsFake(async function * () { + yield { + from: remotePeerId, + name: 'FINAL_PEER', + type: EventTypes.FINAL_PEER, + peer: result + } + }) + sinon.stub(delegate, 'findPeer').callsFake(async () => { + return await defer.promise + }) + + const peer = await node.peerRouting.findPeer(remotePeerId) + expect(peer).to.eql(result) + + defer.resolve(result) + }) + + it('should store the addresses of the found peer', async () => { + const remotePeerId = await createPeerId() + const result = { + id: remotePeerId, + multiaddrs: [ + new Multiaddr('/ip4/123.123.123.123/tcp/38982') + ], + protocols: [] + } + + if (node.dht == null) { + throw new Error('DHT not configured') + } + + const spy = sinon.spy(node.peerStore.addressBook, 'add') + + sinon.stub(node.dht, 'findPeer').callsFake(async function * () { + yield { + from: remotePeerId, + name: 'FINAL_PEER', + type: EventTypes.FINAL_PEER, + peer: result + } + }) + sinon.stub(delegate, 'findPeer').callsFake(async () => { + const deferred = pDefer() + + return await deferred.promise + }) + + await node.peerRouting.findPeer(remotePeerId) + + expect(spy.calledWith(result.id, result.multiaddrs)).to.be.true() + }) + + it('should use the delegate if the dht fails to get the closest peer', async () => { + const remotePeerId = await createPeerId() + const results = [{ + id: remotePeerId, + multiaddrs: [], + protocols: [] + }] + + if (node.dht == null) { + throw new Error('DHT not configured') + } + + sinon.stub(node.dht, 'getClosestPeers').callsFake(async function * () { }) + + sinon.stub(delegate, 'getClosestPeers').callsFake(async function * () { + yield results[0] + }) + + const closest = await all(node.peerRouting.getClosestPeers(remotePeerId.toBytes())) + + expect(closest).to.have.length.above(0) + expect(closest).to.eql(results) + }) + + it('should store the addresses of the closest peer', async () => { + const remotePeerId = await createPeerId() + const result = { + id: remotePeerId, + multiaddrs: [ + new Multiaddr('/ip4/123.123.123.123/tcp/38982') + ], + protocols: [] + } + + if (node.dht == null) { + throw new Error('DHT not configured') + } + + const spy = sinon.spy(node.peerStore.addressBook, 'add') + + sinon.stub(node.dht, 'getClosestPeers').callsFake(async function * () { }) + + sinon.stub(delegate, 'getClosestPeers').callsFake(async function * () { + yield result + }) + + await drain(node.peerRouting.getClosestPeers(remotePeerId.toBytes())) + + expect(spy.calledWith(result.id, result.multiaddrs)).to.be.true() + }) + + it('should dedupe closest peers', async () => { + const remotePeerId = await createPeerId() + const results = [{ + id: remotePeerId, + multiaddrs: [ + new Multiaddr('/ip4/123.123.123.123/tcp/38982') + ], + protocols: [] + }] + + if (node.dht == null) { + throw new Error('DHT not configured') + } + + sinon.stub(node.dht, 'getClosestPeers').callsFake(async function * () { + for (const peer of results) { + yield { + from: remotePeerId, + name: 'FINAL_PEER', + type: EventTypes.FINAL_PEER, + peer + } + } + }) + + sinon.stub(delegate, 'getClosestPeers').callsFake(async function * () { + yield * results + }) + + const peers = await all(node.peerRouting.getClosestPeers(remotePeerId.toBytes())) + + expect(peers).to.be.an('array').with.a.lengthOf(1).that.deep.equals(results) + }) + }) + + describe('peer routing refresh manager service', () => { + let node: Libp2pNode + let peerIds: PeerId[] + + before(async () => { + peerIds = await Promise.all([ + createPeerId(), + createPeerId() + ]) + }) + + afterEach(async () => { + sinon.restore() + + if (node != null) { + await node.stop() + } + }) + + it('should be enabled and start by default', async () => { + const results: PeerInfo[] = [ + { id: peerIds[0], multiaddrs: [new Multiaddr('/ip4/30.0.0.1/tcp/2000')], protocols: [] }, + { id: peerIds[1], multiaddrs: [new Multiaddr('/ip4/32.0.0.1/tcp/2000')], protocols: [] } + ] + + node = await createNode({ + config: createRoutingOptions({ + peerRouting: { + refreshManager: { + enabled: true, + bootDelay: 100 + } + } + }), + started: false + }) + + if (node.dht == null) { + throw new Error('DHT not configured') + } + + const peerStoreAddressBookAddStub = sinon.spy(node.peerStore.addressBook, 'add') + const dhtGetClosestPeersStub = sinon.stub(node.dht, 'getClosestPeers').callsFake(async function * () { + yield { + name: 'PEER_RESPONSE', + type: EventTypes.PEER_RESPONSE, + messageName: 'FIND_NODE', + messageType: MessageType.FIND_NODE, + from: peerIds[0], + closer: [ + results[0] + ], + providers: [] + } + yield { + name: 'PEER_RESPONSE', + type: EventTypes.PEER_RESPONSE, + messageName: 'FIND_NODE', + messageType: MessageType.FIND_NODE, + from: peerIds[0], + closer: [ + results[1] + ], + providers: [] + } + }) + + await node.start() + + await pWaitFor(() => dhtGetClosestPeersStub.callCount === 1) + await pWaitFor(() => peerStoreAddressBookAddStub.callCount === results.length) + + const call0 = peerStoreAddressBookAddStub.getCall(0) + expect(call0.args[0].equals(results[0].id)) + call0.args[1].forEach((m, index) => { + expect(m.equals(results[0].multiaddrs[index])) + }) + + const call1 = peerStoreAddressBookAddStub.getCall(1) + expect(call1.args[0].equals(results[1].id)) + call0.args[1].forEach((m, index) => { + expect(m.equals(results[1].multiaddrs[index])) + }) + }) + + it('should support being disabled', async () => { + node = await createNode({ + config: createRoutingOptions({ + peerRouting: { + refreshManager: { + bootDelay: 100, + enabled: false + } + } + }), + started: false + }) + + if (node.dht == null) { + throw new Error('DHT not configured') + } + + const dhtGetClosestPeersStub = sinon.stub(node.dht, 'getClosestPeers').callsFake(async function * () { + yield { + name: 'SENDING_QUERY', + type: EventTypes.SENDING_QUERY, + to: peerIds[0], + messageName: 'FIND_NODE', + messageType: MessageType.FIND_NODE + } + throw new Error('should not be called') + }) + + await node.start() + await delay(100) + + expect(dhtGetClosestPeersStub.callCount === 0) + }) + + it('should start and run on interval', async () => { + node = await createNode({ + config: createRoutingOptions({ + peerRouting: { + refreshManager: { + interval: 500, + bootDelay: 200 + } + } + }), + started: false + }) + + if (node.dht == null) { + throw new Error('DHT not configured') + } + + const dhtGetClosestPeersStub = sinon.stub(node.dht, 'getClosestPeers').callsFake(async function * () { + yield { + name: 'PEER_RESPONSE', + type: EventTypes.PEER_RESPONSE, + messageName: 'FIND_NODE', + messageType: MessageType.FIND_NODE, + from: peerIds[0], + closer: [ + { id: peerIds[0], multiaddrs: [new Multiaddr('/ip4/30.0.0.1/tcp/2000')], protocols: [] } + ], + providers: [] + } + }) + + await node.start() + + // should run more than once + await pWaitFor(() => dhtGetClosestPeersStub.callCount === 2) + }) + }) +}) diff --git a/test/peer-routing/utils.js b/test/peer-routing/utils.js deleted file mode 100644 index 7b43d05046..0000000000 --- a/test/peer-routing/utils.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict' - -const KadDht = require('libp2p-kad-dht') -const mergeOptions = require('merge-options') -const baseOptions = require('../utils/base-options') - -module.exports.baseOptions = baseOptions - -const routingOptions = mergeOptions(baseOptions, { - modules: { - dht: KadDht - }, - config: { - dht: { - kBucketSize: 20, - enabled: true - } - } -}) - -module.exports.routingOptions = routingOptions diff --git a/test/peer-routing/utils.ts b/test/peer-routing/utils.ts new file mode 100644 index 0000000000..25ae8b23be --- /dev/null +++ b/test/peer-routing/utils.ts @@ -0,0 +1,11 @@ +import { KadDHT } from '@libp2p/kad-dht' +import type { Libp2pOptions } from '../../src/index.js' +import { createBaseOptions } from '../utils/base-options.js' + +export function createRoutingOptions (...overrides: Libp2pOptions[]): Libp2pOptions { + return createBaseOptions({ + dht: new KadDHT({ + kBucketSize: 20 + }) + }, ...overrides) +} diff --git a/test/peer-store/address-book.spec.js b/test/peer-store/address-book.spec.js deleted file mode 100644 index e67a899c73..0000000000 --- a/test/peer-store/address-book.spec.js +++ /dev/null @@ -1,745 +0,0 @@ -'use strict' -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 6] */ - -const { expect } = require('aegir/utils/chai') -const { Buffer } = require('buffer') -const { Multiaddr } = require('multiaddr') -const arrayEquals = require('libp2p-utils/src/array-equals') -const addressSort = require('libp2p-utils/src/address-sort') -const PeerId = require('peer-id') -const pDefer = require('p-defer') -const { MemoryDatastore } = require('datastore-core/memory') -const PeerStore = require('../../src/peer-store') -const Envelope = require('../../src/record/envelope') -const PeerRecord = require('../../src/record/peer-record') -const { mockConnectionGater } = require('../utils/mock-connection-gater') -const peerUtils = require('../utils/creators/peer') -const { - codes: { ERR_INVALID_PARAMETERS } -} = require('../../src/errors') - -/** - * @typedef {import('../../src/peer-store/types').PeerStore} PeerStore - * @typedef {import('../../src/peer-store/types').AddressBook} AddressBook - */ - -const addr1 = new Multiaddr('/ip4/127.0.0.1/tcp/8000') -const addr2 = new Multiaddr('/ip4/20.0.0.1/tcp/8001') -const addr3 = new Multiaddr('/ip4/127.0.0.1/tcp/8002') - -describe('addressBook', () => { - const connectionGater = mockConnectionGater() - let peerId - - before(async () => { - [peerId] = await peerUtils.createPeerId() - }) - - describe('addressBook.set', () => { - /** @type {PeerStore} */ - let peerStore - /** @type {AddressBook} */ - let ab - - beforeEach(() => { - peerStore = new PeerStore({ - peerId, - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - ab = peerStore.addressBook - }) - - afterEach(() => { - peerStore.removeAllListeners() - }) - - it('throws invalid parameters error if invalid PeerId is provided', async () => { - try { - await ab.set('invalid peerId') - } catch (/** @type {any} */ err) { - expect(err.code).to.equal(ERR_INVALID_PARAMETERS) - return - } - throw new Error('invalid peerId should throw error') - }) - - it('throws invalid parameters error if no addresses provided', async () => { - try { - await ab.set(peerId) - } catch (/** @type {any} */ err) { - expect(err.code).to.equal(ERR_INVALID_PARAMETERS) - return - } - throw new Error('no addresses should throw error') - }) - - it('throws invalid parameters error if invalid multiaddrs are provided', async () => { - try { - await ab.set(peerId, ['invalid multiaddr']) - } catch (/** @type {any} */ err) { - expect(err.code).to.equal(ERR_INVALID_PARAMETERS) - return - } - throw new Error('invalid multiaddrs should throw error') - }) - - it('replaces the stored content by default and emit change event', async () => { - const defer = pDefer() - const supportedMultiaddrs = [addr1, addr2] - - peerStore.once('change:multiaddrs', ({ peerId, multiaddrs }) => { - expect(peerId).to.exist() - expect(multiaddrs).to.eql(supportedMultiaddrs) - defer.resolve() - }) - - await ab.set(peerId, supportedMultiaddrs) - const addresses = await ab.get(peerId) - const multiaddrs = addresses.map((mi) => mi.multiaddr) - expect(multiaddrs).to.have.deep.members(supportedMultiaddrs) - - return defer.promise - }) - - it('emits on set if not storing the exact same content', async () => { - const defer = pDefer() - - const supportedMultiaddrsA = [addr1, addr2] - const supportedMultiaddrsB = [addr2] - - let changeCounter = 0 - peerStore.on('change:multiaddrs', () => { - changeCounter++ - if (changeCounter > 1) { - defer.resolve() - } - }) - - // set 1 - await ab.set(peerId, supportedMultiaddrsA) - - // set 2 (same content) - await ab.set(peerId, supportedMultiaddrsB) - const addresses = await ab.get(peerId) - const multiaddrs = addresses.map((mi) => mi.multiaddr) - expect(multiaddrs).to.have.deep.members(supportedMultiaddrsB) - - await defer.promise - }) - - it('does not emit on set if it is storing the exact same content', async () => { - const defer = pDefer() - - const supportedMultiaddrs = [addr1, addr2] - - let changeCounter = 0 - peerStore.on('change:multiaddrs', () => { - changeCounter++ - if (changeCounter > 1) { - defer.reject() - } - }) - - // set 1 - await ab.set(peerId, supportedMultiaddrs) - - // set 2 (same content) - await ab.set(peerId, supportedMultiaddrs) - - // Wait 50ms for incorrect second event - setTimeout(() => { - defer.resolve() - }, 50) - - await defer.promise - }) - }) - - describe('addressBook.add', () => { - /** @type {PeerStore} */ - let peerStore - /** @type {AddressBook} */ - let ab - - beforeEach(() => { - peerStore = new PeerStore({ - peerId, - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - ab = peerStore.addressBook - }) - - afterEach(() => { - peerStore.removeAllListeners() - }) - - it('throws invalid parameters error if invalid PeerId is provided', async () => { - try { - await ab.add('invalid peerId') - } catch (/** @type {any} */ err) { - expect(err.code).to.equal(ERR_INVALID_PARAMETERS) - return - } - throw new Error('invalid peerId should throw error') - }) - - it('throws invalid parameters error if no addresses provided', async () => { - try { - await ab.add(peerId) - } catch (/** @type {any} */ err) { - expect(err.code).to.equal(ERR_INVALID_PARAMETERS) - return - } - throw new Error('no addresses provided should throw error') - }) - - it('throws invalid parameters error if invalid multiaddrs are provided', async () => { - try { - await ab.add(peerId, ['invalid multiaddr']) - } catch (/** @type {any} */ err) { - expect(err.code).to.equal(ERR_INVALID_PARAMETERS) - return - } - throw new Error('invalid multiaddr should throw error') - }) - - it('does not emit event if no addresses are added', async () => { - const defer = pDefer() - - peerStore.on('peer', () => { - defer.reject() - }) - - await ab.add(peerId, []) - - // Wait 50ms for incorrect second event - setTimeout(() => { - defer.resolve() - }, 50) - - await defer.promise - }) - - it('adds the new content and emits change event', async () => { - const defer = pDefer() - - const supportedMultiaddrsA = [addr1, addr2] - const supportedMultiaddrsB = [addr3] - const finalMultiaddrs = supportedMultiaddrsA.concat(supportedMultiaddrsB) - - let changeTrigger = 2 - peerStore.on('change:multiaddrs', ({ multiaddrs }) => { - changeTrigger-- - if (changeTrigger === 0 && arrayEquals(multiaddrs, finalMultiaddrs)) { - defer.resolve() - } - }) - - // Replace - await ab.set(peerId, supportedMultiaddrsA) - let addresses = await ab.get(peerId) - let multiaddrs = addresses.map((mi) => mi.multiaddr) - expect(multiaddrs).to.have.deep.members(supportedMultiaddrsA) - - // Add - await ab.add(peerId, supportedMultiaddrsB) - addresses = await ab.get(peerId) - multiaddrs = addresses.map((mi) => mi.multiaddr) - expect(multiaddrs).to.have.deep.members(finalMultiaddrs) - - return defer.promise - }) - - it('emits on add if the content to add not exists', async () => { - const defer = pDefer() - - const supportedMultiaddrsA = [addr1] - const supportedMultiaddrsB = [addr2] - const finalMultiaddrs = supportedMultiaddrsA.concat(supportedMultiaddrsB) - - let changeCounter = 0 - peerStore.on('change:multiaddrs', () => { - changeCounter++ - if (changeCounter > 1) { - defer.resolve() - } - }) - - // set 1 - await ab.set(peerId, supportedMultiaddrsA) - - // set 2 (content already existing) - await ab.add(peerId, supportedMultiaddrsB) - const addresses = await ab.get(peerId) - const multiaddrs = addresses.map((mi) => mi.multiaddr) - expect(multiaddrs).to.have.deep.members(finalMultiaddrs) - - await defer.promise - }) - - it('does not emit on add if the content to add already exists', async () => { - const defer = pDefer() - - const supportedMultiaddrsA = [addr1, addr2] - const supportedMultiaddrsB = [addr2] - - let changeCounter = 0 - peerStore.on('change:multiaddrs', () => { - changeCounter++ - if (changeCounter > 1) { - defer.reject() - } - }) - - // set 1 - await ab.set(peerId, supportedMultiaddrsA) - - // set 2 (content already existing) - await ab.add(peerId, supportedMultiaddrsB) - - // Wait 50ms for incorrect second event - setTimeout(() => { - defer.resolve() - }, 50) - - await defer.promise - }) - - it('does not add replicated content', async () => { - // set 1 - await ab.set(peerId, [addr1, addr1]) - - const addresses = await ab.get(peerId) - expect(addresses).to.have.lengthOf(1) - }) - }) - - describe('addressBook.get', () => { - /** @type {PeerStore} */ - let peerStore - /** @type {AddressBook} */ - let ab - - beforeEach(() => { - peerStore = new PeerStore({ - peerId, - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - ab = peerStore.addressBook - }) - - it('throws invalid parameters error if invalid PeerId is provided', async () => { - try { - await ab.get('invalid peerId') - } catch (/** @type {any} */ err) { - expect(err.code).to.equal(ERR_INVALID_PARAMETERS) - return - } - throw new Error('invalid peerId should throw error') - }) - - it('returns empty if no multiaddrs are known for the provided peer', async () => { - const addresses = await ab.get(peerId) - - expect(addresses).to.be.empty() - }) - - it('returns the multiaddrs stored', async () => { - const supportedMultiaddrs = [addr1, addr2] - - await ab.set(peerId, supportedMultiaddrs) - - const addresses = await ab.get(peerId) - const multiaddrs = addresses.map((mi) => mi.multiaddr) - expect(multiaddrs).to.have.deep.members(supportedMultiaddrs) - }) - }) - - describe('addressBook.getMultiaddrsForPeer', () => { - /** @type {PeerStore} */ - let peerStore - /** @type {AddressBook} */ - let ab - - beforeEach(() => { - peerStore = new PeerStore({ - peerId, - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - ab = peerStore.addressBook - }) - - it('throws invalid parameters error if invalid PeerId is provided', async () => { - try { - await ab.getMultiaddrsForPeer('invalid peerId') - } catch (/** @type {any} */ err) { - expect(err.code).to.equal(ERR_INVALID_PARAMETERS) - return - } - throw new Error('invalid peerId should throw error') - }) - - it('returns empty if no multiaddrs are known for the provided peer', async () => { - const addresses = await ab.getMultiaddrsForPeer(peerId) - - expect(addresses).to.be.empty() - }) - - it('returns the multiaddrs stored', async () => { - const supportedMultiaddrs = [addr1, addr2] - - await ab.set(peerId, supportedMultiaddrs) - - const multiaddrs = await ab.getMultiaddrsForPeer(peerId) - multiaddrs.forEach((m) => { - expect(m.getPeerId()).to.equal(peerId.toB58String()) - }) - }) - - it('can sort multiaddrs providing a sorter', async () => { - const supportedMultiaddrs = [addr1, addr2] - await ab.set(peerId, supportedMultiaddrs) - - const multiaddrs = await ab.getMultiaddrsForPeer(peerId, addressSort.publicAddressesFirst) - const sortedAddresses = addressSort.publicAddressesFirst(supportedMultiaddrs.map((m) => ({ multiaddr: m }))) - - multiaddrs.forEach((m, index) => { - expect(m.equals(sortedAddresses[index].multiaddr)) - }) - }) - }) - - describe('addressBook.delete', () => { - /** @type {PeerStore} */ - let peerStore - /** @type {AddressBook} */ - let ab - - beforeEach(() => { - peerStore = new PeerStore({ - peerId, - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - ab = peerStore.addressBook - }) - - it('throws invalid parameters error if invalid PeerId is provided', async () => { - try { - await ab.delete('invalid peerId') - } catch (/** @type {any} */ err) { - expect(err.code).to.equal(ERR_INVALID_PARAMETERS) - return - } - throw new Error('invalid peerId should throw error') - }) - - it('does not emit an event if no records exist for the peer', async () => { - const defer = pDefer() - - peerStore.on('change:multiaddrs', () => { - defer.reject() - }) - - await ab.delete(peerId) - - // Wait 50ms for incorrect invalid event - setTimeout(() => { - defer.resolve() - }, 50) - - return defer.promise - }) - - it('emits an event if the record exists', async () => { - const defer = pDefer() - - const supportedMultiaddrs = [addr1, addr2] - await ab.set(peerId, supportedMultiaddrs) - - // Listen after set - peerStore.on('change:multiaddrs', ({ multiaddrs }) => { - expect(multiaddrs.length).to.eql(0) - defer.resolve() - }) - - await ab.delete(peerId) - - return defer.promise - }) - }) - - describe('certified records', () => { - /** @type {PeerStore} */ - let peerStore - /** @type {AddressBook} */ - let ab - - describe('consumes a valid peer record and stores its data', () => { - beforeEach(() => { - peerStore = new PeerStore({ - peerId, - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - ab = peerStore.addressBook - }) - - it('no previous data in AddressBook', async () => { - const multiaddrs = [addr1, addr2] - const peerRecord = new PeerRecord({ - peerId, - multiaddrs - }) - const envelope = await Envelope.seal(peerRecord, peerId) - - // consume peer record - const consumed = await ab.consumePeerRecord(envelope) - expect(consumed).to.eql(true) - - // Validate AddressBook addresses - const addrs = await ab.get(peerId) - expect(addrs).to.exist() - expect(addrs).to.have.lengthOf(multiaddrs.length) - addrs.forEach((addr, index) => { - expect(addr.isCertified).to.eql(true) - expect(multiaddrs[index].equals(addr.multiaddr)).to.eql(true) - }) - }) - - it('emits change:multiaddrs event when adding multiaddrs', async () => { - const defer = pDefer() - const multiaddrs = [addr1, addr2] - const peerRecord = new PeerRecord({ - peerId, - multiaddrs - }) - const envelope = await Envelope.seal(peerRecord, peerId) - - peerStore.once('change:multiaddrs', ({ peerId, multiaddrs }) => { - expect(peerId).to.exist() - expect(multiaddrs).to.eql(multiaddrs) - defer.resolve() - }) - - // consume peer record - const consumed = await ab.consumePeerRecord(envelope) - expect(consumed).to.eql(true) - - return defer.promise - }) - - it('emits change:multiaddrs event with same data currently in AddressBook (not certified)', async () => { - const defer = pDefer() - const multiaddrs = [addr1, addr2] - - // Set addressBook data - await ab.set(peerId, multiaddrs) - - // Validate data exists, but not certified - let addrs = await ab.get(peerId) - expect(addrs).to.exist() - expect(addrs).to.have.lengthOf(multiaddrs.length) - - addrs.forEach((addr, index) => { - expect(addr.isCertified).to.eql(false) - expect(multiaddrs[index].equals(addr.multiaddr)).to.eql(true) - }) - - // Create peer record - const peerRecord = new PeerRecord({ - peerId, - multiaddrs - }) - const envelope = await Envelope.seal(peerRecord, peerId) - - peerStore.once('change:multiaddrs', ({ peerId, multiaddrs }) => { - expect(peerId).to.exist() - expect(multiaddrs).to.eql(multiaddrs) - defer.resolve() - }) - - // consume peer record - const consumed = await ab.consumePeerRecord(envelope) - expect(consumed).to.eql(true) - - // Wait event - await defer.promise - - // Validate data exists and certified - addrs = await ab.get(peerId) - expect(addrs).to.exist() - expect(addrs).to.have.lengthOf(multiaddrs.length) - addrs.forEach((addr, index) => { - expect(addr.isCertified).to.eql(true) - expect(multiaddrs[index].equals(addr.multiaddr)).to.eql(true) - }) - }) - - it('emits change:multiaddrs event with previous partial data in AddressBook (not certified)', async () => { - const defer = pDefer() - const multiaddrs = [addr1, addr2] - - // Set addressBook data - await ab.set(peerId, [addr1]) - - // Validate data exists, but not certified - let addrs = await ab.get(peerId) - expect(addrs).to.exist() - expect(addrs).to.have.lengthOf(1) - expect(addrs[0].isCertified).to.eql(false) - expect(addrs[0].multiaddr.equals(addr1)).to.eql(true) - - // Create peer record - const peerRecord = new PeerRecord({ - peerId, - multiaddrs - }) - const envelope = await Envelope.seal(peerRecord, peerId) - - peerStore.once('change:multiaddrs', ({ peerId, multiaddrs }) => { - expect(peerId).to.exist() - expect(multiaddrs).to.eql(multiaddrs) - defer.resolve() - }) - - // consume peer record - const consumed = await ab.consumePeerRecord(envelope) - expect(consumed).to.eql(true) - - // Wait event - await defer.promise - - // Validate data exists and certified - addrs = await ab.get(peerId) - expect(addrs).to.exist() - expect(addrs).to.have.lengthOf(multiaddrs.length) - addrs.forEach((addr, index) => { - expect(addr.isCertified).to.eql(true) - expect(multiaddrs[index].equals(addr.multiaddr)).to.eql(true) - }) - }) - - it('with previous different data in AddressBook (not certified)', async () => { - const defer = pDefer() - const multiaddrsUncertified = [addr3] - const multiaddrsCertified = [addr1, addr2] - - // Set addressBook data - await ab.set(peerId, multiaddrsUncertified) - - // Validate data exists, but not certified - let addrs = await ab.get(peerId) - expect(addrs).to.exist() - expect(addrs).to.have.lengthOf(multiaddrsUncertified.length) - addrs.forEach((addr, index) => { - expect(addr.isCertified).to.eql(false) - expect(multiaddrsUncertified[index].equals(addr.multiaddr)).to.eql(true) - }) - - // Create peer record - const peerRecord = new PeerRecord({ - peerId, - multiaddrs: multiaddrsCertified - }) - const envelope = await Envelope.seal(peerRecord, peerId) - - peerStore.once('change:multiaddrs', ({ peerId, multiaddrs }) => { - expect(peerId).to.exist() - expect(multiaddrs).to.eql(multiaddrs) - defer.resolve() - }) - - // consume peer record - const consumed = await ab.consumePeerRecord(envelope) - expect(consumed).to.eql(true) - - // Wait event - await defer.promise - - // Validate data exists and certified - addrs = await ab.get(peerId) - expect(addrs).to.exist() - expect(addrs).to.have.lengthOf(multiaddrsCertified.length) - addrs.forEach((addr, index) => { - expect(addr.isCertified).to.eql(true) - expect(multiaddrsCertified[index].equals(addr.multiaddr)).to.eql(true) - }) - }) - }) - - describe('fails to consume invalid peer records', () => { - beforeEach(() => { - peerStore = new PeerStore({ - peerId, - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - ab = peerStore.addressBook - }) - - it('invalid peer record', async () => { - const invalidEnvelope = { - payload: Buffer.from('invalid-peerRecord') - } - - const consumed = await ab.consumePeerRecord(invalidEnvelope) - expect(consumed).to.eql(false) - }) - - it('peer that created the envelope is not the same as the peer record', async () => { - const multiaddrs = [addr1, addr2] - - // Create peer record - const peerId2 = await PeerId.create() - const peerRecord = new PeerRecord({ - peerId: peerId2, - multiaddrs - }) - const envelope = await Envelope.seal(peerRecord, peerId) - - const consumed = await ab.consumePeerRecord(envelope) - expect(consumed).to.eql(false) - }) - - it('does not store an outdated record', async () => { - const multiaddrs = [addr1, addr2] - const peerRecord1 = new PeerRecord({ - peerId, - multiaddrs, - seqNumber: Date.now() - }) - const peerRecord2 = new PeerRecord({ - peerId, - multiaddrs, - seqNumber: Date.now() - 1 - }) - const envelope1 = await Envelope.seal(peerRecord1, peerId) - const envelope2 = await Envelope.seal(peerRecord2, peerId) - - // Consume envelope1 (bigger seqNumber) - let consumed = await ab.consumePeerRecord(envelope1) - expect(consumed).to.eql(true) - - consumed = await ab.consumePeerRecord(envelope2) - expect(consumed).to.eql(false) - }) - - it('empty multiaddrs', async () => { - const peerRecord = new PeerRecord({ - peerId, - multiaddrs: [] - }) - const envelope = await Envelope.seal(peerRecord, peerId) - - const consumed = await ab.consumePeerRecord(envelope) - expect(consumed).to.eql(false) - }) - }) - }) -}) diff --git a/test/peer-store/key-book.spec.js b/test/peer-store/key-book.spec.js deleted file mode 100644 index c5c70db74a..0000000000 --- a/test/peer-store/key-book.spec.js +++ /dev/null @@ -1,114 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') -const { MemoryDatastore } = require('datastore-core/memory') -const PeerStore = require('../../src/peer-store') -const pDefer = require('p-defer') -const peerUtils = require('../utils/creators/peer') -const { - codes: { ERR_INVALID_PARAMETERS } -} = require('../../src/errors') - -/** - * @typedef {import('../../src/peer-store/types').PeerStore} PeerStore - * @typedef {import('../../src/peer-store/types').KeyBook} KeyBook - * @typedef {import('peer-id')} PeerId - */ - -describe('keyBook', () => { - /** @type {PeerId} */ - let peerId - /** @type {PeerStore} */ - let peerStore - /** @type {KeyBook} */ - let kb - /** @type {MemoryDatastore} */ - let datastore - - beforeEach(async () => { - [peerId] = await peerUtils.createPeerId() - datastore = new MemoryDatastore() - peerStore = new PeerStore({ - peerId, - datastore - }) - kb = peerStore.keyBook - }) - - it('throws invalid parameters error if invalid PeerId is provided in set', async () => { - try { - await kb.set('invalid peerId') - } catch (/** @type {any} */ err) { - expect(err.code).to.equal(ERR_INVALID_PARAMETERS) - return - } - throw new Error('invalid peerId should throw error') - }) - - it('throws invalid parameters error if invalid PeerId is provided in get', async () => { - try { - await kb.get('invalid peerId') - } catch (/** @type {any} */ err) { - expect(err.code).to.equal(ERR_INVALID_PARAMETERS) - return - } - throw new Error('invalid peerId should throw error') - }) - - it('stores the peerId in the book and returns the public key', async () => { - // Set PeerId - await kb.set(peerId, peerId.pubKey) - - // Get public key - const pubKey = await kb.get(peerId) - expect(peerId.pubKey.bytes).to.equalBytes(pubKey.bytes) - }) - - it('should not store if already stored', async () => { - const spy = sinon.spy(datastore, 'put') - - // Set PeerId - await kb.set(peerId, peerId.pubKey) - await kb.set(peerId, peerId.pubKey) - - expect(spy).to.have.property('callCount', 1) - }) - - it('should emit an event when setting a key', async () => { - const defer = pDefer() - - peerStore.on('change:pubkey', ({ peerId: id, pubKey }) => { - expect(id.toB58String()).to.equal(peerId.toB58String()) - expect(pubKey.bytes).to.equalBytes(peerId.pubKey.bytes) - defer.resolve() - }) - - // Set PeerId - await kb.set(peerId, peerId.pubKey) - await defer.promise - }) - - it('should not set when key does not match', async () => { - const [edKey] = await peerUtils.createPeerId({ fixture: false, opts: { keyType: 'Ed25519' } }) - - // Set PeerId - await expect(kb.set(edKey, peerId.pubKey)).to.eventually.be.rejectedWith(/bytes do not match/) - }) - - it('should emit an event when deleting a key', async () => { - const defer = pDefer() - - await kb.set(peerId, peerId.pubKey) - - peerStore.on('change:pubkey', ({ peerId: id, pubKey }) => { - expect(id.toB58String()).to.equal(peerId.toB58String()) - expect(pubKey).to.be.undefined() - defer.resolve() - }) - - await kb.delete(peerId) - await defer.promise - }) -}) diff --git a/test/peer-store/metadata-book.spec.js b/test/peer-store/metadata-book.spec.js deleted file mode 100644 index 214a23ca1e..0000000000 --- a/test/peer-store/metadata-book.spec.js +++ /dev/null @@ -1,384 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') -const { MemoryDatastore } = require('datastore-core/memory') -const pDefer = require('p-defer') -const PeerStore = require('../../src/peer-store') - -const peerUtils = require('../utils/creators/peer') -const { - codes: { ERR_INVALID_PARAMETERS } -} = require('../../src/errors') - -/** - * @typedef {import('../../src/peer-store/types').PeerStore} PeerStore - * @typedef {import('../../src/peer-store/types').MetadataBook} MetadataBook - * @typedef {import('peer-id')} PeerId - */ - -describe('metadataBook', () => { - /** @type {PeerId} */ - let peerId - - before(async () => { - [peerId] = await peerUtils.createPeerId() - }) - - describe('metadataBook.set', () => { - /** @type {PeerStore} */ - let peerStore - /** @type {MetadataBook} */ - let mb - - beforeEach(() => { - peerStore = new PeerStore({ - peerId, - datastore: new MemoryDatastore() - }) - mb = peerStore.metadataBook - }) - - afterEach(() => { - peerStore.removeAllListeners() - }) - - it('throws invalid parameters error if invalid PeerId is provided', async () => { - try { - await mb.set('invalid peerId') - } catch (/** @type {any} */ err) { - expect(err.code).to.equal(ERR_INVALID_PARAMETERS) - return - } - throw new Error('invalid peerId should throw error') - }) - - it('throws invalid parameters error if no metadata provided', async () => { - try { - await mb.set(peerId) - } catch (/** @type {any} */ err) { - expect(err.code).to.equal(ERR_INVALID_PARAMETERS) - return - } - throw new Error('no key provided should throw error') - }) - - it('throws invalid parameters error if no value provided', async () => { - try { - await mb.setValue(peerId, 'location') - } catch (/** @type {any} */ err) { - expect(err.code).to.equal(ERR_INVALID_PARAMETERS) - return - } - throw new Error('no value provided should throw error') - }) - - it('throws invalid parameters error if value is not a buffer', async () => { - try { - await mb.setValue(peerId, 'location', 'mars') - } catch (/** @type {any} */ err) { - expect(err.code).to.equal(ERR_INVALID_PARAMETERS) - return - } - throw new Error('invalid value provided should throw error') - }) - - it('stores the content and emit change event', async () => { - const defer = pDefer() - const metadataKey = 'location' - const metadataValue = uint8ArrayFromString('mars') - - peerStore.once('change:metadata', ({ peerId, metadata }) => { - expect(peerId).to.exist() - expect(metadata.get(metadataKey)).to.equalBytes(metadataValue) - defer.resolve() - }) - - await mb.setValue(peerId, metadataKey, metadataValue) - - const value = await mb.getValue(peerId, metadataKey) - expect(value).to.equalBytes(metadataValue) - - const peerMetadata = await mb.get(peerId) - expect(peerMetadata).to.exist() - expect(peerMetadata.get(metadataKey)).to.equalBytes(metadataValue) - - return defer.promise - }) - - it('emits on set if not storing the exact same content', async () => { - const defer = pDefer() - const metadataKey = 'location' - const metadataValue1 = uint8ArrayFromString('mars') - const metadataValue2 = uint8ArrayFromString('saturn') - - let changeCounter = 0 - peerStore.on('change:metadata', () => { - changeCounter++ - if (changeCounter > 1) { - defer.resolve() - } - }) - - // set 1 - await mb.setValue(peerId, metadataKey, metadataValue1) - - // set 2 (same content) - await mb.setValue(peerId, metadataKey, metadataValue2) - - const value = await mb.getValue(peerId, metadataKey) - expect(value).to.equalBytes(metadataValue2) - - const peerMetadata = await mb.get(peerId) - expect(peerMetadata).to.exist() - expect(peerMetadata.get(metadataKey)).to.equalBytes(metadataValue2) - - return defer.promise - }) - - it('does not emit on set if it is storing the exact same content', async () => { - const defer = pDefer() - const metadataKey = 'location' - const metadataValue = uint8ArrayFromString('mars') - - let changeCounter = 0 - peerStore.on('change:metadata', () => { - changeCounter++ - if (changeCounter > 1) { - defer.reject() - } - }) - - // set 1 - await mb.setValue(peerId, metadataKey, metadataValue) - - // set 2 (same content) - await mb.setValue(peerId, metadataKey, metadataValue) - - // Wait 50ms for incorrect second event - setTimeout(() => { - defer.resolve() - }, 50) - - return defer.promise - }) - }) - - describe('metadataBook.get', () => { - /** @type {PeerStore} */ - let peerStore - /** @type {MetadataBook} */ - let mb - - beforeEach(() => { - peerStore = new PeerStore({ - peerId, - datastore: new MemoryDatastore() - }) - mb = peerStore.metadataBook - }) - - it('throws invalid parameters error if invalid PeerId is provided', async () => { - try { - await mb.get('invalid peerId') - } catch (/** @type {any} */ err) { - expect(err.code).to.equal(ERR_INVALID_PARAMETERS) - return - } - throw new Error('invalid peerId should throw error') - }) - - it('returns empty if no metadata is known for the provided peer', async () => { - const metadata = await mb.get(peerId) - - expect(metadata).to.be.empty() - }) - - it('returns the metadata stored', async () => { - const metadataKey = 'location' - const metadataValue = uint8ArrayFromString('mars') - const metadata = new Map() - metadata.set(metadataKey, metadataValue) - - await mb.set(peerId, metadata) - - const peerMetadata = await mb.get(peerId) - expect(peerMetadata).to.exist() - expect(peerMetadata.get(metadataKey)).to.equalBytes(metadataValue) - }) - }) - - describe('metadataBook.getValue', () => { - /** @type {PeerStore} */ - let peerStore - /** @type {MetadataBook} */ - let mb - - beforeEach(() => { - peerStore = new PeerStore({ - peerId, - datastore: new MemoryDatastore() - }) - mb = peerStore.metadataBook - }) - - it('throws invalid parameters error if invalid PeerId is provided', async () => { - try { - await mb.getValue('invalid peerId') - } catch (/** @type {any} */ err) { - expect(err.code).to.equal(ERR_INVALID_PARAMETERS) - return - } - throw new Error('invalid peerId should throw error') - }) - - it('returns undefined if no metadata is known for the provided peer', async () => { - const metadataKey = 'location' - const metadata = await mb.getValue(peerId, metadataKey) - - expect(metadata).to.not.exist() - }) - - it('returns the metadata value stored for the given key', async () => { - const metadataKey = 'location' - const metadataValue = uint8ArrayFromString('mars') - - await mb.setValue(peerId, metadataKey, metadataValue) - - const value = await mb.getValue(peerId, metadataKey) - expect(value).to.exist() - expect(value).to.equalBytes(metadataValue) - }) - - it('returns undefined if no metadata is known for the provided peer and key', async () => { - const metadataKey = 'location' - const metadataBadKey = 'nickname' - const metadataValue = uint8ArrayFromString('mars') - - await mb.setValue(peerId, metadataKey, metadataValue) - - const metadata = await mb.getValue(peerId, metadataBadKey) - expect(metadata).to.not.exist() - }) - }) - - describe('metadataBook.delete', () => { - /** @type {PeerStore} */ - let peerStore - /** @type {MetadataBook} */ - let mb - - beforeEach(() => { - peerStore = new PeerStore({ - peerId, - datastore: new MemoryDatastore() - }) - mb = peerStore.metadataBook - }) - - it('throws invalid parameters error if invalid PeerId is provided', async () => { - try { - await mb.delete('invalid peerId') - } catch (/** @type {any} */ err) { - expect(err.code).to.equal(ERR_INVALID_PARAMETERS) - return - } - throw new Error('invalid peerId should throw error') - }) - - it('should not emit event if no records exist for the peer', async () => { - const defer = pDefer() - - peerStore.on('change:metadata', () => { - defer.reject() - }) - - await mb.delete(peerId) - - // Wait 50ms for incorrect invalid event - setTimeout(() => { - defer.resolve() - }, 50) - - return defer.promise - }) - - it('should emit an event if the record exists for the peer', async () => { - const defer = pDefer() - const metadataKey = 'location' - const metadataValue = uint8ArrayFromString('mars') - - await mb.setValue(peerId, metadataKey, metadataValue) - - // Listen after set - peerStore.on('change:metadata', () => { - defer.resolve() - }) - - await mb.delete(peerId) - - return defer.promise - }) - }) - - describe('metadataBook.deleteValue', () => { - /** @type {PeerStore} */ - let peerStore - /** @type {MetadataBook} */ - let mb - - beforeEach(() => { - peerStore = new PeerStore({ - peerId, - datastore: new MemoryDatastore() - }) - mb = peerStore.metadataBook - }) - - it('throws invalid parameters error if invalid PeerId is provided', async () => { - try { - await mb.deleteValue('invalid peerId') - } catch (/** @type {any} */ err) { - expect(err.code).to.equal(ERR_INVALID_PARAMETERS) - return - } - throw new Error('invalid peerId should throw error') - }) - - it('should not emit event if no records exist for the peer', async () => { - const defer = pDefer() - const metadataKey = 'location' - - peerStore.on('change:metadata', () => { - defer.reject() - }) - - await mb.deleteValue(peerId, metadataKey) - - // Wait 50ms for incorrect invalid event - setTimeout(() => { - defer.resolve() - }, 50) - - return defer.promise - }) - - it('should emit event if a record exists for the peer', async () => { - const defer = pDefer() - const metadataKey = 'location' - const metadataValue = uint8ArrayFromString('mars') - - await mb.setValue(peerId, metadataKey, metadataValue) - - // Listen after set - peerStore.on('change:metadata', () => { - defer.resolve() - }) - - await mb.deleteValue(peerId, metadataKey) - - return defer.promise - }) - }) -}) diff --git a/test/peer-store/peer-store.node.js b/test/peer-store/peer-store.node.js deleted file mode 100644 index c7d14f110d..0000000000 --- a/test/peer-store/peer-store.node.js +++ /dev/null @@ -1,50 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') - -const baseOptions = require('../utils/base-options') -const peerUtils = require('../utils/creators/peer') -const all = require('it-all') - -describe('libp2p.peerStore', () => { - let libp2p, remoteLibp2p - - beforeEach(async () => { - [libp2p, remoteLibp2p] = await peerUtils.createPeer({ - number: 2, - populateAddressBooks: false, - config: { - ...baseOptions - } - }) - }) - - afterEach(() => Promise.all([libp2p, remoteLibp2p].map(l => l.stop()))) - - it('adds peer address to AddressBook and keys to the keybook when establishing connection', async () => { - const remoteIdStr = remoteLibp2p.peerId.toB58String() - - const spyAddressBook = sinon.spy(libp2p.peerStore.addressBook, 'add') - const spyKeyBook = sinon.spy(libp2p.peerStore.keyBook, 'set') - - const remoteMultiaddr = `${remoteLibp2p.multiaddrs[0]}/p2p/${remoteIdStr}` - const conn = await libp2p.dial(remoteMultiaddr) - - expect(conn).to.exist() - expect(spyAddressBook).to.have.property('called', true) - expect(spyKeyBook).to.have.property('called', true) - - const localPeers = await all(libp2p.peerStore.getPeers()) - - expect(localPeers.length).to.equal(1) - - const publicKeyInLocalPeer = localPeers[0].id.pubKey - expect(publicKeyInLocalPeer.bytes).to.equalBytes(remoteLibp2p.peerId.pubKey.bytes) - - const publicKeyInRemotePeer = await remoteLibp2p.peerStore.keyBook.get(libp2p.peerId) - expect(publicKeyInRemotePeer).to.exist() - expect(publicKeyInRemotePeer.bytes).to.equalBytes(libp2p.peerId.pubKey.bytes) - }) -}) diff --git a/test/peer-store/peer-store.spec.js b/test/peer-store/peer-store.spec.js deleted file mode 100644 index d0380025ab..0000000000 --- a/test/peer-store/peer-store.spec.js +++ /dev/null @@ -1,227 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const all = require('it-all') -const PeerStore = require('../../src/peer-store') -const { Multiaddr } = require('multiaddr') -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') -const { MemoryDatastore } = require('datastore-core/memory') -const peerUtils = require('../utils/creators/peer') -const { mockConnectionGater } = require('../utils/mock-connection-gater') - -const addr1 = new Multiaddr('/ip4/127.0.0.1/tcp/8000') -const addr2 = new Multiaddr('/ip4/127.0.0.1/tcp/8001') -const addr3 = new Multiaddr('/ip4/127.0.0.1/tcp/8002') -const addr4 = new Multiaddr('/ip4/127.0.0.1/tcp/8003') - -const proto1 = '/protocol1' -const proto2 = '/protocol2' -const proto3 = '/protocol3' - -/** - * @typedef {import('../../src/peer-store/types').PeerStore} PeerStore - */ - -describe('peer-store', () => { - const connectionGater = mockConnectionGater() - let peerIds - before(async () => { - peerIds = await peerUtils.createPeerId({ - number: 5 - }) - }) - - describe('empty books', () => { - /** @type {PeerStore} */ - let peerStore - - beforeEach(() => { - peerStore = new PeerStore({ - peerId: peerIds[4], - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - }) - - it('has an empty map of peers', async () => { - const peers = await all(peerStore.getPeers()) - expect(peers.length).to.equal(0) - }) - - it('deletes a peerId', async () => { - await peerStore.addressBook.set(peerIds[0], [new Multiaddr('/ip4/127.0.0.1/tcp/4001')]) - await expect(peerStore.has(peerIds[0])).to.eventually.be.true() - await peerStore.delete(peerIds[0]) - await expect(peerStore.has(peerIds[0])).to.eventually.be.false() - }) - - it('sets the peer\'s public key to the KeyBook', async () => { - await peerStore.keyBook.set(peerIds[0], peerIds[0].pubKey) - await expect(peerStore.keyBook.get(peerIds[0])).to.eventually.deep.equal(peerIds[0].pubKey) - }) - }) - - describe('previously populated books', () => { - /** @type {PeerStore} */ - let peerStore - - beforeEach(async () => { - peerStore = new PeerStore({ - peerId: peerIds[4], - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - - // Add peer0 with { addr1, addr2 } and { proto1 } - await peerStore.addressBook.set(peerIds[0], [addr1, addr2]) - await peerStore.protoBook.set(peerIds[0], [proto1]) - - // Add peer1 with { addr3 } and { proto2, proto3 } - await peerStore.addressBook.set(peerIds[1], [addr3]) - await peerStore.protoBook.set(peerIds[1], [proto2, proto3]) - - // Add peer2 with { addr4 } - await peerStore.addressBook.set(peerIds[2], [addr4]) - - // Add peer3 with { addr4 } and { proto2 } - await peerStore.addressBook.set(peerIds[3], [addr4]) - await peerStore.protoBook.set(peerIds[3], [proto2]) - }) - - it('has peers', async () => { - const peers = await all(peerStore.getPeers()) - - expect(peers.length).to.equal(4) - expect(peers.map(peer => peer.id.toB58String())).to.have.members([ - peerIds[0].toB58String(), - peerIds[1].toB58String(), - peerIds[2].toB58String(), - peerIds[3].toB58String() - ]) - }) - - it('deletes a stored peer', async () => { - await peerStore.delete(peerIds[0]) - - const peers = await all(peerStore.getPeers()) - expect(peers.length).to.equal(3) - expect(Array.from(peers.keys())).to.not.have.members([peerIds[0].toB58String()]) - }) - - it('deletes a stored peer which is only on one book', async () => { - await peerStore.delete(peerIds[2]) - - const peers = await all(peerStore.getPeers()) - expect(peers.length).to.equal(3) - }) - - it('gets the stored information of a peer in all its books', async () => { - const peer = await peerStore.get(peerIds[0]) - expect(peer).to.exist() - expect(peer.protocols).to.have.members([proto1]) - - const peerMultiaddrs = peer.addresses.map((mi) => mi.multiaddr) - expect(peerMultiaddrs).to.have.deep.members([addr1, addr2]) - - expect(peer.id.toB58String()).to.equal(peerIds[0].toB58String()) - }) - - it('gets the stored information of a peer that is not present in all its books', async () => { - const peers = await peerStore.get(peerIds[2]) - expect(peers).to.exist() - expect(peers.protocols.length).to.eql(0) - - const peerMultiaddrs = peers.addresses.map((mi) => mi.multiaddr) - expect(peerMultiaddrs).to.have.deep.members([addr4]) - }) - - it('can find all the peers supporting a protocol', async () => { - const peerSupporting2 = [] - - for await (const peer of peerStore.getPeers()) { - if (peer.protocols.includes(proto2)) { - peerSupporting2.push(peer) - } - } - - expect(peerSupporting2.length).to.eql(2) - expect(peerSupporting2[0].id.toB58String()).to.eql(peerIds[1].toB58String()) - expect(peerSupporting2[1].id.toB58String()).to.eql(peerIds[3].toB58String()) - }) - - it('can find all the peers listening on a given address', async () => { - const peerListening4 = [] - - for await (const peer of peerStore.getPeers()) { - const multiaddrs = peer.addresses.map((mi) => mi.multiaddr.toString()) - - if (multiaddrs.includes(addr4.toString())) { - peerListening4.push(peer) - } - } - - expect(peerListening4.length).to.eql(2) - expect(peerListening4[0].id.toB58String()).to.eql(peerIds[2].toB58String()) - expect(peerListening4[1].id.toB58String()).to.eql(peerIds[3].toB58String()) - }) - }) - - describe('peerStore.getPeers', () => { - /** @type {PeerStore} */ - let peerStore - - beforeEach(() => { - peerStore = new PeerStore({ - peerId: peerIds[4], - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - }) - - it('returns peers if only addresses are known', async () => { - await peerStore.addressBook.set(peerIds[0], [addr1]) - - const peers = await all(peerStore.getPeers()) - expect(peers.length).to.equal(1) - - const peerData = peers[0] - expect(peerData).to.exist() - expect(peerData.id).to.exist() - expect(peerData.addresses).to.have.lengthOf(1) - expect(peerData.protocols).to.have.lengthOf(0) - expect(peerData.metadata).to.be.empty() - }) - - it('returns peers if only protocols are known', async () => { - await peerStore.protoBook.set(peerIds[0], [proto1]) - - const peers = await all(peerStore.getPeers()) - expect(peers.length).to.equal(1) - - const peerData = peers[0] - expect(peerData).to.exist() - expect(peerData.id).to.exist() - expect(peerData.addresses).to.have.lengthOf(0) - expect(peerData.protocols).to.have.lengthOf(1) - expect(peerData.metadata).to.be.empty() - }) - - it('returns peers if only metadata is known', async () => { - const metadataKey = 'location' - const metadataValue = uint8ArrayFromString('earth') - await peerStore.metadataBook.setValue(peerIds[0], metadataKey, metadataValue) - - const peers = await all(peerStore.getPeers()) - expect(peers.length).to.equal(1) - - const peerData = peers[0] - expect(peerData).to.exist() - expect(peerData.id).to.exist() - expect(peerData.addresses).to.have.lengthOf(0) - expect(peerData.protocols).to.have.lengthOf(0) - expect(peerData.metadata).to.exist() - expect(peerData.metadata.get(metadataKey)).to.equalBytes(metadataValue) - }) - }) -}) diff --git a/test/peer-store/proto-book.spec.js b/test/peer-store/proto-book.spec.js deleted file mode 100644 index 667ec4aa04..0000000000 --- a/test/peer-store/proto-book.spec.js +++ /dev/null @@ -1,416 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') -const { MemoryDatastore } = require('datastore-core/memory') -const pDefer = require('p-defer') -const pWaitFor = require('p-wait-for') - -const PeerStore = require('../../src/peer-store') - -const peerUtils = require('../utils/creators/peer') -const { - codes: { ERR_INVALID_PARAMETERS } -} = require('../../src/errors') - -/** - * @typedef {import('../../src/peer-store/types').PeerStore} PeerStore - * @typedef {import('../../src/peer-store/types').ProtoBook} ProtoBook - * @typedef {import('peer-id')} PeerId - */ - -const arraysAreEqual = (a, b) => a.length === b.length && a.sort().every((item, index) => b[index] === item) - -describe('protoBook', () => { - /** @type {PeerId} */ - let peerId - - before(async () => { - [peerId] = await peerUtils.createPeerId() - }) - - describe('protoBook.set', () => { - /** @type {PeerStore} */ - let peerStore - /** @type {ProtoBook} */ - let pb - - beforeEach(() => { - peerStore = new PeerStore({ - peerId, - datastore: new MemoryDatastore() - }) - pb = peerStore.protoBook - }) - - afterEach(() => { - peerStore.removeAllListeners() - }) - - it('throws invalid parameters error if invalid PeerId is provided', async () => { - await expect(pb.set('invalid peerId')).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) - }) - - it('throws invalid parameters error if no protocols provided', async () => { - await expect(pb.set(peerId)).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) - }) - - it('replaces the stored content by default and emit change event', async () => { - const defer = pDefer() - const supportedProtocols = ['protocol1', 'protocol2'] - - peerStore.once('change:protocols', ({ peerId, protocols }) => { - expect(peerId).to.exist() - expect(protocols).to.have.deep.members(supportedProtocols) - defer.resolve() - }) - - await pb.set(peerId, supportedProtocols) - const protocols = await pb.get(peerId) - expect(protocols).to.have.deep.members(supportedProtocols) - - await defer.promise - }) - - it('emits on set if not storing the exact same content', async () => { - const defer = pDefer() - - const supportedProtocolsA = ['protocol1', 'protocol2'] - const supportedProtocolsB = ['protocol2'] - - let changeCounter = 0 - peerStore.on('change:protocols', () => { - changeCounter++ - if (changeCounter > 1) { - defer.resolve() - } - }) - - // set 1 - await pb.set(peerId, supportedProtocolsA) - - // set 2 (same content) - await pb.set(peerId, supportedProtocolsB) - const protocols = await pb.get(peerId) - expect(protocols).to.have.deep.members(supportedProtocolsB) - - await defer.promise - }) - - it('does not emit on set if it is storing the exact same content', async () => { - const defer = pDefer() - - const supportedProtocols = ['protocol1', 'protocol2'] - - let changeCounter = 0 - peerStore.on('change:protocols', () => { - changeCounter++ - if (changeCounter > 1) { - defer.reject() - } - }) - - // set 1 - await pb.set(peerId, supportedProtocols) - - // set 2 (same content) - await pb.set(peerId, supportedProtocols) - - // Wait 50ms for incorrect second event - setTimeout(() => { - defer.resolve() - }, 50) - - return defer.promise - }) - }) - - describe('protoBook.add', () => { - /** @type {PeerStore} */ - let peerStore - /** @type {ProtoBook} */ - let pb - - beforeEach(() => { - peerStore = new PeerStore({ - peerId, - datastore: new MemoryDatastore() - }) - pb = peerStore.protoBook - }) - - afterEach(() => { - peerStore.removeAllListeners() - }) - - it('throws invalid parameters error if invalid PeerId is provided', async () => { - await expect(pb.add('invalid peerId')).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) - }) - - it('throws invalid parameters error if no protocols provided', async () => { - await expect(pb.add(peerId)).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) - }) - - it('adds the new content and emits change event', async () => { - const defer = pDefer() - - const supportedProtocolsA = ['protocol1', 'protocol2'] - const supportedProtocolsB = ['protocol3'] - const finalProtocols = supportedProtocolsA.concat(supportedProtocolsB) - - let changeTrigger = 2 - peerStore.on('change:protocols', ({ protocols }) => { - changeTrigger-- - if (changeTrigger === 0 && arraysAreEqual(protocols, finalProtocols)) { - defer.resolve() - } - }) - - // Replace - await pb.set(peerId, supportedProtocolsA) - let protocols = await pb.get(peerId) - expect(protocols).to.have.deep.members(supportedProtocolsA) - - // Add - await pb.add(peerId, supportedProtocolsB) - protocols = await pb.get(peerId) - expect(protocols).to.have.deep.members(finalProtocols) - - return defer.promise - }) - - it('emits on add if the content to add not exists', async () => { - const defer = pDefer() - - const supportedProtocolsA = ['protocol1'] - const supportedProtocolsB = ['protocol2'] - const finalProtocols = supportedProtocolsA.concat(supportedProtocolsB) - - let changeCounter = 0 - peerStore.on('change:protocols', () => { - changeCounter++ - if (changeCounter > 1) { - defer.resolve() - } - }) - - // set 1 - await pb.set(peerId, supportedProtocolsA) - - // set 2 (content already existing) - await pb.add(peerId, supportedProtocolsB) - const protocols = await pb.get(peerId) - expect(protocols).to.have.deep.members(finalProtocols) - - return defer.promise - }) - - it('does not emit on add if the content to add already exists', async () => { - const defer = pDefer() - - const supportedProtocolsA = ['protocol1', 'protocol2'] - const supportedProtocolsB = ['protocol2'] - - let changeCounter = 0 - peerStore.on('change:protocols', () => { - changeCounter++ - if (changeCounter > 1) { - defer.reject() - } - }) - - // set 1 - await pb.set(peerId, supportedProtocolsA) - - // set 2 (content already existing) - await pb.add(peerId, supportedProtocolsB) - - // Wait 50ms for incorrect second event - setTimeout(() => { - defer.resolve() - }, 50) - - return defer.promise - }) - }) - - describe('protoBook.remove', () => { - /** @type {PeerStore} */ - let peerStore - /** @type {ProtoBook} */ - let pb - - beforeEach(() => { - peerStore = new PeerStore({ - peerId, - datastore: new MemoryDatastore() - }) - pb = peerStore.protoBook - }) - - afterEach(() => { - peerStore.removeAllListeners() - }) - - it('throws invalid parameters error if invalid PeerId is provided', async () => { - await expect(pb.remove('invalid peerId')).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) - }) - - it('throws invalid parameters error if no protocols provided', async () => { - await expect(pb.remove(peerId)).to.eventually.be.rejected().with.property('code', 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 - await pb.set(peerId, supportedProtocols) - let protocols = await pb.get(peerId) - expect(protocols).to.have.deep.members(supportedProtocols) - - // Remove - await pb.remove(peerId, removedProtocols) - protocols = await 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', async () => { - 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 - await pb.set(peerId, supportedProtocols) - - // remove (content already existing) - await pb.remove(peerId, removedProtocols) - const protocols = await 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', async () => { - const spy = sinon.spy() - - const supportedProtocols = ['protocol1', 'protocol2'] - const removedProtocols = ['protocol3'] - - peerStore.on('change:protocols', spy) - - // set - await pb.set(peerId, supportedProtocols) - - // remove - await pb.remove(peerId, removedProtocols) - - // Only one event - expect(spy.callCount).to.eql(1) - }) - }) - - describe('protoBook.get', () => { - /** @type {PeerStore} */ - let peerStore - /** @type {ProtoBook} */ - let pb - - beforeEach(() => { - peerStore = new PeerStore({ - peerId, - datastore: new MemoryDatastore() - }) - pb = peerStore.protoBook - }) - - it('throws invalid parameters error if invalid PeerId is provided', async () => { - await expect(pb.get('invalid peerId')).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) - }) - - it('returns empty if no protocols are known for the provided peer', async () => { - const protocols = await pb.get(peerId) - - expect(protocols).to.be.empty() - }) - - it('returns the protocols stored', async () => { - const supportedProtocols = ['protocol1', 'protocol2'] - - await pb.set(peerId, supportedProtocols) - - const protocols = await pb.get(peerId) - expect(protocols).to.have.deep.members(supportedProtocols) - }) - }) - - describe('protoBook.delete', () => { - /** @type {PeerStore} */ - let peerStore - /** @type {ProtoBook} */ - let pb - - beforeEach(() => { - peerStore = new PeerStore({ - peerId, - datastore: new MemoryDatastore() - }) - pb = peerStore.protoBook - }) - - it('throws invalid parameters error if invalid PeerId is provided', async () => { - await expect(pb.delete('invalid peerId')).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) - }) - - it('should not emit event if no records exist for the peer', async () => { - const defer = pDefer() - - peerStore.on('change:protocols', () => { - defer.reject() - }) - - await pb.delete(peerId) - - // Wait 50ms for incorrect invalid event - setTimeout(() => { - defer.resolve() - }, 50) - - await defer.promise - }) - - it('should emit event if a record exists for the peer', async () => { - const defer = pDefer() - - const supportedProtocols = ['protocol1', 'protocol2'] - await pb.set(peerId, supportedProtocols) - - // Listen after set - peerStore.on('change:protocols', ({ protocols }) => { - expect(protocols.length).to.eql(0) - defer.resolve() - }) - - await pb.delete(peerId) - - await defer.promise - }) - }) -}) diff --git a/test/pnet/index.spec.js b/test/pnet/index.spec.js deleted file mode 100644 index 76278e3829..0000000000 --- a/test/pnet/index.spec.js +++ /dev/null @@ -1,92 +0,0 @@ -/* eslint-env mocha */ -'use strict' - -const { expect } = require('aegir/utils/chai') -const duplexPair = require('it-pair/duplex') -const pipe = require('it-pipe') -const { collect } = require('streaming-iterables') -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') - -const Protector = require('../../src/pnet') -const Errors = Protector.errors -const generate = Protector.generate - -const swarmKeyBuffer = new Uint8Array(95) -const wrongSwarmKeyBuffer = new Uint8Array(95) - -// Write new psk files to the buffers -generate(swarmKeyBuffer) -generate(wrongSwarmKeyBuffer) - -describe('private network', () => { - it('should accept a valid psk buffer', () => { - const protector = new Protector(swarmKeyBuffer) - - expect(protector.tag).to.equal('/key/swarm/psk/1.0.0/') - expect(protector.psk.byteLength).to.equal(32) - }) - - it('should protect a simple connection', async () => { - const [inbound, outbound] = duplexPair() - const protector = new Protector(swarmKeyBuffer) - - const [aToB, bToA] = await Promise.all([ - protector.protect(inbound), - protector.protect(outbound) - ]) - - pipe( - [uint8ArrayFromString('hello world'), uint8ArrayFromString('doo dah')], - aToB - ) - - const output = await pipe( - bToA, - source => (async function * () { - for await (const chunk of source) { - yield chunk.slice() - } - })(), - collect - ) - - expect(output).to.eql([uint8ArrayFromString('hello world'), uint8ArrayFromString('doo dah')]) - }) - - it('should not be able to share correct data with different keys', async () => { - const [inbound, outbound] = duplexPair() - const protector = new Protector(swarmKeyBuffer) - const protectorB = new Protector(wrongSwarmKeyBuffer) - - const [aToB, bToA] = await Promise.all([ - protector.protect(inbound), - protectorB.protect(outbound) - ]) - - pipe( - [uint8ArrayFromString('hello world'), uint8ArrayFromString('doo dah')], - aToB - ) - - const output = await pipe( - bToA, - collect - ) - - expect(output).to.not.eql([uint8ArrayFromString('hello world'), uint8ArrayFromString('doo dah')]) - }) - - describe('invalid psks', () => { - it('should not accept a bad psk', () => { - expect(() => { - return new Protector(uint8ArrayFromString('not-a-key')) - }).to.throw(Errors.INVALID_PSK) - }) - - it('should not accept a psk of incorrect length', () => { - expect(() => { - return new Protector(uint8ArrayFromString('/key/swarm/psk/1.0.0/\n/base16/\ndffb7e')) - }).to.throw(Errors.INVALID_PSK) - }) - }) -}) diff --git a/test/pnet/index.spec.ts b/test/pnet/index.spec.ts new file mode 100644 index 0000000000..1cf1f3c12f --- /dev/null +++ b/test/pnet/index.spec.ts @@ -0,0 +1,115 @@ +/* eslint-env mocha */ +import { expect } from 'aegir/utils/chai.js' +import { pipe } from 'it-pipe' +import all from 'it-all' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { PreSharedKeyConnectionProtector } from '../../src/pnet/index.js' +import { generate } from '../../src/pnet/key-generator.js' +import { INVALID_PSK } from '../../src/pnet/errors.js' +import { mockMultiaddrConnPair } from '@libp2p/interface-compliance-tests/mocks' +import { Multiaddr } from '@multiformats/multiaddr' +import { createEd25519PeerId } from '@libp2p/peer-id-factory' + +const swarmKeyBuffer = new Uint8Array(95) +const wrongSwarmKeyBuffer = new Uint8Array(95) + +// Write new psk files to the buffers +generate(swarmKeyBuffer) +generate(wrongSwarmKeyBuffer) + +describe('private network', () => { + it('should accept a valid psk buffer', () => { + const protector = new PreSharedKeyConnectionProtector({ + psk: swarmKeyBuffer + }) + + expect(protector.tag).to.equal('/key/swarm/psk/1.0.0/') + }) + + it('should protect a simple connection', async () => { + const { inbound, outbound } = mockMultiaddrConnPair({ + addrs: [ + new Multiaddr('/ip4/127.0.0.1/tcp/1234'), + new Multiaddr('/ip4/127.0.0.1/tcp/1235') + ], + remotePeer: await createEd25519PeerId() + }) + const protector = new PreSharedKeyConnectionProtector({ + psk: swarmKeyBuffer + }) + + const [aToB, bToA] = await Promise.all([ + protector.protect(inbound), + protector.protect(outbound) + ]) + + void pipe( + [uint8ArrayFromString('hello world'), uint8ArrayFromString('doo dah')], + aToB + ) + + const output = await pipe( + bToA, + async function * (source) { + for await (const chunk of source) { + yield chunk.slice() + } + }, + async (source) => await all(source) + ) + + expect(output).to.eql([uint8ArrayFromString('hello world'), uint8ArrayFromString('doo dah')]) + }) + + it('should not be able to share correct data with different keys', async () => { + const { inbound, outbound } = mockMultiaddrConnPair({ + addrs: [ + new Multiaddr('/ip4/127.0.0.1/tcp/1234'), + new Multiaddr('/ip4/127.0.0.1/tcp/1235') + ], + remotePeer: await createEd25519PeerId() + }) + const protector = new PreSharedKeyConnectionProtector({ + psk: swarmKeyBuffer + }) + const protectorB = new PreSharedKeyConnectionProtector({ + enabled: true, + psk: wrongSwarmKeyBuffer + }) + + const [aToB, bToA] = await Promise.all([ + protector.protect(inbound), + protectorB.protect(outbound) + ]) + + void pipe( + [uint8ArrayFromString('hello world'), uint8ArrayFromString('doo dah')], + aToB + ) + + const output = await pipe( + bToA, + async (source) => await all(source) + ) + + expect(output).to.not.eql([uint8ArrayFromString('hello world'), uint8ArrayFromString('doo dah')]) + }) + + describe('invalid psks', () => { + it('should not accept a bad psk', () => { + expect(() => { + return new PreSharedKeyConnectionProtector({ + psk: uint8ArrayFromString('not-a-key') + }) + }).to.throw(INVALID_PSK) + }) + + it('should not accept a psk of incorrect length', () => { + expect(() => { + return new PreSharedKeyConnectionProtector({ + psk: uint8ArrayFromString('/key/swarm/psk/1.0.0/\n/base16/\ndffb7e') + }) + }).to.throw(INVALID_PSK) + }) + }) +}) diff --git a/test/record/envelope.spec.js b/test/record/envelope.spec.js deleted file mode 100644 index c932528f21..0000000000 --- a/test/record/envelope.spec.js +++ /dev/null @@ -1,87 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const { fromString: uint8arrayFromString } = require('uint8arrays/from-string') -const { equals: uint8arrayEquals } = require('uint8arrays/equals') -const Envelope = require('../../src/record/envelope') -const { codes: ErrorCodes } = require('../../src/errors') - -const peerUtils = require('../utils/creators/peer') - -const domain = 'libp2p-testing' -const codec = uint8arrayFromString('/libp2p/testdata') - -class TestRecord { - constructor (data) { - this.domain = domain - this.codec = codec - this.data = data - } - - marshal () { - return uint8arrayFromString(this.data) - } - - equals (other) { - return uint8arrayEquals(this.data, other.data) - } -} - -describe('Envelope', () => { - const payloadType = codec - let peerId - let testRecord - - before(async () => { - [peerId] = await peerUtils.createPeerId() - testRecord = new TestRecord('test-data') - }) - - it('creates an envelope with a random key', () => { - const payload = testRecord.marshal() - const signature = uint8arrayFromString(Math.random().toString(36).substring(7)) - - const envelope = new Envelope({ - peerId, - payloadType, - payload, - signature - }) - - expect(envelope).to.exist() - expect(envelope.peerId.equals(peerId)).to.eql(true) - expect(envelope.payloadType).to.equalBytes(payloadType) - expect(envelope.payload).to.equalBytes(payload) - expect(envelope.signature).to.equalBytes(signature) - }) - - it('can seal a record', async () => { - const envelope = await Envelope.seal(testRecord, peerId) - expect(envelope).to.exist() - expect(envelope.peerId.equals(peerId)).to.eql(true) - expect(envelope.payloadType).to.eql(payloadType) - expect(envelope.payload).to.exist() - expect(envelope.signature).to.exist() - }) - - it('can open and verify a sealed record', async () => { - const envelope = await Envelope.seal(testRecord, peerId) - const rawEnvelope = envelope.marshal() - - const unmarshalledEnvelope = await Envelope.openAndCertify(rawEnvelope, testRecord.domain) - expect(unmarshalledEnvelope).to.exist() - - const equals = envelope.equals(unmarshalledEnvelope) - expect(equals).to.eql(true) - }) - - it('throw on open and verify when a different domain is used', async () => { - const envelope = await Envelope.seal(testRecord, peerId) - const rawEnvelope = envelope.marshal() - - await expect(Envelope.openAndCertify(rawEnvelope, '/bad-domain')) - .to.eventually.be.rejected() - .and.to.have.property('code', ErrorCodes.ERR_SIGNATURE_NOT_VALID) - }) -}) diff --git a/test/record/peer-record.spec.js b/test/record/peer-record.spec.js deleted file mode 100644 index 532c79a5fb..0000000000 --- a/test/record/peer-record.spec.js +++ /dev/null @@ -1,157 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') - -const tests = require('libp2p-interfaces-compliance-tests/src/record') -const { Multiaddr } = require('multiaddr') -const PeerId = require('peer-id') - -const Envelope = require('../../src/record/envelope') -const PeerRecord = require('../../src/record/peer-record') - -const peerUtils = require('../utils/creators/peer') - -describe('interface-record compliance', () => { - tests({ - async setup () { - const [peerId] = await peerUtils.createPeerId() - return new PeerRecord({ peerId }) - }, - async teardown () { - // cleanup resources created by setup() - } - }) -}) - -describe('PeerRecord', () => { - let peerId - - before(async () => { - [peerId] = await peerUtils.createPeerId() - }) - - it('de/serializes the same as a go record', async () => { - const privKey = Uint8Array.from([8, 1, 18, 64, 133, 251, 231, 43, 96, 100, 40, 144, 4, 165, 49, 249, 103, 137, 141, 245, 49, 158, 224, 41, 146, 253, 216, 64, 33, 250, 80, 82, 67, 75, 246, 238, 17, 187, 163, 237, 23, 33, 148, 140, 239, 180, 229, 11, 10, 11, 181, 202, 216, 166, 181, 45, 199, 177, 164, 15, 79, 102, 82, 16, 92, 145, 226, 196]) - const rawEnvelope = Uint8Array.from([10, 36, 8, 1, 18, 32, 17, 187, 163, 237, 23, 33, 148, 140, 239, 180, 229, 11, 10, 11, 181, 202, 216, 166, 181, 45, 199, 177, 164, 15, 79, 102, 82, 16, 92, 145, 226, 196, 18, 2, 3, 1, 26, 170, 1, 10, 38, 0, 36, 8, 1, 18, 32, 17, 187, 163, 237, 23, 33, 148, 140, 239, 180, 229, 11, 10, 11, 181, 202, 216, 166, 181, 45, 199, 177, 164, 15, 79, 102, 82, 16, 92, 145, 226, 196, 16, 216, 184, 224, 191, 147, 145, 182, 151, 22, 26, 10, 10, 8, 4, 1, 2, 3, 4, 6, 0, 0, 26, 10, 10, 8, 4, 1, 2, 3, 4, 6, 0, 1, 26, 10, 10, 8, 4, 1, 2, 3, 4, 6, 0, 2, 26, 10, 10, 8, 4, 1, 2, 3, 4, 6, 0, 3, 26, 10, 10, 8, 4, 1, 2, 3, 4, 6, 0, 4, 26, 10, 10, 8, 4, 1, 2, 3, 4, 6, 0, 5, 26, 10, 10, 8, 4, 1, 2, 3, 4, 6, 0, 6, 26, 10, 10, 8, 4, 1, 2, 3, 4, 6, 0, 7, 26, 10, 10, 8, 4, 1, 2, 3, 4, 6, 0, 8, 26, 10, 10, 8, 4, 1, 2, 3, 4, 6, 0, 9, 42, 64, 177, 151, 247, 107, 159, 40, 138, 242, 180, 103, 254, 102, 111, 119, 68, 118, 40, 112, 73, 180, 36, 183, 57, 117, 200, 134, 14, 251, 2, 55, 45, 2, 106, 121, 149, 132, 84, 26, 215, 47, 38, 84, 52, 100, 133, 188, 163, 236, 227, 100, 98, 183, 209, 177, 57, 28, 141, 39, 109, 196, 171, 139, 202, 11]) - const peerId = await PeerId.createFromPrivKey(privKey) - - const env = await Envelope.openAndCertify(rawEnvelope, PeerRecord.DOMAIN) - expect(peerId.equals(env.peerId)) - - const record = PeerRecord.createFromProtobuf(env.payload) - - // The payload isn't going to match because of how the protobuf encodes uint64 values - // They are marshalled correctly on both sides, but will be off by 1 value - // Signatures will still be validated - const jsEnv = await Envelope.seal(record, peerId) - expect(env.payloadType).to.eql(jsEnv.payloadType) - }) - - it('creates a peer record with peerId', () => { - const peerRecord = new PeerRecord({ peerId }) - - expect(peerRecord).to.exist() - expect(peerRecord.peerId).to.exist() - expect(peerRecord.multiaddrs).to.exist() - expect(peerRecord.multiaddrs).to.have.lengthOf(0) - expect(peerRecord.seqNumber).to.exist() - }) - - it('creates a peer record with provided data', () => { - const multiaddrs = [ - new Multiaddr('/ip4/127.0.0.1/tcp/2000') - ] - const seqNumber = Date.now() - const peerRecord = new PeerRecord({ peerId, multiaddrs, seqNumber }) - - expect(peerRecord).to.exist() - expect(peerRecord.peerId).to.exist() - expect(peerRecord.multiaddrs).to.exist() - expect(peerRecord.multiaddrs).to.eql(multiaddrs) - expect(peerRecord.seqNumber).to.exist() - expect(peerRecord.seqNumber).to.eql(seqNumber) - }) - - it('marshals and unmarshals a peer record', () => { - const multiaddrs = [ - new Multiaddr('/ip4/127.0.0.1/tcp/2000') - ] - const seqNumber = Date.now() - const peerRecord = new PeerRecord({ peerId, multiaddrs, seqNumber }) - - // Marshal - const rawData = peerRecord.marshal() - expect(rawData).to.exist() - - // Unmarshal - const unmarshalPeerRecord = PeerRecord.createFromProtobuf(rawData) - expect(unmarshalPeerRecord).to.exist() - - const equals = peerRecord.equals(unmarshalPeerRecord) - expect(equals).to.eql(true) - }) - - it('equals returns false if the peer record has a different peerId', async () => { - const peerRecord0 = new PeerRecord({ peerId }) - - const [peerId1] = await peerUtils.createPeerId({ fixture: false }) - const peerRecord1 = new PeerRecord({ peerId: peerId1 }) - - const equals = peerRecord0.equals(peerRecord1) - expect(equals).to.eql(false) - }) - - it('equals returns false if the peer record has a different seqNumber', () => { - const ts0 = Date.now() - const peerRecord0 = new PeerRecord({ peerId, seqNumber: ts0 }) - - const ts1 = ts0 + 20 - const peerRecord1 = new PeerRecord({ peerId, seqNumber: ts1 }) - - const equals = peerRecord0.equals(peerRecord1) - expect(equals).to.eql(false) - }) - - it('equals returns false if the peer record has a different multiaddrs', () => { - const multiaddrs = [ - new Multiaddr('/ip4/127.0.0.1/tcp/2000') - ] - const peerRecord0 = new PeerRecord({ peerId, multiaddrs }) - - const multiaddrs1 = [ - new Multiaddr('/ip4/127.0.0.1/tcp/2001') - ] - const peerRecord1 = new PeerRecord({ peerId, multiaddrs: multiaddrs1 }) - - const equals = peerRecord0.equals(peerRecord1) - expect(equals).to.eql(false) - }) -}) - -describe('PeerRecord inside Envelope', () => { - let peerId - let peerRecord - - before(async () => { - [peerId] = await peerUtils.createPeerId() - const multiaddrs = [ - new Multiaddr('/ip4/127.0.0.1/tcp/2000') - ] - const seqNumber = Date.now() - peerRecord = new PeerRecord({ peerId, multiaddrs, seqNumber }) - }) - - it('creates an envelope with the PeerRecord and can unmarshal it', async () => { - const e = await Envelope.seal(peerRecord, peerId) - const byteE = e.marshal() - - const decodedE = await Envelope.openAndCertify(byteE, peerRecord.domain) - expect(decodedE).to.exist() - - const decodedPeerRecord = PeerRecord.createFromProtobuf(decodedE.payload) - - const equals = peerRecord.equals(decodedPeerRecord) - expect(equals).to.eql(true) - }) -}) diff --git a/test/registrar/registrar.spec.js b/test/registrar/registrar.spec.js deleted file mode 100644 index fa9be0c88c..0000000000 --- a/test/registrar/registrar.spec.js +++ /dev/null @@ -1,198 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const pDefer = require('p-defer') - -const { EventEmitter } = require('events') -const { MemoryDatastore } = require('datastore-core/memory') -const Topology = require('libp2p-interfaces/src/topology/multicodec-topology') -const PeerStore = require('../../src/peer-store') -const Registrar = require('../../src/registrar') -const { mockConnectionGater } = require('../utils/mock-connection-gater') -const createMockConnection = require('../utils/mockConnection') -const peerUtils = require('../utils/creators/peer') -const baseOptions = require('../utils/base-options.browser') - -const multicodec = '/test/1.0.0' - -describe('registrar', () => { - const connectionGater = mockConnectionGater() - let peerStore - let registrar - let peerId - - before(async () => { - [peerId] = await peerUtils.createPeerId() - }) - - describe('errors', () => { - beforeEach(() => { - peerStore = new PeerStore({ - peerId, - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - registrar = new Registrar({ peerStore, connectionManager: new EventEmitter() }) - }) - - it('should fail to register a protocol if no multicodec is provided', () => { - return expect(registrar.register()).to.eventually.be.rejected() - }) - - it('should fail to register a protocol if an invalid topology is provided', () => { - const fakeTopology = { - random: 1 - } - - return expect(registrar.register(fakeTopology)).to.eventually.be.rejected() - }) - }) - - describe('registration', () => { - let libp2p - - beforeEach(async () => { - [libp2p] = await peerUtils.createPeer({ - config: { - modules: baseOptions.modules - }, - started: false - }) - }) - - afterEach(() => libp2p.stop()) - - it('should be able to register a protocol', async () => { - const topologyProps = new Topology({ - multicodecs: multicodec, - handlers: { - onConnect: () => { }, - onDisconnect: () => { } - } - }) - - const identifier = await libp2p.registrar.register(topologyProps) - - expect(identifier).to.exist() - }) - - it('should be able to unregister a protocol', async () => { - const topologyProps = new Topology({ - multicodecs: multicodec, - handlers: { - onConnect: () => { }, - onDisconnect: () => { } - } - }) - - const identifier = await libp2p.registrar.register(topologyProps) - const success = libp2p.registrar.unregister(identifier) - - expect(success).to.eql(true) - }) - - it('should fail to unregister if no register was made', () => { - const success = libp2p.registrar.unregister('bad-identifier') - - expect(success).to.eql(false) - }) - - it('should call onConnect handler for connected peers after register', async () => { - const onConnectDefer = pDefer() - const onDisconnectDefer = pDefer() - - // Setup connections before registrar - const conn = await createMockConnection() - const remotePeerId = conn.remotePeer - - const topologyProps = new Topology({ - multicodecs: multicodec, - handlers: { - onConnect: (peerId, connection) => { - expect(peerId.toB58String()).to.eql(remotePeerId.toB58String()) - expect(connection.id).to.eql(conn.id) - - onConnectDefer.resolve() - }, - onDisconnect: (peerId) => { - expect(peerId.toB58String()).to.eql(remotePeerId.toB58String()) - - onDisconnectDefer.resolve() - } - } - }) - - await libp2p.start() - - // Register protocol - const identifier = await libp2p.registrar.register(topologyProps) - const topology = libp2p.registrar.topologies.get(identifier) - - // Topology created - expect(topology).to.exist() - - // Add connected peer with protocol to peerStore and registrar - await libp2p.peerStore.protoBook.add(remotePeerId, [multicodec]) - - await libp2p.connectionManager.onConnect(conn) - expect(libp2p.connectionManager.size).to.eql(1) - - await conn.close() - - libp2p.connectionManager.onDisconnect(conn) - expect(libp2p.connectionManager.size).to.eql(0) - - // Wait for handlers to be called - return Promise.all([ - onConnectDefer.promise, - onDisconnectDefer.promise - ]) - }) - - it('should call onConnect handler after register, once a peer is connected and protocols are updated', async () => { - const onConnectDefer = pDefer() - const onDisconnectDefer = pDefer() - - const topologyProps = new Topology({ - multicodecs: multicodec, - handlers: { - onConnect: () => { - onConnectDefer.resolve() - }, - onDisconnect: () => { - onDisconnectDefer.resolve() - } - } - }) - - await libp2p.start() - - // Register protocol - const identifier = await libp2p.registrar.register(topologyProps) - const topology = libp2p.registrar.topologies.get(identifier) - - // Topology created - expect(topology).to.exist() - expect(libp2p.connectionManager.size).to.eql(0) - - // Setup connections before registrar - const conn = await createMockConnection() - const remotePeerId = conn.remotePeer - - // Add connected peer to peerStore and registrar - await libp2p.peerStore.protoBook.set(remotePeerId, []) - - // Add protocol to peer and update it - await libp2p.peerStore.protoBook.add(remotePeerId, [multicodec]) - - await libp2p.connectionManager.onConnect(conn) - await onConnectDefer.promise - - // Remove protocol to peer and update it - await libp2p.peerStore.protoBook.set(remotePeerId, []) - - await onDisconnectDefer.promise - }) - }) -}) diff --git a/test/registrar/registrar.spec.ts b/test/registrar/registrar.spec.ts new file mode 100644 index 0000000000..6ed291b378 --- /dev/null +++ b/test/registrar/registrar.spec.ts @@ -0,0 +1,228 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import pDefer from 'p-defer' +import { MemoryDatastore } from 'datastore-core/memory' +import { createTopology } from '@libp2p/topology' +import { PersistentPeerStore } from '@libp2p/peer-store' +import { DefaultRegistrar } from '../../src/registrar.js' +import { mockConnectionGater, mockDuplex, mockMultiaddrConnection, mockUpgrader, mockConnection } from '@libp2p/interface-compliance-tests/mocks' +import { createPeerId, createNode } from '../utils/creators/peer.js' +import { createBaseOptions } from '../utils/base-options.browser.js' +import type { Registrar } from '@libp2p/interfaces/registrar' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import { Components } from '@libp2p/interfaces/components' +import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' +import { createEd25519PeerId } from '@libp2p/peer-id-factory' +import { CustomEvent } from '@libp2p/interfaces' +import type { Connection } from '@libp2p/interfaces/connection' +import { DefaultConnectionManager } from '../../src/connection-manager/index.js' +import { Plaintext } from '../../src/insecure/index.js' +import { WebSockets } from '@libp2p/websockets' +import { Mplex } from '@libp2p/mplex' + +const protocol = '/test/1.0.0' + +describe('registrar', () => { + const connectionGater = mockConnectionGater() + let components: Components + let registrar: Registrar + let peerId: PeerId + + before(async () => { + peerId = await createPeerId() + }) + + describe('errors', () => { + beforeEach(() => { + components = new Components({ + peerId, + datastore: new MemoryDatastore(), + upgrader: mockUpgrader() + }) + components.setPeerStore(new PersistentPeerStore(components, { + addressFilter: connectionGater.filterMultiaddrForPeer + })) + components.setConnectionManager(new DefaultConnectionManager(components)) + registrar = new DefaultRegistrar(components) + }) + + it('should fail to register a protocol if no multicodec is provided', () => { + // @ts-expect-error invalid parameters + return expect(registrar.register()).to.eventually.be.rejected() + }) + + it('should fail to register a protocol if an invalid topology is provided', () => { + const fakeTopology = { + random: 1 + } + + // @ts-expect-error invalid parameters + return expect(registrar.register(fakeTopology)).to.eventually.be.rejected() + }) + }) + + describe('registration', () => { + let libp2p: Libp2pNode + + beforeEach(async () => { + libp2p = await createNode({ + config: createBaseOptions(), + started: false + }) + }) + + afterEach(async () => await libp2p.stop()) + + it('should be able to register a protocol', async () => { + const topology = createTopology({ + onConnect: () => { }, + onDisconnect: () => { } + }) + + expect(libp2p.components.getRegistrar().getTopologies(protocol)).to.have.lengthOf(0) + + const identifier = await libp2p.components.getRegistrar().register(protocol, topology) + + expect(identifier).to.exist() + expect(libp2p.components.getRegistrar().getTopologies(protocol)).to.have.lengthOf(1) + }) + + it('should be able to unregister a protocol', async () => { + const topology = createTopology({ + onConnect: () => { }, + onDisconnect: () => { } + }) + + expect(libp2p.components.getRegistrar().getTopologies(protocol)).to.have.lengthOf(0) + + const identifier = await libp2p.components.getRegistrar().register(protocol, topology) + + expect(libp2p.components.getRegistrar().getTopologies(protocol)).to.have.lengthOf(1) + + libp2p.components.getRegistrar().unregister(identifier) + + expect(libp2p.components.getRegistrar().getTopologies(protocol)).to.have.lengthOf(0) + }) + + it('should not error if unregistering unregistered topology handler', () => { + libp2p.components.getRegistrar().unregister('bad-identifier') + }) + + it('should call onConnect handler for connected peers after register', async () => { + const onConnectDefer = pDefer() + const onDisconnectDefer = pDefer() + + // Setup connections before registrar + const remotePeerId = await createEd25519PeerId() + const conn = mockConnection(mockMultiaddrConnection(mockDuplex(), remotePeerId)) + + const topology = createTopology({ + onConnect: (peerId, connection) => { + expect(peerId.equals(remotePeerId)).to.be.true() + expect(connection.id).to.eql(conn.id) + + onConnectDefer.resolve() + }, + onDisconnect: (peerId) => { + expect(peerId.equals(remotePeerId)).to.be.true() + + onDisconnectDefer.resolve() + } + }) + + await libp2p.start() + + // Register protocol + await libp2p.components.getRegistrar().register(protocol, topology) + + // Add connected peer with protocol to peerStore and registrar + await libp2p.peerStore.protoBook.add(remotePeerId, [protocol]) + + // remote peer connects + await libp2p.components.getUpgrader().dispatchEvent(new CustomEvent('connection', { + detail: conn + })) + + // remote peer disconnects + await conn.close() + await libp2p.components.getUpgrader().dispatchEvent(new CustomEvent('connectionEnd', { + detail: conn + })) + + // Wait for handlers to be called + return await Promise.all([ + onConnectDefer.promise, + onDisconnectDefer.promise + ]) + }) + + it('should call onConnect handler after register, once a peer is connected and protocols are updated', async () => { + const onConnectDefer = pDefer() + const onDisconnectDefer = pDefer() + + // Setup connections before registrar + const remotePeerId = await createEd25519PeerId() + const conn = mockConnection(mockMultiaddrConnection(mockDuplex(), remotePeerId)) + + const topology = createTopology({ + onConnect: () => { + onConnectDefer.resolve() + }, + onDisconnect: () => { + onDisconnectDefer.resolve() + } + }) + + await libp2p.start() + + // Register protocol + await libp2p.components.getRegistrar().register(protocol, topology) + + // Add connected peer to peerStore and registrar + await libp2p.peerStore.protoBook.set(remotePeerId, []) + + // Add protocol to peer and update it + await libp2p.peerStore.protoBook.add(remotePeerId, [protocol]) + + await libp2p.components.getUpgrader().dispatchEvent(new CustomEvent('connection', { + detail: conn + })) + + await onConnectDefer.promise + + // Peer no longer supports the protocol our topology is registered for + await libp2p.peerStore.protoBook.set(remotePeerId, []) + + await onDisconnectDefer.promise + }) + + it('should be able to register and unregister a handler', async () => { + libp2p = await createLibp2pNode({ + peerId: await createEd25519PeerId(), + transports: [ + new WebSockets() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + new Plaintext() + ] + }) + + const registrar = libp2p.components.getRegistrar() + + expect(registrar.getProtocols()).to.not.have.any.keys(['/echo/1.0.0', '/echo/1.0.1']) + + const echoHandler = () => {} + await libp2p.handle(['/echo/1.0.0', '/echo/1.0.1'], echoHandler) + expect(registrar.getHandler('/echo/1.0.0')).to.equal(echoHandler) + expect(registrar.getHandler('/echo/1.0.1')).to.equal(echoHandler) + + await libp2p.unhandle(['/echo/1.0.0']) + expect(registrar.getProtocols()).to.not.have.any.keys(['/echo/1.0.0']) + expect(registrar.getHandler('/echo/1.0.1')).to.equal(echoHandler) + }) + }) +}) diff --git a/test/relay/auto-relay.node.js b/test/relay/auto-relay.node.ts similarity index 56% rename from test/relay/auto-relay.node.js rename to test/relay/auto-relay.node.ts index 2316563dfd..53e8a1b208 100644 --- a/test/relay/auto-relay.node.js +++ b/test/relay/auto-relay.node.ts @@ -1,28 +1,25 @@ -'use strict' /* eslint-env mocha */ -const { expect } = require('aegir/utils/chai') -const defer = require('p-defer') -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' - -async function usingAsRelay (node, relay, opts) { +import { expect } from 'aegir/utils/chai.js' +import defer from 'p-defer' +import pWaitFor from 'p-wait-for' +import sinon from 'sinon' +import nock from 'nock' +import { create as createIpfsHttpClient } from 'ipfs-http-client' +import { DelegatedContentRouting } from '@libp2p/delegated-content-routing' +import { RELAY_CODEC } from '../../src/circuit/multicodec.js' +import { createNode } from '../utils/creators/peer.js' +import type { Libp2pNode } from '../../src/libp2p.js' +import type { Options as PWaitForOptions } from 'p-wait-for' +import type Sinon from 'sinon' +import { createRelayOptions, createNodeOptions } from './utils.js' +import { protocols } from '@multiformats/multiaddr' + +async function usingAsRelay (node: Libp2pNode, relay: Libp2pNode, opts?: PWaitForOptions) { // Wait for peer to be used as a relay await pWaitFor(() => { - for (const addr of node.multiaddrs) { - if (addr.toString().includes(`${relay.peerId.toB58String()}/p2p-circuit`)) { + for (const addr of node.getMultiaddrs()) { + if (addr.toString().includes(`${relay.peerId.toString()}/p2p-circuit`)) { return true } } @@ -31,13 +28,11 @@ async function usingAsRelay (node, relay, opts) { }, opts) } -async function discoveredRelayConfig (node, relay) { +async function discoveredRelayConfig (node: Libp2pNode, relay: Libp2pNode) { await pWaitFor(async () => { - const protos = await node.peerStore.protoBook.get(relay.peerId) - const supportsRelay = protos.includes('/libp2p/circuit/relay/0.1.0') - - const metadata = await node.peerStore.metadataBook.get(relay.peerId) - const supportsHop = metadata.has('hop_relay') + const peerData = await node.peerStore.get(relay.peerId) + const supportsRelay = peerData.protocols.includes(RELAY_CODEC) + const supportsHop = peerData.metadata.has('hop_relay') return supportsRelay && supportsHop }) @@ -45,58 +40,32 @@ async function discoveredRelayConfig (node, relay) { describe('auto-relay', () => { describe('basics', () => { - let libp2p - let relayLibp2p + let libp2p: Libp2pNode + let relayLibp2p: Libp2pNode 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 - }) + libp2p = await createNode({ + config: createNodeOptions() + }) + relayLibp2p = await createNode({ + config: createRelayOptions() }) }) - beforeEach(() => { + beforeEach(async () => { // Start each node - return Promise.all([libp2p, relayLibp2p].map(libp2p => libp2p.start())) + return await Promise.all([libp2p, relayLibp2p].map(async libp2p => await libp2p.start())) }) - afterEach(() => { + afterEach(async () => { // Stop each node - return Promise.all([libp2p, relayLibp2p].map(libp2p => libp2p.stop())) + return await Promise.all([libp2p, relayLibp2p].map(async libp2p => await libp2p.stop())) }) it('should ask if node supports hop on protocol change (relay protocol) and add to listen multiaddrs', async () => { // Discover relay - await libp2p.peerStore.addressBook.add(relayLibp2p.peerId, relayLibp2p.multiaddrs) + await libp2p.peerStore.addressBook.add(relayLibp2p.peerId, relayLibp2p.getMultiaddrs()) await libp2p.dial(relayLibp2p.peerId) // Wait for peer added as listen relay @@ -107,69 +76,37 @@ describe('auto-relay', () => { // Peer has relay multicodec const knownProtocols = await libp2p.peerStore.protoBook.get(relayLibp2p.peerId) - expect(knownProtocols).to.include(relayMulticodec) + expect(knownProtocols).to.include(RELAY_CODEC) }) }) describe('flows with 1 listener max', () => { - let libp2p - let relayLibp2p1 - let relayLibp2p2 - let relayLibp2p3 + let libp2p: Libp2pNode + let relayLibp2p1: Libp2pNode + let relayLibp2p2: Libp2pNode + let relayLibp2p3: Libp2pNode 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 - }) - }) - }) + [libp2p, relayLibp2p1, relayLibp2p2, relayLibp2p3] = await Promise.all([ + createNode({ config: createNodeOptions() }), + createNode({ config: createRelayOptions() }), + createNode({ config: createRelayOptions() }), + createNode({ config: createRelayOptions() }) + ]) - beforeEach(() => { // Start each node - return Promise.all([libp2p, relayLibp2p1, relayLibp2p2, relayLibp2p3].map(libp2p => libp2p.start())) + await Promise.all([libp2p, relayLibp2p1, relayLibp2p2, relayLibp2p3].map(async libp2p => await libp2p.start())) }) - afterEach(() => { + afterEach(async () => { // Stop each node - return Promise.all([libp2p, relayLibp2p1, relayLibp2p2, relayLibp2p3].map(libp2p => libp2p.stop())) + return await Promise.all([libp2p, relayLibp2p1, relayLibp2p2, relayLibp2p3].map(async libp2p => await libp2p.stop())) }) it('should ask if node supports hop on protocol change (relay protocol) and add to listen multiaddrs', async () => { // Discover relay - await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.getMultiaddrs()) await relayLibp2p1.dial(relayLibp2p2.peerId) await discoveredRelayConfig(relayLibp2p1, relayLibp2p2) @@ -178,12 +115,12 @@ describe('auto-relay', () => { // Peer has relay multicodec const knownProtocols = await relayLibp2p1.peerStore.protoBook.get(relayLibp2p2.peerId) - expect(knownProtocols).to.include(relayMulticodec) + expect(knownProtocols).to.include(RELAY_CODEC) }) it('should be able to dial a peer from its relayed address previously added', async () => { // Discover relay - await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.getMultiaddrs()) await relayLibp2p1.dial(relayLibp2p2.peerId) await discoveredRelayConfig(relayLibp2p1, relayLibp2p2) @@ -191,14 +128,14 @@ describe('auto-relay', () => { await usingAsRelay(relayLibp2p1, relayLibp2p2) // Dial from the other through a relay - const relayedMultiaddr2 = new Multiaddr(`${relayLibp2p1.multiaddrs[0]}/p2p/${relayLibp2p1.peerId.toB58String()}/p2p-circuit`) + const relayedMultiaddr2 = relayLibp2p1.getMultiaddrs()[0].encapsulate('/p2p-circuit') await libp2p.peerStore.addressBook.add(relayLibp2p2.peerId, [relayedMultiaddr2]) await libp2p.dial(relayLibp2p2.peerId) }) it('should only add maxListeners relayed addresses', async () => { // Discover one relay and connect - await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.getMultiaddrs()) await relayLibp2p1.dial(relayLibp2p2.peerId) await discoveredRelayConfig(relayLibp2p1, relayLibp2p2) @@ -207,10 +144,10 @@ describe('auto-relay', () => { // Relay2 has relay multicodec const knownProtocols2 = await relayLibp2p1.peerStore.protoBook.get(relayLibp2p2.peerId) - expect(knownProtocols2).to.include(relayMulticodec) + expect(knownProtocols2).to.include(RELAY_CODEC) // Discover an extra relay and connect - await relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.getMultiaddrs()) await relayLibp2p1.dial(relayLibp2p3.peerId) await discoveredRelayConfig(relayLibp2p1, relayLibp2p3) @@ -221,15 +158,19 @@ describe('auto-relay', () => { // Relay2 has relay multicodec const knownProtocols3 = await relayLibp2p1.peerStore.protoBook.get(relayLibp2p3.peerId) - expect(knownProtocols3).to.include(relayMulticodec) + expect(knownProtocols3).to.include(RELAY_CODEC) }) it('should not listen on a relayed address we disconnect from peer', async () => { + if (relayLibp2p1.identifyService == null) { + throw new Error('Identify service not configured') + } + // Spy if identify push is fired on adding/removing listen addr sinon.spy(relayLibp2p1.identifyService, 'pushToPeerStore') // Discover one relay and connect - await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.getMultiaddrs()) await relayLibp2p1.dial(relayLibp2p2.peerId) await discoveredRelayConfig(relayLibp2p1, relayLibp2p2) @@ -247,13 +188,13 @@ describe('auto-relay', () => { it('should try to listen on other connected peers relayed address if one used relay disconnects', async () => { // Discover one relay and connect - await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.getMultiaddrs()) await relayLibp2p1.dial(relayLibp2p2.peerId) await discoveredRelayConfig(relayLibp2p1, relayLibp2p2) await usingAsRelay(relayLibp2p1, relayLibp2p2) // Discover an extra relay and connect - await relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.getMultiaddrs()) await relayLibp2p1.dial(relayLibp2p3.peerId) await discoveredRelayConfig(relayLibp2p1, relayLibp2p3) @@ -276,14 +217,14 @@ describe('auto-relay', () => { it('should try to listen on stored peers relayed address if one used relay disconnects and there are not enough connected', async () => { // Discover one relay and connect - await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.getMultiaddrs()) await relayLibp2p1.dial(relayLibp2p2.peerId) // Wait for peer to be used as a relay await usingAsRelay(relayLibp2p1, relayLibp2p2) // Discover an extra relay and connect to gather its Hop support - await relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.getMultiaddrs()) await relayLibp2p1.dial(relayLibp2p3.peerId) // wait for identify for newly dialled peer @@ -295,7 +236,7 @@ describe('auto-relay', () => { // Remove peer used as relay from peerStore and disconnect it await relayLibp2p1.hangUp(relayLibp2p2.peerId) await relayLibp2p1.peerStore.delete(relayLibp2p2.peerId) - await pWaitFor(() => relayLibp2p1.connectionManager.size === 0) + await pWaitFor(() => relayLibp2p1.getConnections().length === 0) // Wait for other peer connected to be added as listen addr await usingAsRelay(relayLibp2p1, relayLibp2p3) @@ -305,11 +246,11 @@ describe('auto-relay', () => { const deferred = defer() // Discover one relay and connect - await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.getMultiaddrs()) await relayLibp2p1.dial(relayLibp2p2.peerId) // Discover an extra relay and connect to gather its Hop support - await relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.getMultiaddrs()) await relayLibp2p1.dial(relayLibp2p3.peerId) // Wait for peer to be used as a relay @@ -322,15 +263,15 @@ describe('auto-relay', () => { await relayLibp2p1.hangUp(relayLibp2p3.peerId) // Stub dial - sinon.stub(relayLibp2p1, 'dial').callsFake(() => { + sinon.stub(relayLibp2p1.components.getDialer(), 'dial').callsFake(async () => { deferred.resolve() - return Promise.reject(new Error('failed to dial')) + return await Promise.reject(new Error('failed to dial')) }) // Remove peer used as relay from peerStore and disconnect it await relayLibp2p1.hangUp(relayLibp2p2.peerId) await relayLibp2p1.peerStore.delete(relayLibp2p2.peerId) - expect(relayLibp2p1.connectionManager.size).to.equal(0) + expect(relayLibp2p1.getConnections()).to.be.empty() // Wait for failed dial await deferred.promise @@ -338,66 +279,40 @@ describe('auto-relay', () => { }) describe('flows with 2 max listeners', () => { - let relayLibp2p1 - let relayLibp2p2 - let relayLibp2p3 + let relayLibp2p1: Libp2pNode + let relayLibp2p2: Libp2pNode + let relayLibp2p3: Libp2pNode 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 - }) - }) - }) + [relayLibp2p1, relayLibp2p2, relayLibp2p3] = await Promise.all([ + createNode({ config: createRelayOptions() }), + createNode({ config: createRelayOptions() }), + createNode({ config: createRelayOptions() }) + ]) - beforeEach(() => { // Start each node - return Promise.all([relayLibp2p1, relayLibp2p2, relayLibp2p3].map(libp2p => libp2p.start())) + await Promise.all([relayLibp2p1, relayLibp2p2, relayLibp2p3].map(async libp2p => await libp2p.start())) }) - afterEach(() => { + afterEach(async () => { // Stop each node - return Promise.all([relayLibp2p1, relayLibp2p2, relayLibp2p3].map(libp2p => libp2p.stop())) + return await Promise.all([relayLibp2p1, relayLibp2p2, relayLibp2p3].map(async libp2p => await libp2p.stop())) }) it('should not add listener to a already relayed connection', async () => { // Relay 1 discovers Relay 3 and connect - await relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.getMultiaddrs()) await relayLibp2p1.dial(relayLibp2p3.peerId) await usingAsRelay(relayLibp2p1, relayLibp2p3) // Relay 2 discovers Relay 3 and connect - await relayLibp2p2.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) + await relayLibp2p2.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.getMultiaddrs()) await relayLibp2p2.dial(relayLibp2p3.peerId) await usingAsRelay(relayLibp2p2, relayLibp2p3) // Relay 1 discovers Relay 2 relayed multiaddr via Relay 3 - const ma2RelayedBy3 = relayLibp2p2.multiaddrs[relayLibp2p2.multiaddrs.length - 1] + const ma2RelayedBy3 = relayLibp2p2.getMultiaddrs()[relayLibp2p2.getMultiaddrs().length - 1] await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, [ma2RelayedBy3]) await relayLibp2p1.dial(relayLibp2p2.peerId) @@ -409,64 +324,54 @@ describe('auto-relay', () => { }) describe('discovery', () => { - let local - let remote - let relayLibp2p + let local: Libp2pNode + let remote: Libp2pNode + let relayLibp2p: Libp2pNode + let contentRoutingProvideSpy: Sinon.SinonSpy 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.create({ - host: '0.0.0.0', - protocol: 'http', - port: 60197 - }), [ - new Multiaddr('/ip4/0.0.0.0/tcp/60197') - ]) - - const opts = { - ...baseOptions, - config: { - ...baseOptions.config, + const delegate = new DelegatedContentRouting(createIpfsHttpClient({ + host: '0.0.0.0', + protocol: 'http', + port: 60197 + })) + + ;[local, remote, relayLibp2p] = await Promise.all([ + createNode({ + config: createNodeOptions({ + contentRouters: [ + delegate + ] + }) + }), + createNode({ + config: createNodeOptions({ + contentRouters: [ + delegate + ] + }) + }), + createNode({ + config: createRelayOptions({ 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 + }, + contentRouters: [ + delegate + ] + }) }) - }) + ]) - sinon.spy(relayLibp2p.contentRouting, 'provide') + contentRoutingProvideSpy = sinon.spy(relayLibp2p.contentRouting, 'provide') }) beforeEach(async () => { @@ -474,19 +379,19 @@ describe('auto-relay', () => { // mock the refs call .post('/api/v0/refs') .query(true) - .reply(200, null, [ + .reply(200, undefined, [ 'Content-Type', 'application/json', 'X-Chunked-Output', '1' ]) // Start each node - await Promise.all([local, remote, relayLibp2p].map(libp2p => libp2p.start())) + await Promise.all([local, remote, relayLibp2p].map(async libp2p => await libp2p.start())) // Should provide on start - await pWaitFor(() => relayLibp2p.contentRouting.provide.callCount === 1) + await pWaitFor(() => contentRoutingProvideSpy.callCount === 1) - const provider = relayLibp2p.peerId.toB58String() - const multiaddrs = relayLibp2p.multiaddrs.map((m) => m.toString()) + const provider = relayLibp2p.peerId.toString() + const multiaddrs = relayLibp2p.getMultiaddrs().map(ma => ma.decapsulateCode(protocols('p2p').code)) // Mock findProviders nock('http://0.0.0.0:60197') @@ -498,34 +403,44 @@ describe('auto-relay', () => { ]) }) - afterEach(() => { + afterEach(async () => { // Stop each node - return Promise.all([local, remote, relayLibp2p].map(libp2p => libp2p.stop())) + return await Promise.all([local, remote, relayLibp2p].map(async libp2p => await libp2p.stop())) }) it('should find providers for relay and add it as listen relay', async () => { - const originalMultiaddrsLength = local.multiaddrs.length + const originalMultiaddrsLength = local.getMultiaddrs().length - // Spy add listen relay - sinon.spy(local.relay._autoRelay, '_addListenRelay') // Spy Find Providers - sinon.spy(local.contentRouting, 'findProviders') + const contentRoutingFindProvidersSpy = sinon.spy(local.contentRouting, 'findProviders') + + const relayAddr = relayLibp2p.getMultiaddrs().pop() + + if (relayAddr == null) { + throw new Error('Relay had no addresses') + } + + // connect to relay + await local.dial(relayAddr) + + // should start using the relay + await usingAsRelay(local, relayLibp2p) - // Try to listen on Available hop relays - await local.relay._autoRelay._listenOnAvailableHopRelays() + // disconnect from relay, should start looking for new relays + await local.hangUp(relayAddr) // Should try to find relay service providers - await pWaitFor(() => local.contentRouting.findProviders.callCount === 1) + await pWaitFor(() => contentRoutingFindProvidersSpy.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) + await pWaitFor(() => local.getMultiaddrs().length === originalMultiaddrsLength + 1) - const relayedAddr = local.multiaddrs[local.multiaddrs.length - 1] + const relayedAddr = local.getMultiaddrs()[local.getMultiaddrs().length - 1] await 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() }) }) diff --git a/test/relay/relay.node.js b/test/relay/relay.node.js deleted file mode 100644 index e9c1207875..0000000000 --- a/test/relay/relay.node.js +++ /dev/null @@ -1,165 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') - -const { Multiaddr } = require('multiaddr') -const { collect } = require('streaming-iterables') -const pipe = require('it-pipe') -const AggregateError = require('aggregate-error') -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') - -const { createPeerId } = require('../utils/creators/peer') -const baseOptions = require('../utils/base-options') -const Libp2p = require('../../src') -const { codes: Errors } = require('../../src/errors') - -const listenAddr = '/ip4/0.0.0.0/tcp/0' - -describe('Dialing (via relay, TCP)', () => { - let srcLibp2p - let relayLibp2p - let dstLibp2p - - beforeEach(async () => { - const peerIds = await createPeerId({ number: 3 }) - // Create 3 nodes, and turn HOP on for the relay - ;[srcLibp2p, relayLibp2p, dstLibp2p] = peerIds.map((peerId, index) => { - const opts = baseOptions - index === 1 && (opts.config.relay.hop.enabled = true) - return new Libp2p({ - ...opts, - addresses: { - listen: [listenAddr] - }, - peerId - }) - }) - - dstLibp2p.handle('/echo/1.0.0', ({ stream }) => pipe(stream, stream)) - }) - - beforeEach(() => { - // Start each node - return Promise.all([srcLibp2p, relayLibp2p, dstLibp2p].map(libp2p => libp2p.start())) - }) - - afterEach(async () => { - // Stop each node - return Promise.all([srcLibp2p, relayLibp2p, dstLibp2p].map(libp2p => libp2p.stop())) - }) - - it('should be able to connect to a peer over a relay with active connections', async () => { - const relayAddr = relayLibp2p.transportManager.getAddrs()[0] - const relayIdString = relayLibp2p.peerId.toB58String() - - const dialAddr = relayAddr - .encapsulate(`/p2p/${relayIdString}`) - .encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerId.toB58String()}`) - - const tcpAddrs = dstLibp2p.transportManager.getAddrs() - sinon.stub(dstLibp2p.addressManager, 'listen').value([new Multiaddr(`/p2p-circuit${relayAddr}/p2p/${relayIdString}`)]) - - 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) - expect(connection).to.exist() - expect(connection.remotePeer.toBytes()).to.eql(dstLibp2p.peerId.toBytes()) - expect(connection.localPeer.toBytes()).to.eql(srcLibp2p.peerId.toBytes()) - expect(connection.remoteAddr).to.eql(dialAddr) - expect(connection.localAddr).to.eql( - relayAddr // the relay address - .encapsulate(`/p2p/${relayIdString}`) // with its peer id - .encapsulate('/p2p-circuit') // the local peer is connected over the relay - .encapsulate(`/p2p/${srcLibp2p.peerId.toB58String()}`) // and the local peer id - ) - - const { stream: echoStream } = await connection.newStream('/echo/1.0.0') - const input = uint8ArrayFromString('hello') - const [output] = await pipe( - [input], - echoStream, - collect - ) - - expect(output.slice()).to.eql(input) - }) - - it('should fail to connect to a peer over a relay with inactive connections', async () => { - const relayAddr = relayLibp2p.transportManager.getAddrs()[0] - const relayIdString = relayLibp2p.peerId.toB58String() - - const dialAddr = relayAddr - .encapsulate(`/p2p/${relayIdString}`) - .encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerId.toB58String()}`) - - await expect(srcLibp2p.dial(dialAddr)) - .to.eventually.be.rejectedWith(AggregateError) - .and.to.have.nested.property('._errors[0].code', Errors.ERR_HOP_REQUEST_FAILED) - }) - - it('should not stay connected to a relay when not already connected and HOP fails', async () => { - const relayAddr = relayLibp2p.transportManager.getAddrs()[0] - const relayIdString = relayLibp2p.peerId.toB58String() - - const dialAddr = relayAddr - .encapsulate(`/p2p/${relayIdString}`) - .encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerId.toB58String()}`) - - await expect(srcLibp2p.dial(dialAddr)) - .to.eventually.be.rejectedWith(AggregateError) - .and.to.have.nested.property('._errors[0].code', Errors.ERR_HOP_REQUEST_FAILED) - - // We should not be connected to the relay, because we weren't before the dial - const srcToRelayConn = srcLibp2p.connectionManager.get(relayLibp2p.peerId) - expect(srcToRelayConn).to.not.exist() - }) - - it('dialer should stay connected to an already connected relay on hop failure', async () => { - const relayIdString = relayLibp2p.peerId.toB58String() - const relayAddr = relayLibp2p.transportManager.getAddrs()[0].encapsulate(`/p2p/${relayIdString}`) - - const dialAddr = relayAddr - .encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerId.toB58String()}`) - - await srcLibp2p.dial(relayAddr) - - await expect(srcLibp2p.dial(dialAddr)) - .to.eventually.be.rejectedWith(AggregateError) - .and.to.have.nested.property('._errors[0].code', Errors.ERR_HOP_REQUEST_FAILED) - - const srcToRelayConn = srcLibp2p.connectionManager.get(relayLibp2p.peerId) - expect(srcToRelayConn).to.exist() - expect(srcToRelayConn.stat.status).to.equal('open') - }) - - it('destination peer should stay connected to an already connected relay on hop failure', async () => { - const relayIdString = relayLibp2p.peerId.toB58String() - const relayAddr = relayLibp2p.transportManager.getAddrs()[0].encapsulate(`/p2p/${relayIdString}`) - - const dialAddr = relayAddr - .encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerId.toB58String()}`) - - // Connect the destination peer and the relay - const tcpAddrs = dstLibp2p.transportManager.getAddrs() - sinon.stub(dstLibp2p.addressManager, 'getListenAddrs').returns([new Multiaddr(`${relayAddr}/p2p-circuit`)]) - - 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 - sinon.stub(srcLibp2p, 'multiaddrs').value([{ - bytes: uint8ArrayFromString('an invalid multiaddr') - }]) - - await expect(srcLibp2p.dial(dialAddr)) - .to.eventually.be.rejectedWith(AggregateError) - .and.to.have.nested.property('._errors[0].code', Errors.ERR_HOP_REQUEST_FAILED) - - const dstToRelayConn = dstLibp2p.connectionManager.get(relayLibp2p.peerId) - expect(dstToRelayConn).to.exist() - expect(dstToRelayConn.stat.status).to.equal('open') - }) -}) diff --git a/test/relay/relay.node.ts b/test/relay/relay.node.ts new file mode 100644 index 0000000000..7061f5dba0 --- /dev/null +++ b/test/relay/relay.node.ts @@ -0,0 +1,173 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import sinon from 'sinon' +import { Multiaddr } from '@multiformats/multiaddr' +import { pipe } from 'it-pipe' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { createNode } from '../utils/creators/peer.js' +import { codes as Errors } from '../../src/errors.js' +import type { Libp2pNode } from '../../src/libp2p.js' +import all from 'it-all' +import { RELAY_CODEC } from '../../src/circuit/multicodec.js' +import { StreamHandler } from '../../src/circuit/circuit/stream-handler.js' +import { CircuitRelay } from '../../src/circuit/pb/index.js' +import { createNodeOptions, createRelayOptions } from './utils.js' + +describe('Dialing (via relay, TCP)', () => { + let srcLibp2p: Libp2pNode + let relayLibp2p: Libp2pNode + let dstLibp2p: Libp2pNode + + beforeEach(async () => { + // Create 3 nodes, and turn HOP on for the relay + [srcLibp2p, relayLibp2p, dstLibp2p] = await Promise.all([ + createNode({ + config: createNodeOptions({ + relay: { + autoRelay: { + enabled: false + } + } + }) + }), + createNode({ + config: createRelayOptions({ + relay: { + autoRelay: { + enabled: false + } + } + }) + }), + createNode({ + config: createNodeOptions({ + relay: { + autoRelay: { + enabled: false + } + } + }) + }) + ]) + + await dstLibp2p.handle('/echo/1.0.0', ({ stream }) => { + void pipe(stream, stream) + }) + + // Start each node + await Promise.all([srcLibp2p, relayLibp2p, dstLibp2p].map(async libp2p => await libp2p.start())) + }) + + afterEach(async () => { + // Stop each node + return await Promise.all([srcLibp2p, relayLibp2p, dstLibp2p].map(async libp2p => await libp2p.stop())) + }) + + it('should be able to connect to a peer over a relay with active connections', async () => { + const relayAddr = relayLibp2p.components.getTransportManager().getAddrs()[0] + const relayIdString = relayLibp2p.peerId.toString() + + const dialAddr = relayAddr + .encapsulate(`/p2p/${relayIdString}`) + .encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerId.toString()}`) + + await relayLibp2p.dial(dstLibp2p.getMultiaddrs()[0]) + + const connection = await srcLibp2p.dial(dialAddr) + + expect(connection).to.exist() + expect(connection.remotePeer.toBytes()).to.eql(dstLibp2p.peerId.toBytes()) + expect(connection.remoteAddr).to.eql(dialAddr) + + const { stream: echoStream } = await connection.newStream('/echo/1.0.0') + + const input = uint8ArrayFromString('hello') + const [output] = await pipe( + [input], + echoStream, + async (source) => await all(source) + ) + + expect(output.slice()).to.eql(input) + }) + + it('should fail to connect to a peer over a relay with inactive connections', async () => { + const relayAddr = relayLibp2p.components.getTransportManager().getAddrs()[0] + const relayIdString = relayLibp2p.peerId.toString() + + const dialAddr = relayAddr + .encapsulate(`/p2p/${relayIdString}`) + .encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerId.toString()}`) + + await expect(srcLibp2p.dial(dialAddr)) + .to.eventually.be.rejected() + .and.to.have.nested.property('.errors[0].code', Errors.ERR_HOP_REQUEST_FAILED) + }) + + it('should not stay connected to a relay when not already connected and HOP fails', async () => { + const relayAddr = relayLibp2p.components.getTransportManager().getAddrs()[0] + const relayIdString = relayLibp2p.peerId.toString() + + const dialAddr = relayAddr + .encapsulate(`/p2p/${relayIdString}`) + .encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerId.toString()}`) + + await expect(srcLibp2p.dial(dialAddr)) + .to.eventually.be.rejected() + .and.to.have.nested.property('.errors[0].code', Errors.ERR_HOP_REQUEST_FAILED) + + // We should not be connected to the relay, because we weren't before the dial + const srcToRelayConn = srcLibp2p.components.getConnectionManager().getConnection(relayLibp2p.peerId) + expect(srcToRelayConn).to.not.exist() + }) + + it('dialer should stay connected to an already connected relay on hop failure', async () => { + const relayIdString = relayLibp2p.peerId.toString() + const relayAddr = relayLibp2p.components.getTransportManager().getAddrs()[0].encapsulate(`/p2p/${relayIdString}`) + + const dialAddr = relayAddr + .encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerId.toString()}`) + + await srcLibp2p.dial(relayAddr) + + await expect(srcLibp2p.dial(dialAddr)) + .to.eventually.be.rejected() + .and.to.have.nested.property('.errors[0].code', Errors.ERR_HOP_REQUEST_FAILED) + + const srcToRelayConn = srcLibp2p.components.getConnectionManager().getConnection(relayLibp2p.peerId) + expect(srcToRelayConn).to.exist() + expect(srcToRelayConn?.stat.status).to.equal('OPEN') + }) + + it('destination peer should stay connected to an already connected relay on hop failure', async () => { + const relayIdString = relayLibp2p.peerId.toString() + const relayAddr = relayLibp2p.components.getTransportManager().getAddrs()[0].encapsulate(`/p2p/${relayIdString}`) + + const dialAddr = relayAddr + .encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerId.toString()}`) + + // Connect the destination peer and the relay + const tcpAddrs = dstLibp2p.components.getTransportManager().getAddrs() + sinon.stub(dstLibp2p.components.getAddressManager(), 'getListenAddrs').returns([new Multiaddr(`${relayAddr.toString()}/p2p-circuit`)]) + + await dstLibp2p.components.getTransportManager().listen(dstLibp2p.components.getAddressManager().getListenAddrs()) + expect(dstLibp2p.components.getTransportManager().getAddrs()).to.have.deep.members([...tcpAddrs, dialAddr.decapsulate('p2p')]) + + // send an invalid relay message from the relay to the destination peer + const connections = relayLibp2p.getConnections(dstLibp2p.peerId) + const { stream } = await connections[0].newStream(RELAY_CODEC) + const streamHandler = new StreamHandler({ stream }) + streamHandler.write({ + type: CircuitRelay.Type.STATUS + }) + const res = await streamHandler.read() + expect(res?.code).to.equal(CircuitRelay.Status.MALFORMED_MESSAGE) + streamHandler.close() + + // should still be connected + const dstToRelayConn = dstLibp2p.components.getConnectionManager().getConnection(relayLibp2p.peerId) + expect(dstToRelayConn).to.exist() + expect(dstToRelayConn?.stat.status).to.equal('OPEN') + }) +}) diff --git a/test/relay/utils.ts b/test/relay/utils.ts new file mode 100644 index 0000000000..3e78f26c06 --- /dev/null +++ b/test/relay/utils.ts @@ -0,0 +1,34 @@ +import type { Libp2pOptions } from '../../src/index.js' +import { createBaseOptions } from '../utils/base-options.js' + +const listenAddr = '/ip4/0.0.0.0/tcp/0' + +export function createNodeOptions (...overrides: Libp2pOptions[]): Libp2pOptions { + return createBaseOptions({ + addresses: { + listen: [listenAddr] + }, + connectionManager: { + autoDial: false + }, + relay: { + hop: { + enabled: false + }, + autoRelay: { + enabled: true, + maxListeners: 1 + } + } + }, ...overrides) +} + +export function createRelayOptions (...overrides: Libp2pOptions[]): Libp2pOptions { + return createNodeOptions({ + relay: { + hop: { + enabled: true + } + } + }, ...overrides) +} diff --git a/test/transports/transport-manager.node.js b/test/transports/transport-manager.node.js deleted file mode 100644 index d22d091d5e..0000000000 --- a/test/transports/transport-manager.node.js +++ /dev/null @@ -1,106 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const { MemoryDatastore } = require('datastore-core/memory') -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 sinon = require('sinon') -const Peers = require('../fixtures/peers') -const pWaitFor = require('p-wait-for') -const { mockConnectionGater } = require('../utils/mock-connection-gater') -const addrs = [ - new Multiaddr('/ip4/127.0.0.1/tcp/0'), - new Multiaddr('/ip4/127.0.0.1/tcp/0') -] - -describe('Transport Manager (TCP)', () => { - const connectionGater = mockConnectionGater() - let tm - let localPeer - - before(async () => { - localPeer = await PeerId.createFromJSON(Peers[0]) - }) - - beforeEach(() => { - tm = new TransportManager({ - libp2p: { - peerId: localPeer, - multiaddrs: addrs, - addressManager: new AddressManager({ listen: addrs }), - peerStore: new PeerStore({ - peerId: localPeer, - datastore: new MemoryDatastore(), - addressFilter: connectionGater.filterMultiaddrForPeer - }) - }, - upgrader: mockUpgrader, - onConnection: () => {} - }) - }) - - afterEach(async () => { - await tm.removeAll() - expect(tm._transports.size).to.equal(0) - }) - - it('should be able to add and remove a transport', async () => { - tm.add(Transport.prototype[Symbol.toStringTag], Transport) - expect(tm._transports.size).to.equal(1) - await tm.remove(Transport.prototype[Symbol.toStringTag]) - }) - - it('should be able to listen', async () => { - tm.add(Transport.prototype[Symbol.toStringTag], Transport, { listenerOptions: { listen: 'carefully' } }) - const transport = tm._transports.get(Transport.prototype[Symbol.toStringTag]) - const spyListener = sinon.spy(transport, 'createListener') - 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) - expect(spyListener.firstCall.firstArg).to.deep.equal({ listen: 'carefully' }) - }) - - it('should create self signed peer record on listen', async () => { - let signedPeerRecord = await tm.libp2p.peerStore.addressBook.getRawEnvelope(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, but it is done async - // with no event so we have to wait a bit - await pWaitFor(async () => { - signedPeerRecord = await tm.libp2p.peerStore.addressBook.getPeerRecord(localPeer) - - return signedPeerRecord != null - }, { interval: 100, timeout: 2000 }) - - 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(addrs) - const addr = tm.getAddrs().shift() - const connection = await tm.dial(addr) - expect(connection).to.exist() - await connection.close() - }) -}) diff --git a/test/transports/transport-manager.node.ts b/test/transports/transport-manager.node.ts new file mode 100644 index 0000000000..0098382ed5 --- /dev/null +++ b/test/transports/transport-manager.node.ts @@ -0,0 +1,123 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import { MemoryDatastore } from 'datastore-core/memory' +import { DefaultAddressManager } from '../../src/address-manager/index.js' +import { DefaultTransportManager } from '../../src/transport-manager.js' +import { PersistentPeerStore } from '@libp2p/peer-store' +import { PeerRecord } from '@libp2p/peer-record' +import { TCP } from '@libp2p/tcp' +import { Multiaddr } from '@multiformats/multiaddr' +import { mockUpgrader, mockConnectionGater } from '@libp2p/interface-compliance-tests/mocks' +import sinon from 'sinon' +import Peers from '../fixtures/peers.js' +import pWaitFor from 'p-wait-for' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import { createFromJSON } from '@libp2p/peer-id-factory' +import { Components } from '@libp2p/interfaces/components' +import { PeerRecordUpdater } from '../../src/peer-record-updater.js' + +const addrs = [ + new Multiaddr('/ip4/127.0.0.1/tcp/0'), + new Multiaddr('/ip4/127.0.0.1/tcp/0') +] + +describe('Transport Manager (TCP)', () => { + const connectionGater = mockConnectionGater() + let tm: DefaultTransportManager + let localPeer: PeerId + let components: Components + + before(async () => { + localPeer = await createFromJSON(Peers[0]) + }) + + beforeEach(() => { + components = new Components({ + peerId: localPeer, + datastore: new MemoryDatastore(), + upgrader: mockUpgrader() + }) + components.setAddressManager(new DefaultAddressManager(components, { listen: addrs.map(addr => addr.toString()) })) + components.setPeerStore(new PersistentPeerStore(components, { + addressFilter: connectionGater.filterMultiaddrForPeer + })) + + tm = new DefaultTransportManager(components) + + components.setTransportManager(tm) + }) + + afterEach(async () => { + await tm.removeAll() + expect(tm.getTransports()).to.be.empty() + }) + + it('should be able to add and remove a transport', async () => { + expect(tm.getTransports()).to.have.lengthOf(0) + tm.add(new TCP()) + expect(tm.getTransports()).to.have.lengthOf(1) + await tm.remove(TCP.prototype[Symbol.toStringTag]) + expect(tm.getTransports()).to.have.lengthOf(0) + }) + + it('should be able to listen', async () => { + const transport = new TCP() + + expect(tm.getTransports()).to.be.empty() + + tm.add(transport) + + expect(tm.getTransports()).to.have.lengthOf(1) + + const spyListener = sinon.spy(transport, 'createListener') + await tm.listen(addrs) + + // Ephemeral ip addresses may result in multiple listeners + expect(tm.getAddrs().length).to.equal(addrs.length) + await tm.stop() + expect(spyListener.called).to.be.true() + }) + + it('should create self signed peer record on listen', async () => { + const peerRecordUpdater = new PeerRecordUpdater(components) + await peerRecordUpdater.start() + + let signedPeerRecord = await components.getPeerStore().addressBook.getPeerRecord(localPeer) + expect(signedPeerRecord).to.not.exist() + + tm.add(new TCP()) + await tm.listen(addrs) + + // Should created Self Peer record on new listen address, but it is done async + // with no event so we have to wait a bit + await pWaitFor(async () => { + signedPeerRecord = await components.getPeerStore().addressBook.getPeerRecord(localPeer) + + return signedPeerRecord != null + }, { interval: 100, timeout: 2000 }) + + if (signedPeerRecord == null) { + throw new Error('Could not get signed peer record') + } + + const record = PeerRecord.createFromProtobuf(signedPeerRecord.payload) + expect(record).to.exist() + expect(record.multiaddrs.length).to.equal(addrs.length) + await peerRecordUpdater.stop() + }) + + it('should be able to dial', async () => { + tm.add(new TCP()) + await tm.listen(addrs) + const addr = tm.getAddrs().shift() + + if (addr == null) { + throw new Error('Could not find addr') + } + + const connection = await tm.dial(addr) + expect(connection).to.exist() + await connection.close() + }) +}) diff --git a/test/transports/transport-manager.spec.js b/test/transports/transport-manager.spec.js deleted file mode 100644 index 23006cdd6b..0000000000 --- a/test/transports/transport-manager.spec.js +++ /dev/null @@ -1,247 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') - -const { Multiaddr } = require('multiaddr') -const Transport = require('libp2p-websockets') -const filters = require('libp2p-websockets/src/filters') -const { NOISE: Crypto } = require('@chainsafe/libp2p-noise') -const AddressManager = require('../../src/address-manager') -const TransportManager = require('../../src/transport-manager') -const mockUpgrader = require('../utils/mockUpgrader') -const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser') -const { codes: ErrorCodes } = require('../../src/errors') -const Libp2p = require('../../src') -const { FaultTolerance } = require('../../src/transport-manager') - -const Peers = require('../fixtures/peers') -const PeerId = require('peer-id') - -const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') - -describe('Transport Manager (WebSockets)', () => { - let tm - - before(() => { - tm = new TransportManager({ - libp2p: { - addressManager: new AddressManager({ listen: [listenAddr] }) - }, - upgrader: mockUpgrader, - onConnection: () => {} - }) - }) - - afterEach(async () => { - await tm.removeAll() - expect(tm._transports.size).to.equal(0) - }) - - it('should be able to add and remove a transport', async () => { - tm.add(Transport.prototype[Symbol.toStringTag], Transport, { filter: filters.all }) - expect(tm._transports.size).to.equal(1) - await tm.remove(Transport.prototype[Symbol.toStringTag]) - }) - - it('should not be able to add a transport without a key', async () => { - // Chai as promised conflicts with normal `throws` validation, - // so wrap the call in an async function - await expect((async () => { // eslint-disable-line - tm.add(undefined, Transport) - })()) - .to.eventually.be.rejected() - .and.to.have.property('code', ErrorCodes.ERR_INVALID_KEY) - }) - - it('should not be able to add a transport twice', async () => { - tm.add(Transport.prototype[Symbol.toStringTag], Transport) - // Chai as promised conflicts with normal `throws` validation, - // so wrap the call in an async function - await expect((async () => { // eslint-disable-line - tm.add(Transport.prototype[Symbol.toStringTag], Transport) - })()) - .to.eventually.be.rejected() - .and.to.have.property('code', ErrorCodes.ERR_DUPLICATE_TRANSPORT) - }) - - it('should be able to dial', async () => { - tm.add(Transport.prototype[Symbol.toStringTag], Transport, { filter: filters.all }) - const addr = MULTIADDRS_WEBSOCKETS[0] - const connection = await tm.dial(addr) - expect(connection).to.exist() - await connection.close() - }) - - it('should fail to dial an unsupported address', async () => { - tm.add(Transport.prototype[Symbol.toStringTag], Transport, { filter: filters.all }) - const addr = new Multiaddr('/ip4/127.0.0.1/tcp/0') - await expect(tm.dial(addr)) - .to.eventually.be.rejected() - .and.to.have.property('code', ErrorCodes.ERR_TRANSPORT_UNAVAILABLE) - }) - - it('should fail to listen with no valid address', async () => { - tm.add(Transport.prototype[Symbol.toStringTag], Transport, { filter: filters.all }) - - await expect(tm.listen([listenAddr])) - .to.eventually.be.rejected() - .and.to.have.property('code', ErrorCodes.ERR_NO_VALID_ADDRESSES) - }) -}) - -describe('libp2p.transportManager', () => { - let peerId - let libp2p - - before(async () => { - peerId = await PeerId.createFromJSON(Peers[0]) - }) - - afterEach(async () => { - sinon.restore() - libp2p && await libp2p.stop() - libp2p = null - }) - - it('should create a TransportManager', () => { - libp2p = new Libp2p({ - peerId, - modules: { - transport: [Transport], - connEncryption: [Crypto] - } - }) - - expect(libp2p.transportManager).to.exist() - // Our transport and circuit relay - expect(libp2p.transportManager._transports.size).to.equal(2) - }) - - it('should be able to customize a transport', () => { - const spy = sinon.spy() - const key = spy.prototype[Symbol.toStringTag] = 'TransportSpy' - const customOptions = { - another: 'value', - listenerOptions: { - listen: 'carefully' - } - } - libp2p = new Libp2p({ - peerId, - modules: { - transport: [spy], - connEncryption: [Crypto] - }, - config: { - transport: { - [key]: customOptions - } - } - }) - - expect(libp2p.transportManager).to.exist() - // Our transport and circuit relay - expect(libp2p.transportManager._transports.size).to.equal(2) - expect(libp2p.transportManager._listenerOptions.size).to.equal(2) - expect(spy).to.have.property('callCount', 1) - expect(spy.getCall(0)).to.have.deep.property('args', [{ - ...customOptions, - libp2p, - upgrader: libp2p.upgrader - }]) - }) - - it('starting and stopping libp2p should start and stop TransportManager', async () => { - libp2p = new Libp2p({ - peerId, - modules: { - transport: [Transport], - connEncryption: [Crypto] - } - }) - - // We don't need to listen, stub it - sinon.stub(libp2p.transportManager, 'listen').returns(true) - sinon.spy(libp2p.transportManager, 'close') - - await libp2p.start() - await libp2p.stop() - - expect(libp2p.transportManager.listen.callCount).to.equal(1) - expect(libp2p.transportManager.close.callCount).to.equal(1) - }) -}) - -describe('libp2p.transportManager (dial only)', () => { - let peerId - let libp2p - - before(async () => { - peerId = await PeerId.createFromJSON(Peers[0]) - }) - - afterEach(async () => { - sinon.restore() - libp2p && await libp2p.stop() - }) - - it('fails to start if multiaddr fails to listen', async () => { - libp2p = new Libp2p({ - peerId, - addresses: { - listen: [new Multiaddr('/ip4/127.0.0.1/tcp/0')] - }, - modules: { - transport: [Transport], - connEncryption: [Crypto] - } - }) - - try { - await libp2p.start() - } catch (/** @type {any} */ err) { - expect(err).to.exist() - expect(err.code).to.equal(ErrorCodes.ERR_NO_VALID_ADDRESSES) - return - } - throw new Error('it should fail to start if multiaddr fails to listen') - }) - - it('does not fail to start if provided listen multiaddr are not compatible to configured transports (when supporting dial only mode)', async () => { - libp2p = new Libp2p({ - peerId, - addresses: { - listen: [new Multiaddr('/ip4/127.0.0.1/tcp/0')] - }, - transportManager: { - faultTolerance: FaultTolerance.NO_FATAL - }, - modules: { - transport: [Transport], - connEncryption: [Crypto] - } - }) - - await libp2p.start() - }) - - it('does not fail to start if provided listen multiaddr fail to listen on configured transports (when supporting dial only mode)', async () => { - libp2p = new Libp2p({ - peerId, - addresses: { - listen: [new Multiaddr('/ip4/127.0.0.1/tcp/12345/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3/p2p-circuit')] - }, - transportManager: { - faultTolerance: FaultTolerance.NO_FATAL - }, - modules: { - transport: [Transport], - connEncryption: [Crypto] - } - }) - - await libp2p.start() - }) -}) diff --git a/test/transports/transport-manager.spec.ts b/test/transports/transport-manager.spec.ts new file mode 100644 index 0000000000..93721fc5c7 --- /dev/null +++ b/test/transports/transport-manager.spec.ts @@ -0,0 +1,158 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import sinon from 'sinon' +import { Multiaddr } from '@multiformats/multiaddr' +import { WebSockets } from '@libp2p/websockets' +import * as filters from '@libp2p/websockets/filters' +import { NOISE } from '@chainsafe/libp2p-noise' +import { DefaultAddressManager } from '../../src/address-manager/index.js' +import { DefaultTransportManager, FAULT_TOLERANCE } from '../../src/transport-manager.js' +import { mockUpgrader } from '@libp2p/interface-compliance-tests/mocks' +import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' +import { codes as ErrorCodes } from '../../src/errors.js' +import Peers from '../fixtures/peers.js' +import { Components } from '@libp2p/interfaces/components' +import { createEd25519PeerId, createFromJSON } from '@libp2p/peer-id-factory' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' + +const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') + +describe('Transport Manager (WebSockets)', () => { + let tm: DefaultTransportManager + let components: Components + + before(async () => { + components = new Components({ + peerId: await createEd25519PeerId(), + upgrader: mockUpgrader() + }) + components.setAddressManager(new DefaultAddressManager(components, { listen: [listenAddr.toString()] })) + + tm = new DefaultTransportManager(components) + }) + + afterEach(async () => { + await tm.removeAll() + expect(tm.getTransports()).to.be.empty() + }) + + it('should be able to add and remove a transport', async () => { + const transport = new WebSockets({ + filter: filters.all + }) + + expect(tm.getTransports()).to.have.lengthOf(0) + tm.add(transport) + + expect(tm.getTransports()).to.have.lengthOf(1) + await tm.remove(transport.constructor.name) + expect(tm.getTransports()).to.have.lengthOf(0) + }) + + it('should not be able to add a transport twice', async () => { + tm.add(new WebSockets()) + + expect(() => { + tm.add(new WebSockets()) + }) + .to.throw() + .and.to.have.property('code', ErrorCodes.ERR_DUPLICATE_TRANSPORT) + }) + + it('should be able to dial', async () => { + tm.add(new WebSockets({ filter: filters.all })) + const addr = MULTIADDRS_WEBSOCKETS[0] + const connection = await tm.dial(addr) + expect(connection).to.exist() + await connection.close() + }) + + it('should fail to dial an unsupported address', async () => { + tm.add(new WebSockets({ filter: filters.all })) + const addr = new Multiaddr('/ip4/127.0.0.1/tcp/0') + await expect(tm.dial(addr)) + .to.eventually.be.rejected() + .and.to.have.property('code', ErrorCodes.ERR_TRANSPORT_UNAVAILABLE) + }) + + it('should fail to listen with no valid address', async () => { + tm.add(new WebSockets({ filter: filters.all })) + + await expect(tm.listen([listenAddr])) + .to.eventually.be.rejected() + .and.to.have.property('code', ErrorCodes.ERR_NO_VALID_ADDRESSES) + }) +}) + +describe('libp2p.transportManager (dial only)', () => { + let peerId: PeerId + let libp2p: Libp2pNode + + before(async () => { + peerId = await createFromJSON(Peers[0]) + }) + + afterEach(async () => { + sinon.restore() + + if (libp2p != null) { + await libp2p.stop() + } + }) + + it('fails to start if multiaddr fails to listen', async () => { + libp2p = await createLibp2pNode({ + peerId, + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + transports: [new WebSockets()], + connectionEncryption: [NOISE] + }) + + await expect(libp2p.start()).to.eventually.be.rejected + .with.property('code', ErrorCodes.ERR_NO_VALID_ADDRESSES) + }) + + it('does not fail to start if provided listen multiaddr are not compatible to configured transports (when supporting dial only mode)', async () => { + libp2p = await createLibp2pNode({ + peerId, + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + transportManager: { + faultTolerance: FAULT_TOLERANCE.NO_FATAL + }, + transports: [ + new WebSockets() + ], + connectionEncryption: [ + NOISE + ] + }) + + await libp2p.start() + }) + + it('does not fail to start if provided listen multiaddr fail to listen on configured transports (when supporting dial only mode)', async () => { + libp2p = await createLibp2pNode({ + peerId, + addresses: { + listen: ['/ip4/127.0.0.1/tcp/12345/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3/p2p-circuit'] + }, + transportManager: { + faultTolerance: FAULT_TOLERANCE.NO_FATAL + }, + transports: [ + new WebSockets() + ], + connectionEncryption: [ + NOISE + ] + }) + + await libp2p.start() + }) +}) diff --git a/test/ts-use/package.json b/test/ts-use/package.json deleted file mode 100644 index 87ec9b5bf9..0000000000 --- a/test/ts-use/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "ts-use", - "private": true, - "dependencies": { - "@achingbrain/libp2p-gossipsub": "^0.12.2", - "@chainsafe/libp2p-noise": "^5.0.0", - "datastore-level": "^7.0.1", - "ipfs-http-client": "^55.0.0", - "libp2p": "file:../..", - "libp2p-bootstrap": "^0.14.0", - "libp2p-delegated-content-routing": "^0.11.0", - "libp2p-delegated-peer-routing": "^0.11.1", - "libp2p-interfaces": "^4.0.0", - "libp2p-kad-dht": "^0.28.6", - "libp2p-mplex": "^0.10.4", - "libp2p-record": "^0.10.4", - "libp2p-tcp": "^0.17.1", - "libp2p-websockets": "^0.16.1", - "peer-id": "^0.16.0" - }, - "scripts": { - "build": "npx tsc", - "test": "npm install && npx -p typescript tsc --noEmit" - } -} diff --git a/test/ts-use/src/main.ts b/test/ts-use/src/main.ts deleted file mode 100644 index 15b927b80d..0000000000 --- a/test/ts-use/src/main.ts +++ /dev/null @@ -1,195 +0,0 @@ -import Libp2p = require('libp2p') -import Libp2pRecord = require('libp2p-record') -import TCP = require('libp2p-tcp') - -const WEBSOCKETS = require('libp2p-websockets') -const NOISE = require('@chainsafe/libp2p-noise') -const MPLEX = require('libp2p-mplex') -const Gossipsub = require('libp2p-gossipsub') -const DHT = require('libp2p-kad-dht') - -const { dnsaddrResolver } = require('multiaddr/src/resolvers') -const { publicAddressesFirst } = require('libp2p-utils/src/address-sort') - -const { SignaturePolicy } = require('libp2p-interfaces/src/pubsub/signature-policy') -const { FaultTolerance } = require('libp2p/src/transport-manager') -const filters = require('libp2p-websockets/src/filters') - -const Bootstrap = require('libp2p-bootstrap') -const LevelStore = require('datastore-level') - -const ipfsHttpClient = require('ipfs-http-client') -const DelegatedPeerRouter = require('libp2p-delegated-peer-routing') -const DelegatedContentRouter = require('libp2p-delegated-content-routing') -const PeerId = require('peer-id') - - -// Known peers addresses -const bootstrapMultiaddrs = [ - '/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb', - '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN' -] -const transportKey = WEBSOCKETS.prototype[Symbol.toStringTag] - -async function main() { - // create a peerId - const peerId = await PeerId.create() - - const delegatedPeerRouting = new DelegatedPeerRouter(ipfsHttpClient.create({ - host: 'node0.delegate.ipfs.io', // In production you should setup your own delegates - protocol: 'https', - port: 443 - })) - - const delegatedContentRouting = new DelegatedContentRouter(peerId, ipfsHttpClient.create({ - host: 'node0.delegate.ipfs.io', // In production you should setup your own delegates - protocol: 'https', - port: 443 - })) - - const libp2p = await Libp2p.create({ - peerId, - addresses: { - listen: ['/ip4/127.0.0.1/tcp/8000', '/ip4/127.0.0.1/tcp/8001/ws'] - }, - modules: { - transport: [TCP, WEBSOCKETS], - streamMuxer: [MPLEX], - connEncryption: [NOISE], - peerDiscovery: [Bootstrap], - pubsub: Gossipsub, - dht: DHT, - contentRouting: [delegatedContentRouting], - peerRouting: [delegatedPeerRouting] - }, - peerRouting: { - refreshManager: { - enabled: true, - interval: 1000, - bootDelay: 11111 - } - }, - dialer: { - maxParallelDials: 100, - maxDialsPerPeer: 4, - dialTimeout: 30e3, - resolvers: { - dnsaddr: dnsaddrResolver - }, - addressSorter: publicAddressesFirst - }, - connectionManager: { - maxConnections: Infinity, - minConnections: 0, - pollInterval: 2000, - defaultPeerValue: 1, - maxData: Infinity, - maxSentData: Infinity, - maxReceivedData: Infinity, - maxEventLoopDelay: Infinity, - movingAverageInterval: 60000 - }, - transportManager: { - faultTolerance: FaultTolerance.NO_FATAL - }, - metrics: { - enabled: true, - computeThrottleMaxQueueSize: 1000, - computeThrottleTimeout: 2000, - movingAverageIntervals: [ - 60 * 1000, // 1 minute - 5 * 60 * 1000, // 5 minutes - 15 * 60 * 1000 // 15 minutes - ], - maxOldPeersRetention: 50 - }, - datastore: new LevelStore('path/to/store'), - peerStore: { - persistence: false - }, - keychain: { - pass: 'notsafepassword123456789', - datastore: new LevelStore('path/to/store-keys') - }, - config: { - peerDiscovery: { - autoDial: true, - [Bootstrap.tag]: { - enabled: true, - list: bootstrapMultiaddrs // provide array of multiaddrs - } - }, - dht: { - enabled: true, - kBucketSize: 20, - clientMode: true, - validators: { - pk: Libp2pRecord.validator.validators.pk - }, - selectors: { - pk: Libp2pRecord.selection.selectors.pk - } - }, - nat: { - description: 'my-node', // set as the port mapping description on the router, defaults the current libp2p version and your peer id - enabled: true, // defaults to true - gateway: '192.168.1.1', // leave unset to auto-discover - externalIp: '80.1.1.1', // leave unset to auto-discover - ttl: 7200, // TTL for port mappings (min 20 minutes) - keepAlive: true, // Refresh port mapping after TTL expires - pmp: { - enabled: false, // defaults to false - } - }, - relay: { - 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 - }, - 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 - } - }, - transport: { - [transportKey]: { - filter: filters.all - } - }, - pubsub: { // The pubsub options (and defaults) can be found in the pubsub router documentation - enabled: true, - emitSelf: false, // whether the node should emit to self on publish - globalSignaturePolicy: SignaturePolicy.StrictSign // message signing policy - } - } - }) - - libp2p.connectionManager.on('peer:connect', (connection) => { - console.log(`Connected to ${connection.remotePeer.toB58String()}`) - }) - - - - // Listen for new connections to peers - libp2p.connectionManager.on('peer:connect', (connection) => { - console.log(`Connected to ${connection.remotePeer.toB58String()}`) - }) - - // Listen for peers disconnecting - libp2p.connectionManager.on('peer:disconnect', (connection) => { - console.log(`Disconnected from ${connection.remotePeer.toB58String()}`) - }) - - - await libp2p.start() - console.log('started') - await libp2p.stop() -} - -main() diff --git a/test/ts-use/tsconfig.json b/test/ts-use/tsconfig.json deleted file mode 100644 index 8cd47e1e86..0000000000 --- a/test/ts-use/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "compilerOptions": { - "strict": true, - "noImplicitAny": true, - "skipLibCheck": true - } -} \ No newline at end of file diff --git a/test/upgrading/upgrader.spec.js b/test/upgrading/upgrader.spec.js deleted file mode 100644 index 5caf63f25e..0000000000 --- a/test/upgrading/upgrader.spec.js +++ /dev/null @@ -1,477 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') -const Muxer = require('libp2p-mplex') -const { Multiaddr } = require('multiaddr') -const PeerId = require('peer-id') -const pipe = require('it-pipe') -const { collect } = require('streaming-iterables') -const pSettle = require('p-settle') -const Transport = require('libp2p-websockets') -const { NOISE: Crypto } = require('@chainsafe/libp2p-noise') -const Protector = require('../../src/pnet') -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') -const swarmKeyBuffer = uint8ArrayFromString(require('../fixtures/swarm.key')) - -const Libp2p = require('../../src') -const Upgrader = require('../../src/upgrader') -const { codes } = require('../../src/errors') -const { mockConnectionGater } = require('../utils/mock-connection-gater') -const mockMultiaddrConnPair = require('../utils/mockMultiaddrConn') -const Peers = require('../fixtures/peers') -const addrs = [ - new Multiaddr('/ip4/127.0.0.1/tcp/0'), - new Multiaddr('/ip4/127.0.0.1/tcp/0') -] - -describe('Upgrader', () => { - let localUpgrader - let remoteUpgrader - let localPeer - let remotePeer - const connectionGater = mockConnectionGater() - - const mockConnectionManager = { - gater: { - allowDialPeer: async () => true, - allowDialMultiaddr: async () => true, - acceptConnection: async () => true, - acceptEncryptedConnection: async () => true, - acceptUpgradedConnection: async () => true - } - } - - before(async () => { - ([ - localPeer, - remotePeer - ] = await Promise.all([ - PeerId.createFromJSON(Peers[0]), - PeerId.createFromJSON(Peers[1]) - ])) - - localUpgrader = new Upgrader({ - connectionManager: mockConnectionManager, - localPeer, - connectionGater - }) - remoteUpgrader = new Upgrader({ - connectionManager: mockConnectionManager, - localPeer: remotePeer, - connectionGater - }) - - localUpgrader.protocols.set('/echo/1.0.0', ({ stream }) => pipe(stream, stream)) - remoteUpgrader.protocols.set('/echo/1.0.0', ({ stream }) => pipe(stream, stream)) - }) - - afterEach(() => { - sinon.restore() - }) - - it('should upgrade with valid muxers and crypto', async () => { - const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) - - const muxers = new Map([[Muxer.multicodec, Muxer]]) - sinon.stub(localUpgrader, 'muxers').value(muxers) - sinon.stub(remoteUpgrader, 'muxers').value(muxers) - - const cryptos = new Map([[Crypto.protocol, Crypto]]) - sinon.stub(localUpgrader, 'cryptos').value(cryptos) - sinon.stub(remoteUpgrader, 'cryptos').value(cryptos) - - const connections = await Promise.all([ - localUpgrader.upgradeOutbound(outbound), - remoteUpgrader.upgradeInbound(inbound) - ]) - - expect(connections).to.have.length(2) - - const { stream, protocol } = await connections[0].newStream('/echo/1.0.0') - expect(protocol).to.equal('/echo/1.0.0') - - const hello = uint8ArrayFromString('hello there!') - const result = await pipe( - [hello], - stream, - function toBuffer (source) { - return (async function * () { - for await (const val of source) yield val.slice() - })() - }, - collect - ) - - expect(result).to.eql([hello]) - }) - - it('should upgrade with only crypto', async () => { - const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) - - // No available muxers - const muxers = new Map() - sinon.stub(localUpgrader, 'muxers').value(muxers) - sinon.stub(remoteUpgrader, 'muxers').value(muxers) - - const cryptos = new Map([[Crypto.protocol, Crypto]]) - sinon.stub(localUpgrader, 'cryptos').value(cryptos) - sinon.stub(remoteUpgrader, 'cryptos').value(cryptos) - - const connections = await Promise.all([ - localUpgrader.upgradeOutbound(outbound), - remoteUpgrader.upgradeInbound(inbound) - ]) - - expect(connections).to.have.length(2) - - await expect(connections[0].newStream('/echo/1.0.0')).to.be.rejected() - - // Verify the MultiaddrConnection close method is called - sinon.spy(inbound, 'close') - sinon.spy(outbound, 'close') - await Promise.all(connections.map(conn => conn.close())) - expect(inbound.close.callCount).to.equal(1) - expect(outbound.close.callCount).to.equal(1) - }) - - it('should use a private connection protector when provided', async () => { - const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) - - const muxers = new Map([[Muxer.multicodec, Muxer]]) - sinon.stub(localUpgrader, 'muxers').value(muxers) - sinon.stub(remoteUpgrader, 'muxers').value(muxers) - - const cryptos = new Map([[Crypto.protocol, Crypto]]) - sinon.stub(localUpgrader, 'cryptos').value(cryptos) - sinon.stub(remoteUpgrader, 'cryptos').value(cryptos) - - const protector = new Protector(swarmKeyBuffer) - sinon.stub(localUpgrader, 'protector').value(protector) - sinon.stub(remoteUpgrader, 'protector').value(protector) - sinon.spy(protector, 'protect') - - const connections = await Promise.all([ - localUpgrader.upgradeOutbound(outbound), - remoteUpgrader.upgradeInbound(inbound) - ]) - - expect(connections).to.have.length(2) - - const { stream, protocol } = await connections[0].newStream('/echo/1.0.0') - expect(protocol).to.equal('/echo/1.0.0') - - const hello = uint8ArrayFromString('hello there!') - const result = await pipe( - [hello], - stream, - function toBuffer (source) { - return (async function * () { - for await (const val of source) yield val.slice() - })() - }, - collect - ) - - expect(result).to.eql([hello]) - expect(protector.protect.callCount).to.eql(2) - }) - - it('should fail if crypto fails', async () => { - const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) - - const muxers = new Map([[Muxer.multicodec, Muxer]]) - sinon.stub(localUpgrader, 'muxers').value(muxers) - sinon.stub(remoteUpgrader, 'muxers').value(muxers) - - const crypto = { - tag: '/insecure', - secureInbound: () => { throw new Error('Boom') }, - secureOutbound: () => { throw new Error('Boom') } - } - - const cryptos = new Map([[crypto.tag, crypto]]) - sinon.stub(localUpgrader, 'cryptos').value(cryptos) - sinon.stub(remoteUpgrader, 'cryptos').value(cryptos) - - // Wait for the results of each side of the connection - const results = await pSettle([ - localUpgrader.upgradeOutbound(outbound), - remoteUpgrader.upgradeInbound(inbound) - ]) - - // Ensure both sides fail - expect(results).to.have.length(2) - results.forEach(result => { - expect(result.isRejected).to.equal(true) - expect(result.reason.code).to.equal(codes.ERR_ENCRYPTION_FAILED) - }) - }) - - it('should fail if muxers do not match', async () => { - const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) - - const muxersLocal = new Map([['/muxer-local', Muxer]]) - const muxersRemote = new Map([['/muxer-remote', Muxer]]) - sinon.stub(localUpgrader, 'muxers').value(muxersLocal) - sinon.stub(remoteUpgrader, 'muxers').value(muxersRemote) - - const cryptos = new Map([[Crypto.protocol, Crypto]]) - sinon.stub(localUpgrader, 'cryptos').value(cryptos) - sinon.stub(remoteUpgrader, 'cryptos').value(cryptos) - - // Wait for the results of each side of the connection - const results = await pSettle([ - localUpgrader.upgradeOutbound(outbound), - remoteUpgrader.upgradeInbound(inbound) - ]) - - // Ensure both sides fail - expect(results).to.have.length(2) - results.forEach(result => { - expect(result.isRejected).to.equal(true) - expect(result.reason.code).to.equal(codes.ERR_MUXER_UNAVAILABLE) - }) - }) - - it('should map getStreams and close methods', async () => { - const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) - - const muxers = new Map([[Muxer.multicodec, Muxer]]) - sinon.stub(localUpgrader, 'muxers').value(muxers) - sinon.stub(remoteUpgrader, 'muxers').value(muxers) - - const cryptos = new Map([[Crypto.protocol, Crypto]]) - sinon.stub(localUpgrader, 'cryptos').value(cryptos) - sinon.stub(remoteUpgrader, 'cryptos').value(cryptos) - - const connections = await Promise.all([ - localUpgrader.upgradeOutbound(outbound), - remoteUpgrader.upgradeInbound(inbound) - ]) - - expect(connections).to.have.length(2) - - // Create a few streams, at least 1 in each direction - await connections[0].newStream('/echo/1.0.0') - await connections[1].newStream('/echo/1.0.0') - await connections[0].newStream('/echo/1.0.0') - connections.forEach(conn => { - expect(conn.streams).to.have.length(3) - }) - - // Verify the MultiaddrConnection close method is called - sinon.spy(inbound, 'close') - sinon.spy(outbound, 'close') - await Promise.all(connections.map(conn => conn.close())) - expect(inbound.close.callCount).to.equal(1) - expect(outbound.close.callCount).to.equal(1) - }) - - it('should call connection handlers', async () => { - const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) - - const muxers = new Map([[Muxer.multicodec, Muxer]]) - sinon.stub(localUpgrader, 'muxers').value(muxers) - sinon.stub(remoteUpgrader, 'muxers').value(muxers) - - const cryptos = new Map([[Crypto.protocol, Crypto]]) - sinon.stub(localUpgrader, 'cryptos').value(cryptos) - sinon.stub(remoteUpgrader, 'cryptos').value(cryptos) - - // Verify onConnection is called with the connection - sinon.spy(localUpgrader, 'onConnection') - sinon.spy(remoteUpgrader, 'onConnection') - const connections = await Promise.all([ - localUpgrader.upgradeOutbound(outbound), - remoteUpgrader.upgradeInbound(inbound) - ]) - expect(connections).to.have.length(2) - expect(localUpgrader.onConnection.callCount).to.equal(1) - expect(localUpgrader.onConnection.getCall(0).args).to.eql([connections[0]]) - expect(remoteUpgrader.onConnection.callCount).to.equal(1) - expect(remoteUpgrader.onConnection.getCall(0).args).to.eql([connections[1]]) - - // Verify onConnectionEnd is called with the connection - sinon.spy(localUpgrader, 'onConnectionEnd') - sinon.spy(remoteUpgrader, 'onConnectionEnd') - await Promise.all(connections.map(conn => conn.close())) - expect(localUpgrader.onConnectionEnd.callCount).to.equal(1) - expect(localUpgrader.onConnectionEnd.getCall(0).args).to.eql([connections[0]]) - expect(remoteUpgrader.onConnectionEnd.callCount).to.equal(1) - expect(remoteUpgrader.onConnectionEnd.getCall(0).args).to.eql([connections[1]]) - }) - - it('should fail to create a stream for an unsupported protocol', async () => { - const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) - - const muxers = new Map([[Muxer.multicodec, Muxer]]) - sinon.stub(localUpgrader, 'muxers').value(muxers) - sinon.stub(remoteUpgrader, 'muxers').value(muxers) - - const cryptos = new Map([[Crypto.protocol, Crypto]]) - sinon.stub(localUpgrader, 'cryptos').value(cryptos) - sinon.stub(remoteUpgrader, 'cryptos').value(cryptos) - - const connections = await Promise.all([ - localUpgrader.upgradeOutbound(outbound), - remoteUpgrader.upgradeInbound(inbound) - ]) - - expect(connections).to.have.length(2) - - const results = await pSettle([ - connections[0].newStream('/unsupported/1.0.0'), - connections[1].newStream('/unsupported/1.0.0') - ]) - expect(results).to.have.length(2) - results.forEach(result => { - expect(result.isRejected).to.equal(true) - expect(result.reason.code).to.equal(codes.ERR_UNSUPPORTED_PROTOCOL) - }) - }) -}) - -describe('libp2p.upgrader', () => { - let peers - let libp2p - const connectionGater = mockConnectionGater() - - before(async () => { - peers = await Promise.all([ - PeerId.createFromJSON(Peers[0]), - PeerId.createFromJSON(Peers[1]) - ]) - }) - - afterEach(async () => { - sinon.restore() - libp2p && await libp2p.stop() - libp2p = null - }) - - it('should create an Upgrader', () => { - const protector = new Protector(swarmKeyBuffer) - libp2p = new Libp2p({ - peerId: peers[0], - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto], - connProtector: protector - } - }) - - expect(libp2p.upgrader).to.exist() - expect(libp2p.upgrader.muxers).to.eql(new Map([[Muxer.multicodec, Muxer]])) - expect(libp2p.upgrader.cryptos).to.eql(new Map([[Crypto.protocol, Crypto]])) - expect(libp2p.upgrader.protector).to.equal(protector) - // Ensure the transport manager also has the upgrader - expect(libp2p.upgrader).to.equal(libp2p.transportManager.upgrader) - }) - - it('should be able to register and unregister a handler', async () => { - libp2p = new Libp2p({ - peerId: peers[0], - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - } - }) - - expect(libp2p.upgrader.protocols).to.not.have.any.keys(['/echo/1.0.0', '/echo/1.0.1']) - - const echoHandler = () => {} - await libp2p.handle(['/echo/1.0.0', '/echo/1.0.1'], echoHandler) - expect(libp2p.upgrader.protocols.get('/echo/1.0.0')).to.equal(echoHandler) - expect(libp2p.upgrader.protocols.get('/echo/1.0.1')).to.equal(echoHandler) - - await libp2p.unhandle(['/echo/1.0.0']) - expect(libp2p.upgrader.protocols.get('/echo/1.0.0')).to.equal(undefined) - expect(libp2p.upgrader.protocols.get('/echo/1.0.1')).to.equal(echoHandler) - }) - - it('should return muxed streams', async () => { - const remotePeer = peers[1] - libp2p = new Libp2p({ - peerId: peers[0], - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - } - }) - await libp2p.start() - const echoHandler = () => {} - libp2p.handle(['/echo/1.0.0'], echoHandler) - - const remoteUpgrader = new Upgrader({ - localPeer: remotePeer, - connectionManager: libp2p.connectionManager, - muxers: new Map([[Muxer.multicodec, Muxer]]), - cryptos: new Map([[Crypto.protocol, Crypto]]), - connectionGater - }) - remoteUpgrader.protocols.set('/echo/1.0.0', echoHandler) - - const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) - const [localConnection] = await Promise.all([ - libp2p.upgrader.upgradeOutbound(outbound), - remoteUpgrader.upgradeInbound(inbound) - ]) - sinon.spy(remoteUpgrader, '_onStream') - - const { stream } = await localConnection.newStream(['/echo/1.0.0']) - expect(stream).to.include.keys(['id', 'close', 'reset', 'timeline']) - - const [arg0] = remoteUpgrader._onStream.getCall(0).args - expect(arg0.stream).to.include.keys(['id', 'close', 'reset', 'timeline']) - }) - - it('should emit connect and disconnect events', async () => { - const remotePeer = peers[1] - libp2p = new Libp2p({ - peerId: peers[0], - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - } - }) - - const remoteUpgrader = new Upgrader({ - localPeer: remotePeer, - connectionManager: libp2p.connectionManager, - muxers: new Map([[Muxer.multicodec, Muxer]]), - cryptos: new Map([[Crypto.protocol, Crypto]]), - connectionGater - }) - - await libp2p.start() - - const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) - - // Spy on emit for easy verification - sinon.spy(libp2p.connectionManager, 'emit') - - // Upgrade and check the connect event - const connections = await Promise.all([ - libp2p.upgrader.upgradeOutbound(outbound), - remoteUpgrader.upgradeInbound(inbound) - ]) - expect(libp2p.connectionManager.emit.callCount).to.equal(1) - - let [event, connection] = libp2p.connectionManager.emit.getCall(0).args - expect(event).to.equal('peer:connect') - expect(connection.remotePeer.equals(remotePeer)).to.equal(true) - - // Close and check the disconnect event - await Promise.all(connections.map(conn => conn.close())) - expect(libp2p.connectionManager.emit.callCount).to.equal(2) - ;([event, connection] = libp2p.connectionManager.emit.getCall(1).args) - expect(event).to.equal('peer:disconnect') - expect(connection.remotePeer.equals(remotePeer)).to.equal(true) - }) -}) diff --git a/test/upgrading/upgrader.spec.ts b/test/upgrading/upgrader.spec.ts new file mode 100644 index 0000000000..6afb6cfc8a --- /dev/null +++ b/test/upgrading/upgrader.spec.ts @@ -0,0 +1,517 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import sinon from 'sinon' +import { Mplex } from '@libp2p/mplex' +import { Multiaddr } from '@multiformats/multiaddr' +import { pipe } from 'it-pipe' +import all from 'it-all' +import pSettle from 'p-settle' +import { WebSockets } from '@libp2p/websockets' +import { NOISE } from '@chainsafe/libp2p-noise' +import { PreSharedKeyConnectionProtector } from '../../src/pnet/index.js' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import swarmKey from '../fixtures/swarm.key.js' +import { DefaultUpgrader } from '../../src/upgrader.js' +import { codes } from '../../src/errors.js' +import { mockConnectionGater, mockMultiaddrConnPair, mockRegistrar } from '@libp2p/interface-compliance-tests/mocks' +import Peers from '../fixtures/peers.js' +import type { Upgrader } from '@libp2p/interfaces/transport' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import { createFromJSON } from '@libp2p/peer-id-factory' +import { Components } from '@libp2p/interfaces/components' +import { Plaintext } from '../../src/insecure/index.js' +import type { ConnectionEncrypter, SecuredConnection } from '@libp2p/interfaces/connection-encrypter' +import type { StreamMuxer, StreamMuxerFactory, StreamMuxerInit } from '@libp2p/interfaces/stream-muxer' +import type { Stream } from '@libp2p/interfaces/connection' +import pDefer from 'p-defer' +import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' + +const addrs = [ + new Multiaddr('/ip4/127.0.0.1/tcp/0'), + new Multiaddr('/ip4/127.0.0.1/tcp/0') +] + +describe('Upgrader', () => { + let localUpgrader: Upgrader + let remoteUpgrader: Upgrader + let localPeer: PeerId + let remotePeer: PeerId + let localComponents: Components + let remoteComponents: Components + + beforeEach(async () => { + ([ + localPeer, + remotePeer + ] = await Promise.all([ + createFromJSON(Peers[0]), + createFromJSON(Peers[1]) + ])) + + localComponents = new Components({ + peerId: localPeer, + connectionGater: mockConnectionGater(), + registrar: mockRegistrar() + }) + localUpgrader = new DefaultUpgrader(localComponents, { + connectionEncryption: [ + new Plaintext() + ], + muxers: [ + new Mplex() + ] + }) + + remoteComponents = new Components({ + peerId: remotePeer, + connectionGater: mockConnectionGater(), + registrar: mockRegistrar() + }) + remoteUpgrader = new DefaultUpgrader(remoteComponents, { + connectionEncryption: [ + new Plaintext() + ], + muxers: [ + new Mplex() + ] + }) + + await localComponents.getRegistrar().handle('/echo/1.0.0', ({ stream }) => { + void pipe(stream, stream) + }) + await remoteComponents.getRegistrar().handle('/echo/1.0.0', ({ stream }) => { + void pipe(stream, stream) + }) + }) + + afterEach(() => { + sinon.restore() + }) + + it('should upgrade with valid muxers and crypto', async () => { + const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) + + const connections = await Promise.all([ + localUpgrader.upgradeOutbound(outbound), + remoteUpgrader.upgradeInbound(inbound) + ]) + + expect(connections).to.have.length(2) + + const { stream, protocol } = await connections[0].newStream('/echo/1.0.0') + expect(protocol).to.equal('/echo/1.0.0') + + const hello = uint8ArrayFromString('hello there!') + const result = await pipe( + [hello], + stream, + function toBuffer (source) { + return (async function * () { + for await (const val of source) yield val.slice() + })() + }, + async (source) => await all(source) + ) + + expect(result).to.eql([hello]) + }) + + it('should upgrade with only crypto', async () => { + const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) + + // No available muxers + localUpgrader = new DefaultUpgrader(localComponents, { + connectionEncryption: [ + new Plaintext() + ], + muxers: [] + }) + remoteUpgrader = new DefaultUpgrader(remoteComponents, { + connectionEncryption: [ + new Plaintext() + ], + muxers: [] + }) + + const connections = await Promise.all([ + localUpgrader.upgradeOutbound(outbound), + remoteUpgrader.upgradeInbound(inbound) + ]) + + expect(connections).to.have.length(2) + + await expect(connections[0].newStream('/echo/1.0.0')).to.be.rejected() + + // Verify the MultiaddrConnection close method is called + const inboundCloseSpy = sinon.spy(inbound, 'close') + const outboundCloseSpy = sinon.spy(outbound, 'close') + await Promise.all(connections.map(async conn => await conn.close())) + expect(inboundCloseSpy.callCount).to.equal(1) + expect(outboundCloseSpy.callCount).to.equal(1) + }) + + it('should use a private connection protector when provided', async () => { + const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) + + const protector = new PreSharedKeyConnectionProtector({ + psk: uint8ArrayFromString(swarmKey) + }) + const protectorProtectSpy = sinon.spy(protector, 'protect') + + localComponents.setConnectionProtector(protector) + remoteComponents.setConnectionProtector(protector) + + const connections = await Promise.all([ + localUpgrader.upgradeOutbound(outbound), + remoteUpgrader.upgradeInbound(inbound) + ]) + + expect(connections).to.have.length(2) + + const { stream, protocol } = await connections[0].newStream('/echo/1.0.0') + expect(protocol).to.equal('/echo/1.0.0') + + const hello = uint8ArrayFromString('hello there!') + const result = await pipe( + [hello], + stream, + function toBuffer (source) { + return (async function * () { + for await (const val of source) yield val.slice() + })() + }, + async (source) => await all(source) + ) + + expect(result).to.eql([hello]) + expect(protectorProtectSpy.callCount).to.eql(2) + }) + + it('should fail if crypto fails', async () => { + const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) + + class BoomCrypto implements ConnectionEncrypter { + static protocol = '/insecure' + public protocol = '/insecure' + async secureInbound (): Promise { throw new Error('Boom') } + async secureOutbound (): Promise { throw new Error('Boom') } + } + + localUpgrader = new DefaultUpgrader(localComponents, { + connectionEncryption: [ + new BoomCrypto() + ], + muxers: [] + }) + remoteUpgrader = new DefaultUpgrader(remoteComponents, { + connectionEncryption: [ + new BoomCrypto() + ], + muxers: [] + }) + + // Wait for the results of each side of the connection + const results = await pSettle([ + localUpgrader.upgradeOutbound(outbound), + remoteUpgrader.upgradeInbound(inbound) + ]) + + // Ensure both sides fail + expect(results).to.have.length(2) + results.forEach(result => { + expect(result).to.have.property('isRejected', true) + expect(result).to.have.nested.property('reason.code', codes.ERR_ENCRYPTION_FAILED) + }) + }) + + it('should fail if muxers do not match', async () => { + const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) + + class OtherMuxer implements StreamMuxer { + protocol = '/muxer-local' + streams = [] + newStream (name?: string): Stream { + throw new Error('Not implemented') + } + + source = [] + async sink () {} + } + + class OtherMuxerFactory implements StreamMuxerFactory { + protocol = '/muxer-local' + + createStreamMuxer (components: Components, init?: StreamMuxerInit): StreamMuxer { + return new OtherMuxer() + } + } + + localUpgrader = new DefaultUpgrader(localComponents, { + connectionEncryption: [ + new Plaintext() + ], + muxers: [ + new OtherMuxerFactory() + ] + }) + remoteUpgrader = new DefaultUpgrader(remoteComponents, { + connectionEncryption: [ + new Plaintext() + ], + muxers: [ + new Mplex() + ] + }) + + // Wait for the results of each side of the connection + const results = await pSettle([ + localUpgrader.upgradeOutbound(outbound), + remoteUpgrader.upgradeInbound(inbound) + ]) + + // Ensure both sides fail + expect(results).to.have.length(2) + results.forEach(result => { + expect(result).to.have.property('isRejected', true) + expect(result).to.have.nested.property('reason.code', codes.ERR_MUXER_UNAVAILABLE) + }) + }) + + it('should map getStreams and close methods', async () => { + const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) + + const connections = await Promise.all([ + localUpgrader.upgradeOutbound(outbound), + remoteUpgrader.upgradeInbound(inbound) + ]) + + expect(connections).to.have.length(2) + + // Create a few streams, at least 1 in each direction + await connections[0].newStream('/echo/1.0.0') + await connections[1].newStream('/echo/1.0.0') + await connections[0].newStream('/echo/1.0.0') + connections.forEach(conn => { + expect(conn.streams).to.have.length(3) + }) + + // Verify the MultiaddrConnection close method is called + const inboundCloseSpy = sinon.spy(inbound, 'close') + const outboundCloseSpy = sinon.spy(outbound, 'close') + await Promise.all(connections.map(async conn => await conn.close())) + expect(inboundCloseSpy.callCount).to.equal(1) + expect(outboundCloseSpy.callCount).to.equal(1) + }) + + it('should call connection handlers', async () => { + const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) + const localConnectionEventReceived = pDefer() + const localConnectionEndEventReceived = pDefer() + const remoteConnectionEventReceived = pDefer() + const remoteConnectionEndEventReceived = pDefer() + + localUpgrader.addEventListener('connection', () => { + localConnectionEventReceived.resolve() + }) + localUpgrader.addEventListener('connectionEnd', () => { + localConnectionEndEventReceived.resolve() + }) + remoteUpgrader.addEventListener('connection', () => { + remoteConnectionEventReceived.resolve() + }) + remoteUpgrader.addEventListener('connectionEnd', () => { + remoteConnectionEndEventReceived.resolve() + }) + + // Verify onConnection is called with the connection + const connections = await Promise.all([ + localUpgrader.upgradeOutbound(outbound), + remoteUpgrader.upgradeInbound(inbound) + ]) + expect(connections).to.have.length(2) + + await Promise.all([ + localConnectionEventReceived.promise, + remoteConnectionEventReceived.promise + ]) + + // Verify onConnectionEnd is called with the connection + await Promise.all(connections.map(async conn => await conn.close())) + + await Promise.all([ + localConnectionEndEventReceived.promise, + remoteConnectionEndEventReceived.promise + ]) + }) + + it('should fail to create a stream for an unsupported protocol', async () => { + const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) + + const connections = await Promise.all([ + localUpgrader.upgradeOutbound(outbound), + remoteUpgrader.upgradeInbound(inbound) + ]) + + expect(connections).to.have.length(2) + + const results = await pSettle([ + connections[0].newStream('/unsupported/1.0.0'), + connections[1].newStream('/unsupported/1.0.0') + ]) + expect(results).to.have.length(2) + results.forEach(result => { + expect(result).to.have.property('isRejected', true) + expect(result).to.have.nested.property('reason.code', codes.ERR_UNSUPPORTED_PROTOCOL) + }) + }) +}) + +describe('libp2p.upgrader', () => { + let peers: PeerId[] + let libp2p: Libp2pNode + let remoteLibp2p: Libp2pNode + + before(async () => { + peers = await Promise.all([ + createFromJSON(Peers[0]), + createFromJSON(Peers[1]) + ]) + }) + + afterEach(async () => { + sinon.restore() + + if (libp2p != null) { + await libp2p.stop() + } + + if (remoteLibp2p != null) { + await remoteLibp2p.stop() + } + }) + + it('should create an Upgrader', async () => { + libp2p = await createLibp2pNode({ + peerId: peers[0], + transports: [ + new WebSockets() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ], + connectionProtector: new PreSharedKeyConnectionProtector({ + psk: uint8ArrayFromString(swarmKey) + }) + }) + + expect(libp2p.components.getUpgrader()).to.exist() + expect(libp2p.components.getConnectionProtector()).to.exist() + }) + + it('should return muxed streams', async () => { + const remotePeer = peers[1] + libp2p = await createLibp2pNode({ + peerId: peers[0], + transports: [ + new WebSockets() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + await libp2p.start() + const echoHandler = () => {} + await libp2p.handle(['/echo/1.0.0'], echoHandler) + + remoteLibp2p = await createLibp2pNode({ + peerId: remotePeer, + transports: [ + new WebSockets() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + await remoteLibp2p.start() + await remoteLibp2p.handle('/echo/1.0.0', echoHandler) + + const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) + const [localConnection] = await Promise.all([ + libp2p.components.getUpgrader().upgradeOutbound(outbound), + remoteLibp2p.components.getUpgrader().upgradeInbound(inbound) + ]) + const remoteLibp2pUpgraderOnStreamSpy = sinon.spy(remoteLibp2p.components.getUpgrader() as DefaultUpgrader, '_onStream') + + const { stream } = await localConnection.newStream(['/echo/1.0.0']) + expect(stream).to.include.keys(['id', 'close', 'reset', 'timeline']) + + const [arg0] = remoteLibp2pUpgraderOnStreamSpy.getCall(0).args + expect(arg0.stream).to.include.keys(['id', 'close', 'reset', 'timeline']) + }) + + it('should emit connect and disconnect events', async () => { + const remotePeer = peers[1] + libp2p = await createLibp2pNode({ + peerId: peers[0], + transports: [ + new WebSockets() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + await libp2p.start() + + remoteLibp2p = await createLibp2pNode({ + peerId: remotePeer, + transports: [ + new WebSockets() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + await remoteLibp2p.start() + + const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) + + // Spy on emit for easy verification + const connectionManagerDispatchEventSpy = sinon.spy(libp2p.components.getConnectionManager(), 'dispatchEvent') + + // Upgrade and check the connect event + const connections = await Promise.all([ + libp2p.components.getUpgrader().upgradeOutbound(outbound), + remoteLibp2p.components.getUpgrader().upgradeInbound(inbound) + ]) + expect(connectionManagerDispatchEventSpy.callCount).to.equal(1) + + let [event] = connectionManagerDispatchEventSpy.getCall(0).args + expect(event).to.have.property('type', 'peer:connect') + // @ts-expect-error detail is only on CustomEvent type + expect(remotePeer.equals(event.detail.remotePeer)).to.equal(true) + + // Close and check the disconnect event + await Promise.all(connections.map(async conn => await conn.close())) + expect(connectionManagerDispatchEventSpy.callCount).to.equal(2) + ;([event] = connectionManagerDispatchEventSpy.getCall(1).args) + expect(event).to.have.property('type', 'peer:disconnect') + // @ts-expect-error detail is only on CustomEvent type + expect(remotePeer.equals(event.detail.remotePeer)).to.equal(true) + }) +}) diff --git a/test/utils/base-options.browser.js b/test/utils/base-options.browser.js deleted file mode 100644 index 1d8b5941d3..0000000000 --- a/test/utils/base-options.browser.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict' - -const Transport = require('libp2p-websockets') -const filters = require('libp2p-websockets/src/filters') -const Muxer = require('libp2p-mplex') -const { NOISE: Crypto } = require('@chainsafe/libp2p-noise') - -const transportKey = Transport.prototype[Symbol.toStringTag] - -module.exports = { - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - }, - config: { - relay: { - enabled: true, - hop: { - enabled: false - } - }, - transport: { - [transportKey]: { - filter: filters.all - } - } - } -} diff --git a/test/utils/base-options.browser.ts b/test/utils/base-options.browser.ts new file mode 100644 index 0000000000..0fb4bc9aac --- /dev/null +++ b/test/utils/base-options.browser.ts @@ -0,0 +1,31 @@ + +import { WebSockets } from '@libp2p/websockets' +import * as filters from '@libp2p/websockets/filters' +import { Mplex } from '@libp2p/mplex' +import { Plaintext } from '../../src/insecure/index.js' +import type { Libp2pOptions } from '../../src' +import mergeOptions from 'merge-options' + +export function createBaseOptions (overrides?: Libp2pOptions): Libp2pOptions { + const options: Libp2pOptions = { + transports: [ + new WebSockets({ + filter: filters.all + }) + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + new Plaintext() + ], + relay: { + enabled: false, + hop: { + enabled: false + } + } + } + + return mergeOptions(options, overrides) +} diff --git a/test/utils/base-options.js b/test/utils/base-options.js deleted file mode 100644 index e403ff0fc5..0000000000 --- a/test/utils/base-options.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict' - -const Transport = require('libp2p-tcp') -const Muxer = require('libp2p-mplex') -const { NOISE: Crypto } = require('@chainsafe/libp2p-noise') - -module.exports = { - modules: { - transport: [Transport], - streamMuxer: [Muxer], - connEncryption: [Crypto] - }, - config: { - relay: { - enabled: true, - hop: { - enabled: false - } - }, - nat: { - enabled: false - } - } -} diff --git a/test/utils/base-options.ts b/test/utils/base-options.ts new file mode 100644 index 0000000000..610bdb2240 --- /dev/null +++ b/test/utils/base-options.ts @@ -0,0 +1,30 @@ +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Plaintext } from '../../src/insecure/index.js' +import type { Libp2pOptions } from '../../src' +import mergeOptions from 'merge-options' + +export function createBaseOptions (...overrides: Libp2pOptions[]): Libp2pOptions { + const options: Libp2pOptions = { + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + new Plaintext() + ], + relay: { + enabled: true, + hop: { + enabled: false + } + }, + nat: { + enabled: false + } + } + + return mergeOptions(options, ...overrides) +} diff --git a/test/utils/creators/peer.js b/test/utils/creators/peer.js deleted file mode 100644 index 63b943f76b..0000000000 --- a/test/utils/creators/peer.js +++ /dev/null @@ -1,72 +0,0 @@ -'use strict' - -const pTimes = require('p-times') - -const { Multiaddr } = require('multiaddr') -const PeerId = require('peer-id') - -const Libp2p = require('../../../src') -const Peers = require('../../fixtures/peers') -const defaultOptions = require('../base-options.browser') - -const listenAddr = new 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) - * @returns {Promise>} - */ -async function createPeer ({ number = 1, fixture = true, started = true, populateAddressBooks = true, config = {} } = {}) { - const peerIds = await createPeerId({ number, fixture }) - - const addresses = started ? { listen: [listenAddr] } : {} - const peers = await pTimes(number, (i) => Libp2p.create({ - peerId: peerIds[i], - addresses, - ...defaultOptions, - ...config - })) - - if (started) { - await Promise.all(peers.map((p) => p.start())) - - populateAddressBooks && await _populateAddressBooks(peers) - } - - return peers -} - -async function _populateAddressBooks (peers) { - for (let i = 0; i < peers.length; i++) { - for (let j = 0; j < peers.length; j++) { - if (i !== j) { - await peers[i].peerStore.addressBook.set(peers[j].peerId, peers[j].multiaddrs) - } - } - } -} - -/** - * 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) - * @param {PeerId.CreateOptions} [properties.opts] - * @returns {Promise>} - */ -function createPeerId ({ number = 1, fixture = true, opts = {} } = {}) { - return pTimes(number, (i) => fixture - ? PeerId.createFromJSON(Peers[i]) - : PeerId.create(opts) - ) -} - -module.exports.createPeer = createPeer -module.exports.createPeerId = createPeerId diff --git a/test/utils/creators/peer.ts b/test/utils/creators/peer.ts new file mode 100644 index 0000000000..e010c0e694 --- /dev/null +++ b/test/utils/creators/peer.ts @@ -0,0 +1,104 @@ +import { Multiaddr } from '@multiformats/multiaddr' +import Peers from '../../fixtures/peers.js' +import { createBaseOptions } from '../base-options.browser.js' +import { createEd25519PeerId, createFromJSON, createRSAPeerId } from '@libp2p/peer-id-factory' +import { createLibp2pNode, Libp2pNode } from '../../../src/libp2p.js' +import type { AddressesConfig, Libp2pOptions } from '../../../src/index.js' +import type { PeerId } from '@libp2p/interfaces/peer-id' + +const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') + +export interface CreatePeerOptions { + /** + * number of peers (default: 1) + */ + number?: number + + /** + * fixture index for peer-id generation + */ + fixture?: number + + /** + * nodes should start (default: true) + */ + started?: boolean + + config?: Libp2pOptions +} + +/** + * Create libp2p nodes. + */ +export async function createNode (options: CreatePeerOptions = {}): Promise { + const started = options.started ?? true + const config = options.config ?? {} + const peerId = await createPeerId({ fixture: options.fixture }) + const addresses: AddressesConfig = started + ? { + listen: [listenAddr.toString()], + announce: [], + noAnnounce: [], + announceFilter: (addrs) => addrs + } + : { + listen: [], + announce: [], + noAnnounce: [], + announceFilter: (addrs) => addrs + } + const peer = await createLibp2pNode(createBaseOptions({ + peerId, + addresses, + ...config + })) + + if (started) { + await peer.start() + } + + return peer +} + +export async function populateAddressBooks (peers: Libp2pNode[]) { + for (let i = 0; i < peers.length; i++) { + for (let j = 0; j < peers.length; j++) { + if (i !== j) { + await peers[i].components.getPeerStore().addressBook.set(peers[j].peerId, peers[j].components.getAddressManager().getAddresses()) + } + } + } +} + +export interface CreatePeerIdOptions { + /** + * number of peers (default: 1) + */ + number?: number + + /** + * fixture index for peer-id generation (default: 0) + */ + fixture?: number + + /** + * Options to pass to the PeerId constructor + */ + opts?: { + type?: 'rsa' | 'ed25519' + bits?: number + } +} + +/** + * Create Peer-ids + */ +export async function createPeerId (options: CreatePeerIdOptions = {}): Promise { + const opts = options.opts ?? {} + + if (options.fixture == null) { + return opts.type === 'rsa' ? await createRSAPeerId({ bits: opts.bits ?? 512 }) : await createEd25519PeerId() + } + + return await createFromJSON(Peers[options.fixture]) +} diff --git a/test/utils/mock-connection-gater.js b/test/utils/mock-connection-gater.js deleted file mode 100644 index b1081f4958..0000000000 --- a/test/utils/mock-connection-gater.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict' - -function mockConnectionGater () { - return { - denyDialPeer: async () => Promise.resolve(false), - denyDialMultiaddr: async () => Promise.resolve(false), - denyInboundConnection: async () => Promise.resolve(false), - denyOutboundConnection: async () => Promise.resolve(false), - denyInboundEncryptedConnection: async () => Promise.resolve(false), - denyOutboundEncryptedConnection: async () => Promise.resolve(false), - denyInboundUpgradedConnection: async () => Promise.resolve(false), - denyOutboundUpgradedConnection: async () => Promise.resolve(false), - filterMultiaddrForPeer: async () => Promise.resolve(true) - } -} - -module.exports = { - mockConnectionGater -} diff --git a/test/utils/mockConnection.js b/test/utils/mockConnection.js deleted file mode 100644 index 55c8d60646..0000000000 --- a/test/utils/mockConnection.js +++ /dev/null @@ -1,155 +0,0 @@ -'use strict' - -const pipe = require('it-pipe') -const { Connection } = require('libp2p-interfaces/src/connection') -const { Multiaddr } = require('multiaddr') -const Muxer = require('libp2p-mplex') -const Multistream = require('multistream-select') -const pair = require('it-pair') -const errCode = require('err-code') -const { codes } = require('../../src/errors') - -const mockMultiaddrConnPair = require('./mockMultiaddrConn') -const peerUtils = require('./creators/peer') - -module.exports = async (properties = {}) => { - const localAddr = new Multiaddr('/ip4/127.0.0.1/tcp/8080') - const remoteAddr = new Multiaddr('/ip4/127.0.0.1/tcp/8081') - - const [localPeer, remotePeer] = await peerUtils.createPeerId({ number: 2 }) - const openStreams = [] - let streamId = 0 - - return new Connection({ - localPeer: localPeer, - remotePeer: remotePeer, - localAddr, - remoteAddr, - stat: { - timeline: { - open: Date.now() - 10, - upgraded: Date.now() - }, - direction: 'outbound', - encryption: '/noise', - multiplexer: '/mplex/6.7.0' - }, - newStream: (protocols) => { - const id = streamId++ - const stream = pair() - - stream.close = () => stream.sink([]) - stream.id = id - - openStreams.push(stream) - - return { - stream, - protocol: protocols[0] - } - }, - close: async () => { }, - getStreams: () => openStreams, - ...properties - }) -} - -/** - * Creates a full connection pair, without the transport or encryption - * - * @param {object} options - * @param {Multiaddr[]} options.addrs - Should contain two addresses for the local and remote peer respectively - * @param {Array} options.peers - Array containing local and remote peer ids - * @param {Map} options.protocols - The protocols the connections should support - * @returns {{inbound:Connection, outbound:Connection}} - */ -module.exports.pair = function connectionPair ({ addrs, peers, protocols }) { - const [localPeer, remotePeer] = peers - - const { - inbound: inboundMaConn, - outbound: outboundMaConn - } = mockMultiaddrConnPair({ addrs, remotePeer }) - - const inbound = createConnection({ - direction: 'inbound', - maConn: inboundMaConn, - protocols, - // Inbound connection peers are reversed - localPeer: remotePeer, - remotePeer: localPeer - }) - const outbound = createConnection({ - direction: 'outbound', - maConn: outboundMaConn, - protocols, - localPeer, - remotePeer - }) - - return { inbound, outbound } -} - -function createConnection ({ - direction, - maConn, - localPeer, - remotePeer, - protocols -}) { - // Create the muxer - const muxer = new Muxer({ - // Run anytime a remote stream is created - onStream: async muxedStream => { - const mss = new Multistream.Listener(muxedStream) - try { - const { stream, protocol } = await mss.handle(Array.from(protocols.keys())) - connection.addStream(stream, protocol) - // Need to be able to notify a peer of this this._onStream({ connection, stream, protocol }) - const handler = protocols.get(protocol) - handler({ connection, stream, protocol }) - } catch (/** @type {any} */ err) { - // Do nothing - } - }, - // Run anytime a stream closes - onStreamEnd: muxedStream => { - connection.removeStream(muxedStream.id) - } - }) - - const newStream = async protocols => { - const muxedStream = muxer.newStream() - const mss = new Multistream.Dialer(muxedStream) - try { - const { stream, protocol } = await mss.select(protocols) - return { stream: { ...muxedStream, ...stream }, protocol } - } catch (/** @type {any} */ err) { - throw errCode(err, codes.ERR_UNSUPPORTED_PROTOCOL) - } - } - - // Pipe all data through the muxer - pipe(maConn, muxer, maConn) - - maConn.timeline.upgraded = Date.now() - - // Create the connection - const connection = new Connection({ - localAddr: maConn.localAddr, - remoteAddr: maConn.remoteAddr, - localPeer: localPeer, - remotePeer: remotePeer, - stat: { - direction, - timeline: maConn.timeline, - multiplexer: Muxer.multicodec, - encryption: 'N/A' - }, - newStream, - getStreams: () => muxer.streams, - close: err => maConn.close(err) - }) - - return connection -} diff --git a/test/utils/mockCrypto.js b/test/utils/mockCrypto.js deleted file mode 100644 index 6a5055c6ea..0000000000 --- a/test/utils/mockCrypto.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict' - -const PeerId = require('peer-id') -const Peers = require('../fixtures/peers') - -module.exports = { - protocol: '/insecure', - secureInbound: (localPeer, stream) => { - return { - conn: stream, - remotePeer: localPeer - } - }, - secureOutbound: async (localPeer, stream, remotePeer) => { - // Crypto should always return a remotePeer - if (!remotePeer) { - remotePeer = await PeerId.createFromJSON(Peers[0]) - } - return { - conn: stream, - remotePeer: remotePeer - } - } -} diff --git a/test/utils/mockMultiaddrConn.js b/test/utils/mockMultiaddrConn.js deleted file mode 100644 index 26482dd349..0000000000 --- a/test/utils/mockMultiaddrConn.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict' - -const duplexPair = require('it-pair/duplex') -const abortable = require('abortable-iterator') - -/** - * Returns both sides of a mocked MultiaddrConnection - * - * @param {object} options - * @param {Multiaddr[]} options.addrs - Should contain two addresses for the local and remote peer - * @param {PeerId} options.remotePeer - The peer that is being "dialed" - * @returns {{inbound:MultiaddrConnection, outbound:MultiaddrConnection}} - */ -module.exports = function mockMultiaddrConnPair ({ addrs, remotePeer }) { - const controller = new AbortController() - const [localAddr, remoteAddr] = addrs - - const [inbound, outbound] = duplexPair() - outbound.localAddr = localAddr - outbound.remoteAddr = remoteAddr.encapsulate(`/p2p/${remotePeer.toB58String()}`) - outbound.timeline = { - open: Date.now() - } - outbound.close = () => { - outbound.timeline.close = Date.now() - controller.abort() - } - - inbound.localAddr = remoteAddr - inbound.remoteAddr = localAddr - inbound.timeline = { - open: Date.now() - } - inbound.close = () => { - inbound.timeline.close = Date.now() - controller.abort() - } - - // Make the sources abortable so we can close them easily - inbound.source = abortable(inbound.source, controller.signal) - outbound.source = abortable(outbound.source, controller.signal) - - return { inbound, outbound } -} diff --git a/test/utils/mockUpgrader.js b/test/utils/mockUpgrader.js deleted file mode 100644 index 5e72e2de2a..0000000000 --- a/test/utils/mockUpgrader.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict' - -module.exports = { - upgradeInbound: (maConn) => maConn, - upgradeOutbound: (maConn) => maConn -} diff --git a/tsconfig.json b/tsconfig.json index eafbf8ca8a..634994043c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,18 +1,19 @@ { "extends": "aegir/src/config/tsconfig.aegir.json", "compilerOptions": { - "outDir": "dist" + "outDir": "dist", + "emitDeclarationOnly": false, + "module": "ES2020", + "lib": ["ES2021", "ES2021.Promise", "ES2021.String", "ES2020.BigInt", "DOM", "DOM.Iterable"] }, "include": [ - "src" + "src", + "test" ], "exclude": [ - "src/circuit/protocol/index.js", // exclude generated file - "src/fetch/proto.js", // exclude generated file - "src/identify/message.js", // exclude generated file - "src/insecure/proto.js", // exclude generated file - "src/peer-store/pb/peer.js", // exclude generated file - "src/record/peer-record/peer-record.js", // exclude generated file - "src/record/envelope/envelope.js" // exclude generated file + "src/circuit/pb/index.js", + "src/fetch/pb/proto.js", + "src/identify/pb/message.js", + "src/insecure/pb/proto.js" ] -} \ No newline at end of file +} From 8cca8e4bfc6a339e58b5a5efa8a84fd891aa08ee Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Mon, 28 Mar 2022 16:09:43 +0100 Subject: [PATCH 349/447] fix: update deps (#1181) Update to released versions of interop suite and webrtc-direct --- examples/webrtc-direct/package.json | 2 +- package.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index 950112b1a7..03a33b629a 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -12,7 +12,7 @@ }, "license": "ISC", "dependencies": { - "@achingbrain/webrtc-direct": "^0.7.2", + "@libp2p/webrtc-direct": "^1.0.0", "@chainsafe/libp2p-noise": "^6.0.1", "@libp2p/bootstrap": "^1.0.1", "@libp2p/mplex": "^1.0.2", diff --git a/package.json b/package.json index 166aa40297..1a22070635 100644 --- a/package.json +++ b/package.json @@ -156,13 +156,13 @@ "@achingbrain/libp2p-gossipsub": "^0.13.5", "@chainsafe/libp2p-noise": "^6.0.1", "@libp2p/bootstrap": "^1.0.2", - "@libp2p/daemon-client": "^0.0.2", - "@libp2p/daemon-server": "^0.0.2", + "@libp2p/daemon-client": "^1.0.0", + "@libp2p/daemon-server": "^1.0.0", "@libp2p/delegated-content-routing": "^1.0.2", "@libp2p/delegated-peer-routing": "^1.0.2", "@libp2p/floodsub": "^1.0.2", "@libp2p/interface-compliance-tests": "^1.1.20", - "@libp2p/interop": "^0.0.3", + "@libp2p/interop": "^1.0.0", "@libp2p/kad-dht": "^1.0.3", "@libp2p/mdns": "^1.0.3", "@libp2p/mplex": "^1.0.1", From cc60cfde1a0907ca68f658f6de5362a708189222 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 29 Mar 2022 15:39:50 +0100 Subject: [PATCH 350/447] fix: add transport manager to exports map and fix docs (#1182) Addresses PR comments from #1172 - fixes syntax of examples in docs, adds the transport manager to the exports map and renames fault tolerance enum for consistency. --- doc/CONFIGURATION.md | 7 +++---- doc/STREAMING_ITERABLES.md | 2 +- doc/migrations/v0.26-v0.27.md | 8 ++++---- package.json | 3 +++ src/config.ts | 4 ++-- src/index.ts | 4 ++-- src/transport-manager.ts | 12 ++++++------ test/nat-manager/nat-manager.node.ts | 4 ++-- test/transports/transport-manager.spec.ts | 6 +++--- 9 files changed, 26 insertions(+), 24 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 41cd0e7aba..df5e689aa6 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -246,8 +246,8 @@ import { GossipSub } from 'libp2p-gossipsub' const node = await createLibp2p({ transports: [ - TCP, - new WS() // It can take instances too! + new TCP(), + new WS() ], streamMuxers: [new Mplex()], connectionEncryption: [new Noise()], @@ -697,8 +697,7 @@ import { createLibp2p } from 'libp2p' import { TCP } from '@libp2p/tcp' import { Mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' - -const { FaultTolerance } from 'libp2p/src/transport-manager') +import { FaultTolerance } from 'libp2p/transport-manager' const node = await createLibp2p({ transports: [new TCP()], diff --git a/doc/STREAMING_ITERABLES.md b/doc/STREAMING_ITERABLES.md index fe6a349ded..2a374c3b68 100644 --- a/doc/STREAMING_ITERABLES.md +++ b/doc/STREAMING_ITERABLES.md @@ -22,7 +22,7 @@ Sometimes you may need to wrap an existing duplex stream in order to perform incoming and outgoing [transforms](#transform) on data. This type of wrapping is commonly used in stream encryption/decryption. Using [it-pair][it-pair] and [it-pipe][it-pipe], we can do this rather easily, given an existing [duplex iterable](#duplex). ```js -const duplexPair from 'it-pair/duplex') +import { duplexPair } from 'it-pair/duplex' import { pipe } from 'it-pipe' // Wrapper is what we will write and read from diff --git a/doc/migrations/v0.26-v0.27.md b/doc/migrations/v0.26-v0.27.md index 67dd44c639..db37e6eaf2 100644 --- a/doc/migrations/v0.26-v0.27.md +++ b/doc/migrations/v0.26-v0.27.md @@ -49,13 +49,13 @@ Protocol registration is very similar to how it previously was, however, the han **Before** ```js -const pull from 'pull-stream') +const pull = require('pull-stream') libp2p.handle('/echo/1.0.0', (protocol, conn) => pull(conn, conn)) ``` **After** ```js -const pipe from 'it-pipe') +const pipe = require('it-pipe') libp2p.handle(['/echo/1.0.0'], ({ protocol, stream }) => pipe(stream, stream)) ``` @@ -65,7 +65,7 @@ libp2p.handle(['/echo/1.0.0'], ({ protocol, stream }) => pipe(stream, stream)) **Before** ```js -const pull from 'pull-stream') +const pull = require('pull-stream') libp2p.dialProtocol(peerInfo, '/echo/1.0.0', (err, conn) => { if (err) { throw err } pull( @@ -82,7 +82,7 @@ libp2p.dialProtocol(peerInfo, '/echo/1.0.0', (err, conn) => { **After** ```js -const pipe from 'it-pipe') +const pipe = require('it-pipe') const { protocol, stream } = await libp2p.dialProtocol(peerInfo, '/echo/1.0.0') await pipe( ['hey'], diff --git a/package.json b/package.json index 1a22070635..e1d745754c 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,9 @@ }, "./pnet/generate": { "import": "./dist/src/pnet/key-generator.js" + }, + "./transport-manager": { + "import": "./dist/src/transport-manager.js" } }, "eslintConfig": { diff --git a/src/config.ts b/src/config.ts index 2720c8a9bd..7f77235cc2 100644 --- a/src/config.ts +++ b/src/config.ts @@ -4,7 +4,7 @@ import * as Constants from './constants.js' import { AGENT_VERSION } from './identify/consts.js' import * as RelayConstants from './circuit/constants.js' import { publicAddressesFirst } from '@libp2p/utils/address-sort' -import { FAULT_TOLERANCE } from './transport-manager.js' +import { FaultTolerance } from './transport-manager.js' import type { Multiaddr } from '@multiformats/multiaddr' import type { Libp2pInit } from './index.js' import { codes, messages } from './errors.js' @@ -26,7 +26,7 @@ const DefaultConfig: Partial = { }, connectionGater: {}, transportManager: { - faultTolerance: FAULT_TOLERANCE.FATAL_ALL + faultTolerance: FaultTolerance.FATAL_ALL }, dialer: { maxParallelDials: Constants.MAX_PARALLEL_DIALS, diff --git a/src/index.ts b/src/index.ts index 0f063df94a..1cec15214b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import { createLibp2pNode } from './libp2p.js' import type { AbortOptions, EventEmitter, RecursivePartial, Startable } from '@libp2p/interfaces' import type { Multiaddr } from '@multiformats/multiaddr' -import type { FAULT_TOLERANCE } from './transport-manager.js' +import type { FaultTolerance } from './transport-manager.js' import type { HostProperties } from './identify/index.js' import type { DualDHT } from '@libp2p/interfaces/dht' import type { Datastore } from 'interface-datastore' @@ -95,7 +95,7 @@ export interface ConnectionManagerConfig { } export interface TransportManagerConfig { - faultTolerance?: FAULT_TOLERANCE + faultTolerance?: FaultTolerance } export interface PeerStoreConfig { diff --git a/src/transport-manager.ts b/src/transport-manager.ts index d6e4364217..2eab5cb733 100644 --- a/src/transport-manager.ts +++ b/src/transport-manager.ts @@ -12,14 +12,14 @@ import { trackedMap } from '@libp2p/tracked-map' const log = logger('libp2p:transports') export interface TransportManagerInit { - faultTolerance?: FAULT_TOLERANCE + faultTolerance?: FaultTolerance } export class DefaultTransportManager extends EventEmitter implements TransportManager, Startable { private readonly components: Components private readonly transports: Map private readonly listeners: Map - private readonly faultTolerance: FAULT_TOLERANCE + private readonly faultTolerance: FaultTolerance private started: boolean constructor (components: Components, init: TransportManagerInit = {}) { @@ -33,7 +33,7 @@ export class DefaultTransportManager extends EventEmitter r.isFulfilled) - if ((isListening == null) && this.faultTolerance !== FAULT_TOLERANCE.NO_FATAL) { + if ((isListening == null) && this.faultTolerance !== FaultTolerance.NO_FATAL) { throw errCode(new Error(`Transport (${key}) could not listen on any available address`), codes.ERR_NO_VALID_ADDRESSES) } } @@ -224,7 +224,7 @@ export class DefaultTransportManager extends EventEmitter { }) components.setAddressManager(new DefaultAddressManager(components, { listen: addrs })) components.setTransportManager(new DefaultTransportManager(components, { - faultTolerance: FAULT_TOLERANCE.NO_FATAL + faultTolerance: FaultTolerance.NO_FATAL })) const natManager = new NatManager(components, { diff --git a/test/transports/transport-manager.spec.ts b/test/transports/transport-manager.spec.ts index 93721fc5c7..0edb29d3b6 100644 --- a/test/transports/transport-manager.spec.ts +++ b/test/transports/transport-manager.spec.ts @@ -7,7 +7,7 @@ import { WebSockets } from '@libp2p/websockets' import * as filters from '@libp2p/websockets/filters' import { NOISE } from '@chainsafe/libp2p-noise' import { DefaultAddressManager } from '../../src/address-manager/index.js' -import { DefaultTransportManager, FAULT_TOLERANCE } from '../../src/transport-manager.js' +import { DefaultTransportManager, FaultTolerance } from '../../src/transport-manager.js' import { mockUpgrader } from '@libp2p/interface-compliance-tests/mocks' import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' import { codes as ErrorCodes } from '../../src/errors.js' @@ -123,7 +123,7 @@ describe('libp2p.transportManager (dial only)', () => { listen: ['/ip4/127.0.0.1/tcp/0'] }, transportManager: { - faultTolerance: FAULT_TOLERANCE.NO_FATAL + faultTolerance: FaultTolerance.NO_FATAL }, transports: [ new WebSockets() @@ -143,7 +143,7 @@ describe('libp2p.transportManager (dial only)', () => { listen: ['/ip4/127.0.0.1/tcp/12345/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3/p2p-circuit'] }, transportManager: { - faultTolerance: FAULT_TOLERANCE.NO_FATAL + faultTolerance: FaultTolerance.NO_FATAL }, transports: [ new WebSockets() From 64bfcee5093b368df0b381f78afc2ddff3d339a9 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Sat, 2 Apr 2022 10:11:01 +0100 Subject: [PATCH 351/447] fix: expose metrics and registrar, use dht for peer discovery (#1183) Exposes fields used by bitswap and also uses dht for peer discovery which was missed. --- src/index.ts | 6 ++++-- src/libp2p.ts | 14 +++++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/index.ts b/src/index.ts index 1cec15214b..d64bb22480 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,8 +16,8 @@ import type { ConnectionEncrypter } from '@libp2p/interfaces/connection-encrypte import type { PeerRouting } from '@libp2p/interfaces/peer-routing' import type { ContentRouting } from '@libp2p/interfaces/content-routing' import type { PubSub } from '@libp2p/interfaces/pubsub' -import type { ConnectionManager, StreamHandler } from '@libp2p/interfaces/registrar' -import type { MetricsInit } from '@libp2p/interfaces/metrics' +import type { ConnectionManager, Registrar, StreamHandler } from '@libp2p/interfaces/registrar' +import type { Metrics, MetricsInit } from '@libp2p/interfaces/metrics' import type { PeerInfo } from '@libp2p/interfaces/peer-info' import type { DialerInit } from '@libp2p/interfaces/dialer' import type { KeyChain } from './keychain/index.js' @@ -152,6 +152,8 @@ export interface Libp2p extends Startable, EventEmitter { contentRouting: ContentRouting keychain: KeyChain connectionManager: ConnectionManager + registrar: Registrar + metrics?: Metrics pubsub?: PubSub dht?: DualDHT diff --git a/src/libp2p.ts b/src/libp2p.ts index 142031e127..62f412bf20 100644 --- a/src/libp2p.ts +++ b/src/libp2p.ts @@ -32,7 +32,7 @@ import type { Connection } from '@libp2p/interfaces/connection' import type { PeerRouting } from '@libp2p/interfaces/peer-routing' import type { ContentRouting } from '@libp2p/interfaces/content-routing' import type { PubSub } from '@libp2p/interfaces/pubsub' -import type { ConnectionManager, StreamHandler } from '@libp2p/interfaces/registrar' +import type { ConnectionManager, Registrar, StreamHandler } from '@libp2p/interfaces/registrar' import type { PeerInfo } from '@libp2p/interfaces/peer-info' import type { Libp2p, Libp2pEvents, Libp2pInit, Libp2pOptions } from './index.js' import { validateConfig } from './config.js' @@ -43,6 +43,7 @@ import { concat as uint8ArrayConcat } from 'uint8arrays/concat' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import errCode from 'err-code' import { unmarshalPublicKey } from '@libp2p/crypto/keys' +import type { Metrics } from '@libp2p/interfaces/metrics' const log = logger('libp2p') @@ -59,6 +60,8 @@ export class Libp2pNode extends EventEmitter implements Libp2p { public peerRouting: PeerRouting public keychain: KeyChain public connectionManager: ConnectionManager + public registrar: Registrar + public metrics?: Metrics private started: boolean private readonly services: Startable[] @@ -78,7 +81,7 @@ export class Libp2pNode extends EventEmitter implements Libp2p { // Create Metrics if (init.metrics.enabled) { - this.components.setMetrics(this.configureComponent(new DefaultMetrics(init.metrics))) + this.metrics = this.components.setMetrics(this.configureComponent(new DefaultMetrics(init.metrics))) } this.components.setConnectionGater(this.configureComponent({ @@ -117,7 +120,7 @@ export class Libp2pNode extends EventEmitter implements Libp2p { this.connectionManager = this.components.setConnectionManager(this.configureComponent(new DefaultConnectionManager(this.components, init.connectionManager))) // Create the Registrar - this.components.setRegistrar(this.configureComponent(new DefaultRegistrar(this.components))) + this.registrar = this.components.setRegistrar(this.configureComponent(new DefaultRegistrar(this.components))) // Setup the transport manager this.components.setTransportManager(this.configureComponent(new DefaultTransportManager(this.components, init.transportManager))) @@ -180,6 +183,11 @@ export class Libp2pNode extends EventEmitter implements Libp2p { if (this.dht != null) { // add dht to routers peerRouters.push(this.configureComponent(new DHTPeerRouting(this.dht))) + + // use dht for peer discovery + this.dht.addEventListener('peer', (evt) => { + this.onDiscoveryPeer(evt) + }) } this.peerRouting = this.components.setPeerRouting(this.configureComponent(new DefaultPeerRouting(this.components, { From c64a586a20e51c79ace5ab94824121a6eb733d97 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Sat, 9 Apr 2022 09:26:25 +0100 Subject: [PATCH 352/447] chore: update aegir to the latest version (#1186) Removes boilerplate config that is no longer necessary --- .aegir.cjs => .aegir.js | 17 +- examples/libp2p-in-the-browser/package.json | 3 - examples/libp2p-in-the-browser/vite.config.js | 5 + examples/webrtc-direct/package.json | 3 - examples/webrtc-direct/vite.config.js | 5 + package.json | 44 +- src/circuit/circuit/hop.ts | 8 +- src/circuit/circuit/stop.ts | 6 +- src/circuit/circuit/stream-handler.ts | 11 +- src/circuit/circuit/utils.ts | 4 +- src/circuit/pb/index.d.ts | 173 ------ src/circuit/pb/index.js | 528 ------------------ src/circuit/pb/index.proto | 2 +- src/circuit/pb/index.ts | 88 +++ src/fetch/index.ts | 13 +- src/fetch/pb/proto.d.ts | 134 ----- src/fetch/pb/proto.js | 331 ----------- src/fetch/pb/proto.ts | 58 ++ src/identify/index.ts | 20 +- src/identify/pb/message.d.ts | 110 ---- src/identify/pb/message.js | 369 ------------ src/identify/pb/message.ts | 36 ++ src/insecure/index.ts | 12 +- src/insecure/pb/proto.d.ts | 134 ----- src/insecure/pb/proto.js | 388 ------------- src/insecure/pb/proto.ts | 61 ++ src/keychain/index.ts | 4 +- test/addresses/address-manager.spec.ts | 2 +- test/addresses/addresses.node.ts | 2 +- test/configuration/protocol-prefix.node.ts | 2 +- test/configuration/pubsub.spec.ts | 2 +- test/connection-manager/auto-dialler.spec.ts | 2 +- test/connection-manager/index.node.ts | 2 +- test/connection-manager/index.spec.ts | 2 +- test/content-routing/content-routing.node.ts | 2 +- .../content-routing/dht/configuration.node.ts | 2 +- test/content-routing/dht/operation.node.ts | 2 +- test/core/encryption.spec.ts | 2 +- test/core/listening.node.ts | 2 +- test/core/ping.node.ts | 2 +- test/dialing/dial-request.spec.ts | 2 +- test/dialing/direct.node.ts | 2 +- test/dialing/direct.spec.ts | 2 +- test/dialing/resolver.spec.ts | 2 +- test/fetch/fetch.node.ts | 2 +- test/identify/index.spec.ts | 4 +- test/insecure/plaintext.spec.ts | 2 +- test/interop.ts | 2 +- test/keychain/cms-interop.spec.ts | 2 +- test/keychain/keychain.spec.ts | 2 +- test/keychain/peerid.spec.ts | 2 +- test/metrics/index.node.ts | 2 +- test/metrics/index.spec.ts | 2 +- test/nat-manager/nat-manager.node.ts | 2 +- test/peer-discovery/index.node.ts | 2 +- test/peer-discovery/index.spec.ts | 2 +- test/peer-routing/peer-routing.node.ts | 2 +- test/pnet/index.spec.ts | 2 +- test/registrar/registrar.spec.ts | 2 +- test/relay/auto-relay.node.ts | 2 +- test/relay/relay.node.ts | 2 +- test/transports/transport-manager.node.ts | 2 +- test/transports/transport-manager.spec.ts | 2 +- test/upgrading/upgrader.spec.ts | 2 +- tsconfig.json | 5 +- 65 files changed, 364 insertions(+), 2284 deletions(-) rename .aegir.cjs => .aegir.js (79%) create mode 100644 examples/libp2p-in-the-browser/vite.config.js create mode 100644 examples/webrtc-direct/vite.config.js delete mode 100644 src/circuit/pb/index.d.ts delete mode 100644 src/circuit/pb/index.js create mode 100644 src/circuit/pb/index.ts delete mode 100644 src/fetch/pb/proto.d.ts delete mode 100644 src/fetch/pb/proto.js create mode 100644 src/fetch/pb/proto.ts delete mode 100644 src/identify/pb/message.d.ts delete mode 100644 src/identify/pb/message.js create mode 100644 src/identify/pb/message.ts delete mode 100644 src/insecure/pb/proto.d.ts delete mode 100644 src/insecure/pb/proto.js create mode 100644 src/insecure/pb/proto.ts diff --git a/.aegir.cjs b/.aegir.js similarity index 79% rename from .aegir.cjs rename to .aegir.js index 48d036975f..6186529b07 100644 --- a/.aegir.cjs +++ b/.aegir.js @@ -1,21 +1,20 @@ -'use strict' +import { WebSockets } from '@libp2p/websockets' +import { Mplex } from '@libp2p/mplex' +import { NOISE } from '@chainsafe/libp2p-noise' +import { pipe } from 'it-pipe' +import { createFromJSON } from '@libp2p/peer-id-factory' /** @type {import('aegir').PartialOptions} */ -module.exports = { +export default { build: { - bundlesizeMax: '253kB' + bundlesizeMax: '147kB' }, test: { before: async () => { const { createLibp2p } = await import('./dist/src/index.js') const { MULTIADDRS_WEBSOCKETS } = await import('./dist/test/fixtures/browser.js') - const { default: Peers } = await import('./dist/test/fixtures/peers.js') - const { WebSockets } = await import('@libp2p/websockets') - const { Mplex } = await import('@libp2p/mplex') - const { NOISE } = await import('@chainsafe/libp2p-noise') const { Plaintext } = await import('./dist/src/insecure/index.js') - const { pipe } = await import('it-pipe') - const { createFromJSON } = await import('@libp2p/peer-id-factory') + const { default: Peers } = await import('./dist/test/fixtures/peers.js') // Use the last peer const peerId = await createFromJSON(Peers[Peers.length - 1]) diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json index 6f330dc260..f8ee1eef53 100644 --- a/examples/libp2p-in-the-browser/package.json +++ b/examples/libp2p-in-the-browser/package.json @@ -3,9 +3,6 @@ "version": "1.0.0", "description": "A libp2p node running in the browser", "type": "module", - "browserslist": [ - "last 2 Chrome versions" - ], "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "vite" diff --git a/examples/libp2p-in-the-browser/vite.config.js b/examples/libp2p-in-the-browser/vite.config.js new file mode 100644 index 0000000000..04ae6bed00 --- /dev/null +++ b/examples/libp2p-in-the-browser/vite.config.js @@ -0,0 +1,5 @@ +export default { + build: { + target: 'es2020' + } +} \ No newline at end of file diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index 03a33b629a..57e79c6e36 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -3,9 +3,6 @@ "version": "0.0.1", "private": true, "type": "module", - "browserslist": [ - "last 2 Chrome versions" - ], "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "vite" diff --git a/examples/webrtc-direct/vite.config.js b/examples/webrtc-direct/vite.config.js new file mode 100644 index 0000000000..04ae6bed00 --- /dev/null +++ b/examples/webrtc-direct/vite.config.js @@ -0,0 +1,5 @@ +export default { + build: { + target: 'es2020' + } +} \ No newline at end of file diff --git a/package.json b/package.json index e1d745754c..58efc772a7 100644 --- a/package.json +++ b/package.json @@ -76,36 +76,34 @@ ] }, "scripts": { + "clean": "aegir clean", "lint": "aegir lint", - "build": "tsc", - "postbuild": "mkdirp dist/src/circuit/pb dist/src/fetch/pb dist/src/identify/pb dist/src/insecure/pb && cp src/circuit/pb/*.js src/circuit/pb/*.d.ts dist/src/circuit/pb && cp src/fetch/pb/*.js src/fetch/pb/*.d.ts dist/src/fetch/pb && cp src/identify/pb/*.js src/identify/pb/*.d.ts dist/src/identify/pb && cp src/insecure/pb/*.js src/insecure/pb/*.d.ts dist/src/insecure/pb", - "generate": "run-s generate:proto:* generate:proto-types:*", - "generate:proto:circuit": "pbjs -t static-module -w es6 -r libp2p-circuit --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/circuit/protocol/index.js ./src/circuit/protocol/index.proto", - "generate:proto:fetch": "pbjs -t static-module -w es6 -r libp2p-fetch --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/fetch/proto.js ./src/fetch/proto.proto", - "generate:proto:identify": "pbjs -t static-module -w es6 -r libp2p-identify --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/identify/message.js ./src/identify/message.proto", - "generate:proto:plaintext": "pbjs -t static-module -w es6 -r libp2p-plaintext --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/insecure/proto.js ./src/insecure/proto.proto", - "generate:proto-types:circuit": "pbts -o src/circuit/protocol/index.d.ts src/circuit/protocol/index.js", - "generate:proto-types:fetch": "pbts -o src/fetch/proto.d.ts src/fetch/proto.js", - "generate:proto-types:identify": "pbts -o src/identify/message.d.ts src/identify/message.js", - "generate:proto-types:plaintext": "pbts -o src/insecure/proto.d.ts src/insecure/proto.js", - "pretest": "npm run build", + "dep-check": "aegir dep-check", + "build": "aegir build", + "generate": "run-s generate:proto:*", + "generate:proto:circuit": "protons ./src/circuit/pb/index.proto", + "generate:proto:fetch": "protons ./src/fetch/pb/proto.proto", + "generate:proto:identify": "protons ./src/identify/pb/message.proto", + "generate:proto:plaintext": "protons ./src/insecure/pb/proto.proto", "test": "aegir test", - "test:node": "npm run test -- -t node -f \"./dist/test/**/*.{node,spec}.js\" --cov", - "test:chrome": "npm run test -- -t browser -f \"./dist/test/**/*.spec.js\" --cov", - "test:chrome-webworker": "npm run test -- -t webworker -f \"./dist/test/**/*.spec.js\"", - "test:firefox": "npm run test -- -t browser -f \"./dist/test/**/*.spec.js\" -- --browser firefox", - "test:firefox-webworker": "npm run test -- -t webworker -f \"./dist/test/**/*.spec.js\" -- --browser firefox", + "test:node": "aegir test -t node -f \"./dist/test/**/*.{node,spec}.js\" --cov", + "test:chrome": "aegir test -t browser -f \"./dist/test/**/*.spec.js\" --cov", + "test:chrome-webworker": "aegir test -t webworker -f \"./dist/test/**/*.spec.js\"", + "test:firefox": "aegir test -t browser -f \"./dist/test/**/*.spec.js\" -- --browser firefox", + "test:firefox-webworker": "aegir test -t webworker -f \"./dist/test/**/*.spec.js\" -- --browser firefox", "test:examples": "cd examples && npm run test:all", - "test:interop": "npm run test -- -t node -f dist/test/interop.js" + "test:interop": "aegir test -t node -f dist/test/interop.js" }, "dependencies": { "@achingbrain/nat-port-mapper": "^1.0.0", "@libp2p/connection": "^1.1.4", "@libp2p/crypto": "^0.22.9", "@libp2p/interfaces": "^1.3.17", + "@libp2p/logger": "^1.1.3", "@libp2p/multistream-select": "^1.0.3", "@libp2p/peer-id": "^1.1.8", "@libp2p/peer-id-factory": "^1.0.8", + "@libp2p/peer-record": "^1.0.8", "@libp2p/peer-store": "^1.0.6", "@libp2p/utils": "^1.0.9", "@multiformats/mafmt": "^11.0.2", @@ -144,7 +142,7 @@ "p-retry": "^5.0.0", "p-settle": "^5.0.0", "private-ip": "^2.3.3", - "protobufjs": "^6.11.2", + "protons-runtime": "^1.0.2", "retimer": "^3.0.0", "sanitize-filename": "^1.6.3", "set-delayed-interval": "^1.0.0", @@ -165,11 +163,13 @@ "@libp2p/delegated-peer-routing": "^1.0.2", "@libp2p/floodsub": "^1.0.2", "@libp2p/interface-compliance-tests": "^1.1.20", - "@libp2p/interop": "^1.0.0", + "@libp2p/interop": "^1.0.3", "@libp2p/kad-dht": "^1.0.3", "@libp2p/mdns": "^1.0.3", "@libp2p/mplex": "^1.0.1", + "@libp2p/pubsub": "^1.2.14", "@libp2p/tcp": "^1.0.6", + "@libp2p/topology": "^1.1.7", "@libp2p/tracked-map": "^1.0.4", "@libp2p/webrtc-star": "^1.0.3", "@libp2p/websockets": "^1.0.3", @@ -179,10 +179,11 @@ "@types/p-fifo": "^1.0.0", "@types/varint": "^6.0.0", "@types/xsalsa20": "^1.1.0", - "aegir": "^36.1.3", + "aegir": "^37.0.9", "buffer": "^6.0.3", "cborg": "^1.8.1", "delay": "^5.0.0", + "execa": "^6.1.0", "go-libp2p": "^0.0.6", "into-stream": "^7.0.0", "ipfs-http-client": "^56.0.1", @@ -194,6 +195,7 @@ "p-event": "^5.0.1", "p-times": "^4.0.0", "p-wait-for": "^4.1.0", + "protons": "^3.0.2", "rimraf": "^3.0.2", "sinon": "^13.0.1", "ts-sinon": "^2.0.2" diff --git a/src/circuit/circuit/hop.ts b/src/circuit/circuit/hop.ts index 5440813a90..8d0558842f 100644 --- a/src/circuit/circuit/hop.ts +++ b/src/circuit/circuit/hop.ts @@ -2,7 +2,7 @@ import { logger } from '@libp2p/logger' import errCode from 'err-code' import { validateAddrs } from './utils.js' import { StreamHandler } from './stream-handler.js' -import { CircuitRelay as CircuitPB, ICircuitRelay } from '../pb/index.js' +import { CircuitRelay as CircuitPB } from '../pb/index.js' import { pipe } from 'it-pipe' import { codes as Errors } from '../../errors.js' import { stop } from './stop.js' @@ -17,7 +17,7 @@ const log = logger('libp2p:circuit:hop') export interface HopRequest { connection: Connection - request: ICircuitRelay + request: CircuitPB streamHandler: StreamHandler circuit: Circuit connectionManager: ConnectionManager @@ -120,7 +120,7 @@ export async function handleHop (hopRequest: HopRequest) { export interface HopConfig { connection: Connection - request: ICircuitRelay + request: CircuitPB } /** @@ -153,7 +153,7 @@ export async function hop (options: HopConfig): Promise> { log('hop request failed with code %d, closing stream', response.code) streamHandler.close() - throw errCode(new Error(`HOP request failed with code ${response.code}`), Errors.ERR_HOP_REQUEST_FAILED) + throw errCode(new Error(`HOP request failed with code "${response.code ?? 'unknown'}"`), Errors.ERR_HOP_REQUEST_FAILED) } export interface CanHopOptions { diff --git a/src/circuit/circuit/stop.ts b/src/circuit/circuit/stop.ts index edef9672f4..dab6aab893 100644 --- a/src/circuit/circuit/stop.ts +++ b/src/circuit/circuit/stop.ts @@ -1,5 +1,5 @@ import { logger } from '@libp2p/logger' -import { CircuitRelay as CircuitPB, ICircuitRelay } from '../pb/index.js' +import { CircuitRelay as CircuitPB } from '../pb/index.js' import { RELAY_CODEC } from '../multicodec.js' import { StreamHandler } from './stream-handler.js' import { validateAddrs } from './utils.js' @@ -10,7 +10,7 @@ const log = logger('libp2p:circuit:stop') export interface HandleStopOptions { connection: Connection - request: ICircuitRelay + request: CircuitPB streamHandler: StreamHandler } @@ -44,7 +44,7 @@ export function handleStop (options: HandleStopOptions): Duplex | un export interface StopOptions { connection: Connection - request: ICircuitRelay + request: CircuitPB } /** diff --git a/src/circuit/circuit/stream-handler.ts b/src/circuit/circuit/stream-handler.ts index 8b55f36ad6..3d53bb4d86 100644 --- a/src/circuit/circuit/stream-handler.ts +++ b/src/circuit/circuit/stream-handler.ts @@ -1,7 +1,7 @@ import { logger } from '@libp2p/logger' import * as lp from 'it-length-prefixed' import { Handshake, handshake } from 'it-handshake' -import { CircuitRelay, ICircuitRelay } from '../pb/index.js' +import { CircuitRelay } from '../pb/index.js' import type { Stream } from '@libp2p/interfaces/connection' import type { Source } from 'it-stream-types' @@ -53,10 +53,9 @@ export class StreamHandler { /** * Encode and write array of buffers */ - write (msg: ICircuitRelay) { + write (msg: CircuitRelay) { log('write message type %s', msg.type) - // @ts-expect-error lp.encode expects type type 'Buffer | BufferList', not 'Uint8Array' - this.shake.write(lp.encode.single(CircuitRelay.encode(msg).finish())) + this.shake.write(lp.encode.single(CircuitRelay.encode(msg)).slice()) } /** @@ -68,9 +67,9 @@ export class StreamHandler { } /** - * @param {ICircuitRelay} msg - An unencoded CircuitRelay protobuf message + * @param {CircuitRelay} msg - An unencoded CircuitRelay protobuf message */ - end (msg: ICircuitRelay) { + end (msg: CircuitRelay) { this.write(msg) this.close() } diff --git a/src/circuit/circuit/utils.ts b/src/circuit/circuit/utils.ts index b5fe34f2d0..21e5e2938a 100644 --- a/src/circuit/circuit/utils.ts +++ b/src/circuit/circuit/utils.ts @@ -1,5 +1,5 @@ import { Multiaddr } from '@multiformats/multiaddr' -import { CircuitRelay, ICircuitRelay } from '../pb/index.js' +import { CircuitRelay } from '../pb/index.js' import type { StreamHandler } from './stream-handler.js' /** @@ -15,7 +15,7 @@ function writeResponse (streamHandler: StreamHandler, status: CircuitRelay.Statu /** * Validate incomming HOP/STOP message */ -export function validateAddrs (msg: ICircuitRelay, streamHandler: StreamHandler) { +export function validateAddrs (msg: CircuitRelay, streamHandler: StreamHandler) { try { if (msg.dstPeer?.addrs != null) { msg.dstPeer.addrs.forEach((addr) => { diff --git a/src/circuit/pb/index.d.ts b/src/circuit/pb/index.d.ts deleted file mode 100644 index 68e4880b95..0000000000 --- a/src/circuit/pb/index.d.ts +++ /dev/null @@ -1,173 +0,0 @@ -import * as $protobuf from "protobufjs"; -/** Properties of a CircuitRelay. */ -export interface ICircuitRelay { - - /** CircuitRelay type */ - type?: (CircuitRelay.Type|null); - - /** CircuitRelay srcPeer */ - srcPeer?: (CircuitRelay.IPeer|null); - - /** CircuitRelay dstPeer */ - dstPeer?: (CircuitRelay.IPeer|null); - - /** CircuitRelay code */ - code?: (CircuitRelay.Status|null); -} - -/** Represents a CircuitRelay. */ -export class CircuitRelay implements ICircuitRelay { - - /** - * Constructs a new CircuitRelay. - * @param [p] Properties to set - */ - constructor(p?: ICircuitRelay); - - /** CircuitRelay type. */ - public type: CircuitRelay.Type; - - /** CircuitRelay srcPeer. */ - public srcPeer?: (CircuitRelay.IPeer|null); - - /** CircuitRelay dstPeer. */ - public dstPeer?: (CircuitRelay.IPeer|null); - - /** CircuitRelay code. */ - public code: CircuitRelay.Status; - - /** - * Encodes the specified CircuitRelay message. Does not implicitly {@link CircuitRelay.verify|verify} messages. - * @param m CircuitRelay message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: ICircuitRelay, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a CircuitRelay message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns CircuitRelay - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): CircuitRelay; - - /** - * Creates a CircuitRelay message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns CircuitRelay - */ - public static fromObject(d: { [k: string]: any }): CircuitRelay; - - /** - * Creates a plain object from a CircuitRelay message. Also converts values to other types if specified. - * @param m CircuitRelay - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: CircuitRelay, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this CircuitRelay to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; -} - -export namespace CircuitRelay { - - /** Status enum. */ - enum Status { - SUCCESS = 100, - HOP_SRC_ADDR_TOO_LONG = 220, - HOP_DST_ADDR_TOO_LONG = 221, - HOP_SRC_MULTIADDR_INVALID = 250, - HOP_DST_MULTIADDR_INVALID = 251, - HOP_NO_CONN_TO_DST = 260, - HOP_CANT_DIAL_DST = 261, - HOP_CANT_OPEN_DST_STREAM = 262, - HOP_CANT_SPEAK_RELAY = 270, - HOP_CANT_RELAY_TO_SELF = 280, - STOP_SRC_ADDR_TOO_LONG = 320, - STOP_DST_ADDR_TOO_LONG = 321, - STOP_SRC_MULTIADDR_INVALID = 350, - STOP_DST_MULTIADDR_INVALID = 351, - STOP_RELAY_REFUSED = 390, - MALFORMED_MESSAGE = 400 - } - - /** Type enum. */ - enum Type { - HOP = 1, - STOP = 2, - STATUS = 3, - CAN_HOP = 4 - } - - /** Properties of a Peer. */ - interface IPeer { - - /** Peer id */ - id: Uint8Array; - - /** Peer addrs */ - addrs?: (Uint8Array[]|null); - } - - /** Represents a Peer. */ - class Peer implements IPeer { - - /** - * Constructs a new Peer. - * @param [p] Properties to set - */ - constructor(p?: CircuitRelay.IPeer); - - /** Peer id. */ - public id: Uint8Array; - - /** Peer addrs. */ - public addrs: Uint8Array[]; - - /** - * Encodes the specified Peer message. Does not implicitly {@link CircuitRelay.Peer.verify|verify} messages. - * @param m Peer message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: CircuitRelay.IPeer, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a Peer message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns Peer - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): CircuitRelay.Peer; - - /** - * Creates a Peer message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns Peer - */ - public static fromObject(d: { [k: string]: any }): CircuitRelay.Peer; - - /** - * Creates a plain object from a Peer message. Also converts values to other types if specified. - * @param m Peer - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: CircuitRelay.Peer, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this Peer to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } -} diff --git a/src/circuit/pb/index.js b/src/circuit/pb/index.js deleted file mode 100644 index 2157d6deb9..0000000000 --- a/src/circuit/pb/index.js +++ /dev/null @@ -1,528 +0,0 @@ -/*eslint-disable*/ -import $protobuf from "protobufjs/minimal.js"; - -// Common aliases -const $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; - -// Exported root namespace -const $root = $protobuf.roots["libp2p-circuit"] || ($protobuf.roots["libp2p-circuit"] = {}); - -export const CircuitRelay = $root.CircuitRelay = (() => { - - /** - * Properties of a CircuitRelay. - * @exports ICircuitRelay - * @interface ICircuitRelay - * @property {CircuitRelay.Type|null} [type] CircuitRelay type - * @property {CircuitRelay.IPeer|null} [srcPeer] CircuitRelay srcPeer - * @property {CircuitRelay.IPeer|null} [dstPeer] CircuitRelay dstPeer - * @property {CircuitRelay.Status|null} [code] CircuitRelay code - */ - - /** - * Constructs a new CircuitRelay. - * @exports CircuitRelay - * @classdesc Represents a CircuitRelay. - * @implements ICircuitRelay - * @constructor - * @param {ICircuitRelay=} [p] Properties to set - */ - function CircuitRelay(p) { - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * CircuitRelay type. - * @member {CircuitRelay.Type} type - * @memberof CircuitRelay - * @instance - */ - CircuitRelay.prototype.type = 1; - - /** - * CircuitRelay srcPeer. - * @member {CircuitRelay.IPeer|null|undefined} srcPeer - * @memberof CircuitRelay - * @instance - */ - CircuitRelay.prototype.srcPeer = null; - - /** - * CircuitRelay dstPeer. - * @member {CircuitRelay.IPeer|null|undefined} dstPeer - * @memberof CircuitRelay - * @instance - */ - CircuitRelay.prototype.dstPeer = null; - - /** - * CircuitRelay code. - * @member {CircuitRelay.Status} code - * @memberof CircuitRelay - * @instance - */ - CircuitRelay.prototype.code = 100; - - /** - * Encodes the specified CircuitRelay message. Does not implicitly {@link CircuitRelay.verify|verify} messages. - * @function encode - * @memberof CircuitRelay - * @static - * @param {ICircuitRelay} m CircuitRelay message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - CircuitRelay.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.type != null && Object.hasOwnProperty.call(m, "type")) - w.uint32(8).int32(m.type); - if (m.srcPeer != null && Object.hasOwnProperty.call(m, "srcPeer")) - $root.CircuitRelay.Peer.encode(m.srcPeer, w.uint32(18).fork()).ldelim(); - if (m.dstPeer != null && Object.hasOwnProperty.call(m, "dstPeer")) - $root.CircuitRelay.Peer.encode(m.dstPeer, w.uint32(26).fork()).ldelim(); - if (m.code != null && Object.hasOwnProperty.call(m, "code")) - w.uint32(32).int32(m.code); - return w; - }; - - /** - * Decodes a CircuitRelay message from the specified reader or buffer. - * @function decode - * @memberof CircuitRelay - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {CircuitRelay} CircuitRelay - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - CircuitRelay.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.CircuitRelay(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.type = r.int32(); - break; - case 2: - m.srcPeer = $root.CircuitRelay.Peer.decode(r, r.uint32()); - break; - case 3: - m.dstPeer = $root.CircuitRelay.Peer.decode(r, r.uint32()); - break; - case 4: - m.code = r.int32(); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a CircuitRelay message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof CircuitRelay - * @static - * @param {Object.} d Plain object - * @returns {CircuitRelay} CircuitRelay - */ - CircuitRelay.fromObject = function fromObject(d) { - if (d instanceof $root.CircuitRelay) - return d; - var m = new $root.CircuitRelay(); - switch (d.type) { - case "HOP": - case 1: - m.type = 1; - break; - case "STOP": - case 2: - m.type = 2; - break; - case "STATUS": - case 3: - m.type = 3; - break; - case "CAN_HOP": - case 4: - m.type = 4; - break; - } - if (d.srcPeer != null) { - if (typeof d.srcPeer !== "object") - throw TypeError(".CircuitRelay.srcPeer: object expected"); - m.srcPeer = $root.CircuitRelay.Peer.fromObject(d.srcPeer); - } - if (d.dstPeer != null) { - if (typeof d.dstPeer !== "object") - throw TypeError(".CircuitRelay.dstPeer: object expected"); - m.dstPeer = $root.CircuitRelay.Peer.fromObject(d.dstPeer); - } - switch (d.code) { - case "SUCCESS": - case 100: - m.code = 100; - break; - case "HOP_SRC_ADDR_TOO_LONG": - case 220: - m.code = 220; - break; - case "HOP_DST_ADDR_TOO_LONG": - case 221: - m.code = 221; - break; - case "HOP_SRC_MULTIADDR_INVALID": - case 250: - m.code = 250; - break; - case "HOP_DST_MULTIADDR_INVALID": - case 251: - m.code = 251; - break; - case "HOP_NO_CONN_TO_DST": - case 260: - m.code = 260; - break; - case "HOP_CANT_DIAL_DST": - case 261: - m.code = 261; - break; - case "HOP_CANT_OPEN_DST_STREAM": - case 262: - m.code = 262; - break; - case "HOP_CANT_SPEAK_RELAY": - case 270: - m.code = 270; - break; - case "HOP_CANT_RELAY_TO_SELF": - case 280: - m.code = 280; - break; - case "STOP_SRC_ADDR_TOO_LONG": - case 320: - m.code = 320; - break; - case "STOP_DST_ADDR_TOO_LONG": - case 321: - m.code = 321; - break; - case "STOP_SRC_MULTIADDR_INVALID": - case 350: - m.code = 350; - break; - case "STOP_DST_MULTIADDR_INVALID": - case 351: - m.code = 351; - break; - case "STOP_RELAY_REFUSED": - case 390: - m.code = 390; - break; - case "MALFORMED_MESSAGE": - case 400: - m.code = 400; - break; - } - return m; - }; - - /** - * Creates a plain object from a CircuitRelay message. Also converts values to other types if specified. - * @function toObject - * @memberof CircuitRelay - * @static - * @param {CircuitRelay} m CircuitRelay - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - CircuitRelay.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.defaults) { - d.type = o.enums === String ? "HOP" : 1; - d.srcPeer = null; - d.dstPeer = null; - d.code = o.enums === String ? "SUCCESS" : 100; - } - if (m.type != null && m.hasOwnProperty("type")) { - d.type = o.enums === String ? $root.CircuitRelay.Type[m.type] : m.type; - } - if (m.srcPeer != null && m.hasOwnProperty("srcPeer")) { - d.srcPeer = $root.CircuitRelay.Peer.toObject(m.srcPeer, o); - } - if (m.dstPeer != null && m.hasOwnProperty("dstPeer")) { - d.dstPeer = $root.CircuitRelay.Peer.toObject(m.dstPeer, o); - } - if (m.code != null && m.hasOwnProperty("code")) { - d.code = o.enums === String ? $root.CircuitRelay.Status[m.code] : m.code; - } - return d; - }; - - /** - * Converts this CircuitRelay to JSON. - * @function toJSON - * @memberof CircuitRelay - * @instance - * @returns {Object.} JSON object - */ - CircuitRelay.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - /** - * Status enum. - * @name CircuitRelay.Status - * @enum {number} - * @property {number} SUCCESS=100 SUCCESS value - * @property {number} HOP_SRC_ADDR_TOO_LONG=220 HOP_SRC_ADDR_TOO_LONG value - * @property {number} HOP_DST_ADDR_TOO_LONG=221 HOP_DST_ADDR_TOO_LONG value - * @property {number} HOP_SRC_MULTIADDR_INVALID=250 HOP_SRC_MULTIADDR_INVALID value - * @property {number} HOP_DST_MULTIADDR_INVALID=251 HOP_DST_MULTIADDR_INVALID value - * @property {number} HOP_NO_CONN_TO_DST=260 HOP_NO_CONN_TO_DST value - * @property {number} HOP_CANT_DIAL_DST=261 HOP_CANT_DIAL_DST value - * @property {number} HOP_CANT_OPEN_DST_STREAM=262 HOP_CANT_OPEN_DST_STREAM value - * @property {number} HOP_CANT_SPEAK_RELAY=270 HOP_CANT_SPEAK_RELAY value - * @property {number} HOP_CANT_RELAY_TO_SELF=280 HOP_CANT_RELAY_TO_SELF value - * @property {number} STOP_SRC_ADDR_TOO_LONG=320 STOP_SRC_ADDR_TOO_LONG value - * @property {number} STOP_DST_ADDR_TOO_LONG=321 STOP_DST_ADDR_TOO_LONG value - * @property {number} STOP_SRC_MULTIADDR_INVALID=350 STOP_SRC_MULTIADDR_INVALID value - * @property {number} STOP_DST_MULTIADDR_INVALID=351 STOP_DST_MULTIADDR_INVALID value - * @property {number} STOP_RELAY_REFUSED=390 STOP_RELAY_REFUSED value - * @property {number} MALFORMED_MESSAGE=400 MALFORMED_MESSAGE value - */ - CircuitRelay.Status = (function() { - const valuesById = {}, values = Object.create(valuesById); - values[valuesById[100] = "SUCCESS"] = 100; - values[valuesById[220] = "HOP_SRC_ADDR_TOO_LONG"] = 220; - values[valuesById[221] = "HOP_DST_ADDR_TOO_LONG"] = 221; - values[valuesById[250] = "HOP_SRC_MULTIADDR_INVALID"] = 250; - values[valuesById[251] = "HOP_DST_MULTIADDR_INVALID"] = 251; - values[valuesById[260] = "HOP_NO_CONN_TO_DST"] = 260; - values[valuesById[261] = "HOP_CANT_DIAL_DST"] = 261; - values[valuesById[262] = "HOP_CANT_OPEN_DST_STREAM"] = 262; - values[valuesById[270] = "HOP_CANT_SPEAK_RELAY"] = 270; - values[valuesById[280] = "HOP_CANT_RELAY_TO_SELF"] = 280; - values[valuesById[320] = "STOP_SRC_ADDR_TOO_LONG"] = 320; - values[valuesById[321] = "STOP_DST_ADDR_TOO_LONG"] = 321; - values[valuesById[350] = "STOP_SRC_MULTIADDR_INVALID"] = 350; - values[valuesById[351] = "STOP_DST_MULTIADDR_INVALID"] = 351; - values[valuesById[390] = "STOP_RELAY_REFUSED"] = 390; - values[valuesById[400] = "MALFORMED_MESSAGE"] = 400; - return values; - })(); - - /** - * Type enum. - * @name CircuitRelay.Type - * @enum {number} - * @property {number} HOP=1 HOP value - * @property {number} STOP=2 STOP value - * @property {number} STATUS=3 STATUS value - * @property {number} CAN_HOP=4 CAN_HOP value - */ - CircuitRelay.Type = (function() { - const valuesById = {}, values = Object.create(valuesById); - values[valuesById[1] = "HOP"] = 1; - values[valuesById[2] = "STOP"] = 2; - values[valuesById[3] = "STATUS"] = 3; - values[valuesById[4] = "CAN_HOP"] = 4; - return values; - })(); - - CircuitRelay.Peer = (function() { - - /** - * Properties of a Peer. - * @memberof CircuitRelay - * @interface IPeer - * @property {Uint8Array} id Peer id - * @property {Array.|null} [addrs] Peer addrs - */ - - /** - * Constructs a new Peer. - * @memberof CircuitRelay - * @classdesc Represents a Peer. - * @implements IPeer - * @constructor - * @param {CircuitRelay.IPeer=} [p] Properties to set - */ - function Peer(p) { - this.addrs = []; - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * Peer id. - * @member {Uint8Array} id - * @memberof CircuitRelay.Peer - * @instance - */ - Peer.prototype.id = $util.newBuffer([]); - - /** - * Peer addrs. - * @member {Array.} addrs - * @memberof CircuitRelay.Peer - * @instance - */ - Peer.prototype.addrs = $util.emptyArray; - - /** - * Encodes the specified Peer message. Does not implicitly {@link CircuitRelay.Peer.verify|verify} messages. - * @function encode - * @memberof CircuitRelay.Peer - * @static - * @param {CircuitRelay.IPeer} m Peer message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Peer.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - w.uint32(10).bytes(m.id); - if (m.addrs != null && m.addrs.length) { - for (var i = 0; i < m.addrs.length; ++i) - w.uint32(18).bytes(m.addrs[i]); - } - return w; - }; - - /** - * Decodes a Peer message from the specified reader or buffer. - * @function decode - * @memberof CircuitRelay.Peer - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {CircuitRelay.Peer} Peer - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Peer.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.CircuitRelay.Peer(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.id = r.bytes(); - break; - case 2: - if (!(m.addrs && m.addrs.length)) - m.addrs = []; - m.addrs.push(r.bytes()); - break; - default: - r.skipType(t & 7); - break; - } - } - if (!m.hasOwnProperty("id")) - throw $util.ProtocolError("missing required 'id'", { instance: m }); - return m; - }; - - /** - * Creates a Peer message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof CircuitRelay.Peer - * @static - * @param {Object.} d Plain object - * @returns {CircuitRelay.Peer} Peer - */ - Peer.fromObject = function fromObject(d) { - if (d instanceof $root.CircuitRelay.Peer) - return d; - var m = new $root.CircuitRelay.Peer(); - if (d.id != null) { - if (typeof d.id === "string") - $util.base64.decode(d.id, m.id = $util.newBuffer($util.base64.length(d.id)), 0); - else if (d.id.length) - m.id = d.id; - } - if (d.addrs) { - if (!Array.isArray(d.addrs)) - throw TypeError(".CircuitRelay.Peer.addrs: array expected"); - m.addrs = []; - for (var i = 0; i < d.addrs.length; ++i) { - if (typeof d.addrs[i] === "string") - $util.base64.decode(d.addrs[i], m.addrs[i] = $util.newBuffer($util.base64.length(d.addrs[i])), 0); - else if (d.addrs[i].length) - m.addrs[i] = d.addrs[i]; - } - } - return m; - }; - - /** - * Creates a plain object from a Peer message. Also converts values to other types if specified. - * @function toObject - * @memberof CircuitRelay.Peer - * @static - * @param {CircuitRelay.Peer} m Peer - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - Peer.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.arrays || o.defaults) { - d.addrs = []; - } - if (o.defaults) { - if (o.bytes === String) - d.id = ""; - else { - d.id = []; - if (o.bytes !== Array) - d.id = $util.newBuffer(d.id); - } - } - if (m.id != null && m.hasOwnProperty("id")) { - d.id = o.bytes === String ? $util.base64.encode(m.id, 0, m.id.length) : o.bytes === Array ? Array.prototype.slice.call(m.id) : m.id; - } - if (m.addrs && m.addrs.length) { - d.addrs = []; - for (var j = 0; j < m.addrs.length; ++j) { - d.addrs[j] = o.bytes === String ? $util.base64.encode(m.addrs[j], 0, m.addrs[j].length) : o.bytes === Array ? Array.prototype.slice.call(m.addrs[j]) : m.addrs[j]; - } - } - return d; - }; - - /** - * Converts this Peer to JSON. - * @function toJSON - * @memberof CircuitRelay.Peer - * @instance - * @returns {Object.} JSON object - */ - Peer.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return Peer; - })(); - - return CircuitRelay; -})(); - -export { $root as default }; diff --git a/src/circuit/pb/index.proto b/src/circuit/pb/index.proto index 259ad2835a..1eaec2e29b 100644 --- a/src/circuit/pb/index.proto +++ b/src/circuit/pb/index.proto @@ -1,4 +1,4 @@ -syntax = "proto2"; +syntax = "proto3"; message CircuitRelay { diff --git a/src/circuit/pb/index.ts b/src/circuit/pb/index.ts new file mode 100644 index 0000000000..5337cc4ee4 --- /dev/null +++ b/src/circuit/pb/index.ts @@ -0,0 +1,88 @@ +/* eslint-disable import/export */ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { enumeration, encodeMessage, decodeMessage, message, bytes } from 'protons-runtime' + +export interface CircuitRelay { + type?: CircuitRelay.Type + srcPeer?: CircuitRelay.Peer + dstPeer?: CircuitRelay.Peer + code?: CircuitRelay.Status +} + +export namespace CircuitRelay { + export enum Status { + SUCCESS = 'SUCCESS', + HOP_SRC_ADDR_TOO_LONG = 'HOP_SRC_ADDR_TOO_LONG', + HOP_DST_ADDR_TOO_LONG = 'HOP_DST_ADDR_TOO_LONG', + HOP_SRC_MULTIADDR_INVALID = 'HOP_SRC_MULTIADDR_INVALID', + HOP_DST_MULTIADDR_INVALID = 'HOP_DST_MULTIADDR_INVALID', + HOP_NO_CONN_TO_DST = 'HOP_NO_CONN_TO_DST', + HOP_CANT_DIAL_DST = 'HOP_CANT_DIAL_DST', + HOP_CANT_OPEN_DST_STREAM = 'HOP_CANT_OPEN_DST_STREAM', + HOP_CANT_SPEAK_RELAY = 'HOP_CANT_SPEAK_RELAY', + HOP_CANT_RELAY_TO_SELF = 'HOP_CANT_RELAY_TO_SELF', + STOP_SRC_ADDR_TOO_LONG = 'STOP_SRC_ADDR_TOO_LONG', + STOP_DST_ADDR_TOO_LONG = 'STOP_DST_ADDR_TOO_LONG', + STOP_SRC_MULTIADDR_INVALID = 'STOP_SRC_MULTIADDR_INVALID', + STOP_DST_MULTIADDR_INVALID = 'STOP_DST_MULTIADDR_INVALID', + STOP_RELAY_REFUSED = 'STOP_RELAY_REFUSED', + MALFORMED_MESSAGE = 'MALFORMED_MESSAGE' + } + + export namespace Status { + export const codec = () => { + return enumeration(Status) + } + } + export enum Type { + HOP = 'HOP', + STOP = 'STOP', + STATUS = 'STATUS', + CAN_HOP = 'CAN_HOP' + } + + export namespace Type { + export const codec = () => { + return enumeration(Type) + } + } + export interface Peer { + id: Uint8Array + addrs: Uint8Array[] + } + + export namespace Peer { + export const codec = () => { + return message({ + 1: { name: 'id', codec: bytes }, + 2: { name: 'addrs', codec: bytes, repeats: true } + }) + } + + export const encode = (obj: Peer): Uint8Array => { + return encodeMessage(obj, Peer.codec()) + } + + export const decode = (buf: Uint8Array): Peer => { + return decodeMessage(buf, Peer.codec()) + } + } + + export const codec = () => { + return message({ + 1: { name: 'type', codec: CircuitRelay.Type.codec(), optional: true }, + 2: { name: 'srcPeer', codec: CircuitRelay.Peer.codec(), optional: true }, + 3: { name: 'dstPeer', codec: CircuitRelay.Peer.codec(), optional: true }, + 4: { name: 'code', codec: CircuitRelay.Status.codec(), optional: true } + }) + } + + export const encode = (obj: CircuitRelay): Uint8Array => { + return encodeMessage(obj, CircuitRelay.codec()) + } + + export const decode = (buf: Uint8Array): CircuitRelay => { + return decodeMessage(buf, CircuitRelay.codec()) + } +} diff --git a/src/fetch/index.ts b/src/fetch/index.ts index 65f906dbf1..69e14147f8 100644 --- a/src/fetch/index.ts +++ b/src/fetch/index.ts @@ -75,8 +75,7 @@ export class FetchService implements Startable { const shake = handshake(stream) // send message - const request = new FetchRequest({ identifier: key }) - shake.write(lp.encode.single(FetchRequest.encode(request).finish()).slice()) + shake.write(lp.encode.single(FetchRequest.encode({ identifier: key })).slice()) // read response // @ts-expect-error fromReader returns a Source which has no .next method @@ -109,21 +108,21 @@ export class FetchService implements Startable { // @ts-expect-error fromReader returns a Source which has no .next method const request = FetchRequest.decode((await lp.decode.fromReader(shake.reader).next()).value.slice()) - let response + let response: FetchResponse const lookup = this._getLookupFunction(request.identifier) if (lookup != null) { const data = await lookup(request.identifier) if (data != null) { - response = new FetchResponse({ status: FetchResponse.StatusCode.OK, data }) + response = { status: FetchResponse.StatusCode.OK, data } } else { - response = new FetchResponse({ status: FetchResponse.StatusCode.NOT_FOUND }) + response = { status: FetchResponse.StatusCode.NOT_FOUND, data: new Uint8Array(0) } } } else { const errmsg = (new TextEncoder()).encode('No lookup function registered for key: ' + request.identifier) - response = new FetchResponse({ status: FetchResponse.StatusCode.ERROR, data: errmsg }) + response = { status: FetchResponse.StatusCode.ERROR, data: errmsg } } - shake.write(lp.encode.single(FetchResponse.encode(response).finish()).slice()) + shake.write(lp.encode.single(FetchResponse.encode(response)).slice()) } /** diff --git a/src/fetch/pb/proto.d.ts b/src/fetch/pb/proto.d.ts deleted file mode 100644 index bf022f516c..0000000000 --- a/src/fetch/pb/proto.d.ts +++ /dev/null @@ -1,134 +0,0 @@ -import * as $protobuf from "protobufjs"; -/** Properties of a FetchRequest. */ -export interface IFetchRequest { - - /** FetchRequest identifier */ - identifier?: (string|null); -} - -/** Represents a FetchRequest. */ -export class FetchRequest implements IFetchRequest { - - /** - * Constructs a new FetchRequest. - * @param [p] Properties to set - */ - constructor(p?: IFetchRequest); - - /** FetchRequest identifier. */ - public identifier: string; - - /** - * Encodes the specified FetchRequest message. Does not implicitly {@link FetchRequest.verify|verify} messages. - * @param m FetchRequest message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: IFetchRequest, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a FetchRequest message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns FetchRequest - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): FetchRequest; - - /** - * Creates a FetchRequest message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns FetchRequest - */ - public static fromObject(d: { [k: string]: any }): FetchRequest; - - /** - * Creates a plain object from a FetchRequest message. Also converts values to other types if specified. - * @param m FetchRequest - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: FetchRequest, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this FetchRequest to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; -} - -/** Properties of a FetchResponse. */ -export interface IFetchResponse { - - /** FetchResponse status */ - status?: (FetchResponse.StatusCode|null); - - /** FetchResponse data */ - data?: (Uint8Array|null); -} - -/** Represents a FetchResponse. */ -export class FetchResponse implements IFetchResponse { - - /** - * Constructs a new FetchResponse. - * @param [p] Properties to set - */ - constructor(p?: IFetchResponse); - - /** FetchResponse status. */ - public status: FetchResponse.StatusCode; - - /** FetchResponse data. */ - public data: Uint8Array; - - /** - * Encodes the specified FetchResponse message. Does not implicitly {@link FetchResponse.verify|verify} messages. - * @param m FetchResponse message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: IFetchResponse, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a FetchResponse message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns FetchResponse - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): FetchResponse; - - /** - * Creates a FetchResponse message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns FetchResponse - */ - public static fromObject(d: { [k: string]: any }): FetchResponse; - - /** - * Creates a plain object from a FetchResponse message. Also converts values to other types if specified. - * @param m FetchResponse - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: FetchResponse, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this FetchResponse to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; -} - -export namespace FetchResponse { - - /** StatusCode enum. */ - enum StatusCode { - OK = 0, - NOT_FOUND = 1, - ERROR = 2 - } -} diff --git a/src/fetch/pb/proto.js b/src/fetch/pb/proto.js deleted file mode 100644 index b77d6375a8..0000000000 --- a/src/fetch/pb/proto.js +++ /dev/null @@ -1,331 +0,0 @@ -/*eslint-disable*/ -import $protobuf from "protobufjs/minimal.js"; - -// Common aliases -const $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; - -// Exported root namespace -const $root = $protobuf.roots["libp2p-fetch"] || ($protobuf.roots["libp2p-fetch"] = {}); - -export const FetchRequest = $root.FetchRequest = (() => { - - /** - * Properties of a FetchRequest. - * @exports IFetchRequest - * @interface IFetchRequest - * @property {string|null} [identifier] FetchRequest identifier - */ - - /** - * Constructs a new FetchRequest. - * @exports FetchRequest - * @classdesc Represents a FetchRequest. - * @implements IFetchRequest - * @constructor - * @param {IFetchRequest=} [p] Properties to set - */ - function FetchRequest(p) { - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * FetchRequest identifier. - * @member {string} identifier - * @memberof FetchRequest - * @instance - */ - FetchRequest.prototype.identifier = ""; - - /** - * Encodes the specified FetchRequest message. Does not implicitly {@link FetchRequest.verify|verify} messages. - * @function encode - * @memberof FetchRequest - * @static - * @param {IFetchRequest} m FetchRequest message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - FetchRequest.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.identifier != null && Object.hasOwnProperty.call(m, "identifier")) - w.uint32(10).string(m.identifier); - return w; - }; - - /** - * Decodes a FetchRequest message from the specified reader or buffer. - * @function decode - * @memberof FetchRequest - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {FetchRequest} FetchRequest - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - FetchRequest.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.FetchRequest(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.identifier = r.string(); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a FetchRequest message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof FetchRequest - * @static - * @param {Object.} d Plain object - * @returns {FetchRequest} FetchRequest - */ - FetchRequest.fromObject = function fromObject(d) { - if (d instanceof $root.FetchRequest) - return d; - var m = new $root.FetchRequest(); - if (d.identifier != null) { - m.identifier = String(d.identifier); - } - return m; - }; - - /** - * Creates a plain object from a FetchRequest message. Also converts values to other types if specified. - * @function toObject - * @memberof FetchRequest - * @static - * @param {FetchRequest} m FetchRequest - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - FetchRequest.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.defaults) { - d.identifier = ""; - } - if (m.identifier != null && m.hasOwnProperty("identifier")) { - d.identifier = m.identifier; - } - return d; - }; - - /** - * Converts this FetchRequest to JSON. - * @function toJSON - * @memberof FetchRequest - * @instance - * @returns {Object.} JSON object - */ - FetchRequest.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return FetchRequest; -})(); - -export const FetchResponse = $root.FetchResponse = (() => { - - /** - * Properties of a FetchResponse. - * @exports IFetchResponse - * @interface IFetchResponse - * @property {FetchResponse.StatusCode|null} [status] FetchResponse status - * @property {Uint8Array|null} [data] FetchResponse data - */ - - /** - * Constructs a new FetchResponse. - * @exports FetchResponse - * @classdesc Represents a FetchResponse. - * @implements IFetchResponse - * @constructor - * @param {IFetchResponse=} [p] Properties to set - */ - function FetchResponse(p) { - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * FetchResponse status. - * @member {FetchResponse.StatusCode} status - * @memberof FetchResponse - * @instance - */ - FetchResponse.prototype.status = 0; - - /** - * FetchResponse data. - * @member {Uint8Array} data - * @memberof FetchResponse - * @instance - */ - FetchResponse.prototype.data = $util.newBuffer([]); - - /** - * Encodes the specified FetchResponse message. Does not implicitly {@link FetchResponse.verify|verify} messages. - * @function encode - * @memberof FetchResponse - * @static - * @param {IFetchResponse} m FetchResponse message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - FetchResponse.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.status != null && Object.hasOwnProperty.call(m, "status")) - w.uint32(8).int32(m.status); - if (m.data != null && Object.hasOwnProperty.call(m, "data")) - w.uint32(18).bytes(m.data); - return w; - }; - - /** - * Decodes a FetchResponse message from the specified reader or buffer. - * @function decode - * @memberof FetchResponse - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {FetchResponse} FetchResponse - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - FetchResponse.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.FetchResponse(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.status = r.int32(); - break; - case 2: - m.data = r.bytes(); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a FetchResponse message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof FetchResponse - * @static - * @param {Object.} d Plain object - * @returns {FetchResponse} FetchResponse - */ - FetchResponse.fromObject = function fromObject(d) { - if (d instanceof $root.FetchResponse) - return d; - var m = new $root.FetchResponse(); - switch (d.status) { - case "OK": - case 0: - m.status = 0; - break; - case "NOT_FOUND": - case 1: - m.status = 1; - break; - case "ERROR": - case 2: - m.status = 2; - break; - } - if (d.data != null) { - if (typeof d.data === "string") - $util.base64.decode(d.data, m.data = $util.newBuffer($util.base64.length(d.data)), 0); - else if (d.data.length) - m.data = d.data; - } - return m; - }; - - /** - * Creates a plain object from a FetchResponse message. Also converts values to other types if specified. - * @function toObject - * @memberof FetchResponse - * @static - * @param {FetchResponse} m FetchResponse - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - FetchResponse.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.defaults) { - d.status = o.enums === String ? "OK" : 0; - if (o.bytes === String) - d.data = ""; - else { - d.data = []; - if (o.bytes !== Array) - d.data = $util.newBuffer(d.data); - } - } - if (m.status != null && m.hasOwnProperty("status")) { - d.status = o.enums === String ? $root.FetchResponse.StatusCode[m.status] : m.status; - } - if (m.data != null && m.hasOwnProperty("data")) { - d.data = o.bytes === String ? $util.base64.encode(m.data, 0, m.data.length) : o.bytes === Array ? Array.prototype.slice.call(m.data) : m.data; - } - return d; - }; - - /** - * Converts this FetchResponse to JSON. - * @function toJSON - * @memberof FetchResponse - * @instance - * @returns {Object.} JSON object - */ - FetchResponse.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - /** - * StatusCode enum. - * @name FetchResponse.StatusCode - * @enum {number} - * @property {number} OK=0 OK value - * @property {number} NOT_FOUND=1 NOT_FOUND value - * @property {number} ERROR=2 ERROR value - */ - FetchResponse.StatusCode = (function() { - const valuesById = {}, values = Object.create(valuesById); - values[valuesById[0] = "OK"] = 0; - values[valuesById[1] = "NOT_FOUND"] = 1; - values[valuesById[2] = "ERROR"] = 2; - return values; - })(); - - return FetchResponse; -})(); - -export { $root as default }; diff --git a/src/fetch/pb/proto.ts b/src/fetch/pb/proto.ts new file mode 100644 index 0000000000..b6c112effe --- /dev/null +++ b/src/fetch/pb/proto.ts @@ -0,0 +1,58 @@ +/* eslint-disable import/export */ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { encodeMessage, decodeMessage, message, string, enumeration, bytes } from 'protons-runtime' + +export interface FetchRequest { + identifier: string +} + +export namespace FetchRequest { + export const codec = () => { + return message({ + 1: { name: 'identifier', codec: string } + }) + } + + export const encode = (obj: FetchRequest): Uint8Array => { + return encodeMessage(obj, FetchRequest.codec()) + } + + export const decode = (buf: Uint8Array): FetchRequest => { + return decodeMessage(buf, FetchRequest.codec()) + } +} + +export interface FetchResponse { + status: FetchResponse.StatusCode + data: Uint8Array +} + +export namespace FetchResponse { + export enum StatusCode { + OK = 'OK', + NOT_FOUND = 'NOT_FOUND', + ERROR = 'ERROR' + } + + export namespace StatusCode { + export const codec = () => { + return enumeration(StatusCode) + } + } + + export const codec = () => { + return message({ + 1: { name: 'status', codec: FetchResponse.StatusCode.codec() }, + 2: { name: 'data', codec: bytes } + }) + } + + export const encode = (obj: FetchResponse): Uint8Array => { + return encodeMessage(obj, FetchResponse.codec()) + } + + export const decode = (buf: Uint8Array): FetchResponse => { + return decodeMessage(buf, FetchResponse.codec()) + } +} diff --git a/src/identify/index.ts b/src/identify/index.ts index bfd3b03390..5cbc448499 100644 --- a/src/identify/index.ts +++ b/src/identify/index.ts @@ -8,7 +8,7 @@ import drain from 'it-drain' import first from 'it-first' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { Multiaddr, protocols } from '@multiformats/multiaddr' -import Message from './pb/message.js' +import { Identify } from './pb/message.js' import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record' import { MULTICODEC_IDENTIFY, @@ -132,11 +132,11 @@ export class IdentifyService implements Startable { const { stream } = await connection.newStream([this.identifyPushProtocolStr]) await pipe( - [Message.Identify.encode({ + [Identify.encode({ listenAddrs, signedPeerRecord, protocols - }).finish()], + })], lp.encode(), stream, drain @@ -194,9 +194,9 @@ export class IdentifyService implements Startable { throw errCode(new Error('No data could be retrieved'), codes.ERR_CONNECTION_ENDED) } - let message + let message: Identify try { - message = Message.Identify.decode(data) + message = Identify.decode(data) } catch (err: any) { throw errCode(err, codes.ERR_INVALID_MESSAGE) } @@ -325,7 +325,7 @@ export class IdentifyService implements Startable { signedPeerRecord = envelope.marshal() } - const message = Message.Identify.encode({ + const message = Identify.encode({ protocolVersion: this.host.protocolVersion, agentVersion: this.host.agentVersion, publicKey, @@ -333,7 +333,7 @@ export class IdentifyService implements Startable { signedPeerRecord, observedAddr: connection.remoteAddr.bytes, protocols: peerData.protocols - }).finish() + }) await pipe( [message], @@ -352,7 +352,7 @@ export class IdentifyService implements Startable { async _handlePush (data: IncomingStreamData) { const { connection, stream } = data - let message + let message: Identify | undefined try { const data = await pipe( [], @@ -362,7 +362,7 @@ export class IdentifyService implements Startable { ) if (data != null) { - message = Message.Identify.decode(data) + message = Identify.decode(data) } } catch (err: any) { return log.error('received invalid message', err) @@ -442,4 +442,4 @@ export const multicodecs = { IDENTIFY_PUSH: MULTICODEC_IDENTIFY_PUSH } -export { Message } +export const Message = { Identify } diff --git a/src/identify/pb/message.d.ts b/src/identify/pb/message.d.ts deleted file mode 100644 index 561dbc5569..0000000000 --- a/src/identify/pb/message.d.ts +++ /dev/null @@ -1,110 +0,0 @@ -import * as $protobuf from "protobufjs"; -/** Properties of an Identify. */ -export interface IIdentify { - - /** Identify protocolVersion */ - protocolVersion?: (string|null); - - /** Identify agentVersion */ - agentVersion?: (string|null); - - /** Identify publicKey */ - publicKey?: (Uint8Array|null); - - /** Identify listenAddrs */ - listenAddrs?: (Uint8Array[]|null); - - /** Identify observedAddr */ - observedAddr?: (Uint8Array|null); - - /** Identify protocols */ - protocols?: (string[]|null); - - /** Identify signedPeerRecord */ - signedPeerRecord?: (Uint8Array|null); -} - -/** Represents an Identify. */ -export class Identify implements IIdentify { - - /** - * Constructs a new Identify. - * @param [p] Properties to set - */ - constructor(p?: IIdentify); - - /** Identify protocolVersion. */ - public protocolVersion?: (string|null); - - /** Identify agentVersion. */ - public agentVersion?: (string|null); - - /** Identify publicKey. */ - public publicKey?: (Uint8Array|null); - - /** Identify listenAddrs. */ - public listenAddrs: Uint8Array[]; - - /** Identify observedAddr. */ - public observedAddr?: (Uint8Array|null); - - /** Identify protocols. */ - public protocols: string[]; - - /** Identify signedPeerRecord. */ - public signedPeerRecord?: (Uint8Array|null); - - /** Identify _protocolVersion. */ - public _protocolVersion?: "protocolVersion"; - - /** Identify _agentVersion. */ - public _agentVersion?: "agentVersion"; - - /** Identify _publicKey. */ - public _publicKey?: "publicKey"; - - /** Identify _observedAddr. */ - public _observedAddr?: "observedAddr"; - - /** Identify _signedPeerRecord. */ - public _signedPeerRecord?: "signedPeerRecord"; - - /** - * Encodes the specified Identify message. Does not implicitly {@link Identify.verify|verify} messages. - * @param m Identify message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: IIdentify, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes an Identify message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns Identify - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Identify; - - /** - * Creates an Identify message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns Identify - */ - public static fromObject(d: { [k: string]: any }): Identify; - - /** - * Creates a plain object from an Identify message. Also converts values to other types if specified. - * @param m Identify - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: Identify, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this Identify to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; -} diff --git a/src/identify/pb/message.js b/src/identify/pb/message.js deleted file mode 100644 index 80764cad56..0000000000 --- a/src/identify/pb/message.js +++ /dev/null @@ -1,369 +0,0 @@ -/*eslint-disable*/ -import $protobuf from "protobufjs/minimal.js"; - -// Common aliases -const $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; - -// Exported root namespace -const $root = $protobuf.roots["libp2p-identify"] || ($protobuf.roots["libp2p-identify"] = {}); - -export const Identify = $root.Identify = (() => { - - /** - * Properties of an Identify. - * @exports IIdentify - * @interface IIdentify - * @property {string|null} [protocolVersion] Identify protocolVersion - * @property {string|null} [agentVersion] Identify agentVersion - * @property {Uint8Array|null} [publicKey] Identify publicKey - * @property {Array.|null} [listenAddrs] Identify listenAddrs - * @property {Uint8Array|null} [observedAddr] Identify observedAddr - * @property {Array.|null} [protocols] Identify protocols - * @property {Uint8Array|null} [signedPeerRecord] Identify signedPeerRecord - */ - - /** - * Constructs a new Identify. - * @exports Identify - * @classdesc Represents an Identify. - * @implements IIdentify - * @constructor - * @param {IIdentify=} [p] Properties to set - */ - function Identify(p) { - this.listenAddrs = []; - this.protocols = []; - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * Identify protocolVersion. - * @member {string|null|undefined} protocolVersion - * @memberof Identify - * @instance - */ - Identify.prototype.protocolVersion = null; - - /** - * Identify agentVersion. - * @member {string|null|undefined} agentVersion - * @memberof Identify - * @instance - */ - Identify.prototype.agentVersion = null; - - /** - * Identify publicKey. - * @member {Uint8Array|null|undefined} publicKey - * @memberof Identify - * @instance - */ - Identify.prototype.publicKey = null; - - /** - * Identify listenAddrs. - * @member {Array.} listenAddrs - * @memberof Identify - * @instance - */ - Identify.prototype.listenAddrs = $util.emptyArray; - - /** - * Identify observedAddr. - * @member {Uint8Array|null|undefined} observedAddr - * @memberof Identify - * @instance - */ - Identify.prototype.observedAddr = null; - - /** - * Identify protocols. - * @member {Array.} protocols - * @memberof Identify - * @instance - */ - Identify.prototype.protocols = $util.emptyArray; - - /** - * Identify signedPeerRecord. - * @member {Uint8Array|null|undefined} signedPeerRecord - * @memberof Identify - * @instance - */ - Identify.prototype.signedPeerRecord = null; - - // OneOf field names bound to virtual getters and setters - let $oneOfFields; - - /** - * Identify _protocolVersion. - * @member {"protocolVersion"|undefined} _protocolVersion - * @memberof Identify - * @instance - */ - Object.defineProperty(Identify.prototype, "_protocolVersion", { - get: $util.oneOfGetter($oneOfFields = ["protocolVersion"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Identify _agentVersion. - * @member {"agentVersion"|undefined} _agentVersion - * @memberof Identify - * @instance - */ - Object.defineProperty(Identify.prototype, "_agentVersion", { - get: $util.oneOfGetter($oneOfFields = ["agentVersion"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Identify _publicKey. - * @member {"publicKey"|undefined} _publicKey - * @memberof Identify - * @instance - */ - Object.defineProperty(Identify.prototype, "_publicKey", { - get: $util.oneOfGetter($oneOfFields = ["publicKey"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Identify _observedAddr. - * @member {"observedAddr"|undefined} _observedAddr - * @memberof Identify - * @instance - */ - Object.defineProperty(Identify.prototype, "_observedAddr", { - get: $util.oneOfGetter($oneOfFields = ["observedAddr"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Identify _signedPeerRecord. - * @member {"signedPeerRecord"|undefined} _signedPeerRecord - * @memberof Identify - * @instance - */ - Object.defineProperty(Identify.prototype, "_signedPeerRecord", { - get: $util.oneOfGetter($oneOfFields = ["signedPeerRecord"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Encodes the specified Identify message. Does not implicitly {@link Identify.verify|verify} messages. - * @function encode - * @memberof Identify - * @static - * @param {IIdentify} m Identify message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Identify.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.publicKey != null && Object.hasOwnProperty.call(m, "publicKey")) - w.uint32(10).bytes(m.publicKey); - if (m.listenAddrs != null && m.listenAddrs.length) { - for (var i = 0; i < m.listenAddrs.length; ++i) - w.uint32(18).bytes(m.listenAddrs[i]); - } - if (m.protocols != null && m.protocols.length) { - for (var i = 0; i < m.protocols.length; ++i) - w.uint32(26).string(m.protocols[i]); - } - if (m.observedAddr != null && Object.hasOwnProperty.call(m, "observedAddr")) - w.uint32(34).bytes(m.observedAddr); - if (m.protocolVersion != null && Object.hasOwnProperty.call(m, "protocolVersion")) - w.uint32(42).string(m.protocolVersion); - if (m.agentVersion != null && Object.hasOwnProperty.call(m, "agentVersion")) - w.uint32(50).string(m.agentVersion); - if (m.signedPeerRecord != null && Object.hasOwnProperty.call(m, "signedPeerRecord")) - w.uint32(66).bytes(m.signedPeerRecord); - return w; - }; - - /** - * Decodes an Identify message from the specified reader or buffer. - * @function decode - * @memberof Identify - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {Identify} Identify - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Identify.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.Identify(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 5: - m.protocolVersion = r.string(); - break; - case 6: - m.agentVersion = r.string(); - break; - case 1: - m.publicKey = r.bytes(); - break; - case 2: - if (!(m.listenAddrs && m.listenAddrs.length)) - m.listenAddrs = []; - m.listenAddrs.push(r.bytes()); - break; - case 4: - m.observedAddr = r.bytes(); - break; - case 3: - if (!(m.protocols && m.protocols.length)) - m.protocols = []; - m.protocols.push(r.string()); - break; - case 8: - m.signedPeerRecord = r.bytes(); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates an Identify message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof Identify - * @static - * @param {Object.} d Plain object - * @returns {Identify} Identify - */ - Identify.fromObject = function fromObject(d) { - if (d instanceof $root.Identify) - return d; - var m = new $root.Identify(); - if (d.protocolVersion != null) { - m.protocolVersion = String(d.protocolVersion); - } - if (d.agentVersion != null) { - m.agentVersion = String(d.agentVersion); - } - if (d.publicKey != null) { - if (typeof d.publicKey === "string") - $util.base64.decode(d.publicKey, m.publicKey = $util.newBuffer($util.base64.length(d.publicKey)), 0); - else if (d.publicKey.length) - m.publicKey = d.publicKey; - } - if (d.listenAddrs) { - if (!Array.isArray(d.listenAddrs)) - throw TypeError(".Identify.listenAddrs: array expected"); - m.listenAddrs = []; - for (var i = 0; i < d.listenAddrs.length; ++i) { - if (typeof d.listenAddrs[i] === "string") - $util.base64.decode(d.listenAddrs[i], m.listenAddrs[i] = $util.newBuffer($util.base64.length(d.listenAddrs[i])), 0); - else if (d.listenAddrs[i].length) - m.listenAddrs[i] = d.listenAddrs[i]; - } - } - if (d.observedAddr != null) { - if (typeof d.observedAddr === "string") - $util.base64.decode(d.observedAddr, m.observedAddr = $util.newBuffer($util.base64.length(d.observedAddr)), 0); - else if (d.observedAddr.length) - m.observedAddr = d.observedAddr; - } - if (d.protocols) { - if (!Array.isArray(d.protocols)) - throw TypeError(".Identify.protocols: array expected"); - m.protocols = []; - for (var i = 0; i < d.protocols.length; ++i) { - m.protocols[i] = String(d.protocols[i]); - } - } - if (d.signedPeerRecord != null) { - if (typeof d.signedPeerRecord === "string") - $util.base64.decode(d.signedPeerRecord, m.signedPeerRecord = $util.newBuffer($util.base64.length(d.signedPeerRecord)), 0); - else if (d.signedPeerRecord.length) - m.signedPeerRecord = d.signedPeerRecord; - } - return m; - }; - - /** - * Creates a plain object from an Identify message. Also converts values to other types if specified. - * @function toObject - * @memberof Identify - * @static - * @param {Identify} m Identify - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - Identify.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.arrays || o.defaults) { - d.listenAddrs = []; - d.protocols = []; - } - if (m.publicKey != null && m.hasOwnProperty("publicKey")) { - d.publicKey = o.bytes === String ? $util.base64.encode(m.publicKey, 0, m.publicKey.length) : o.bytes === Array ? Array.prototype.slice.call(m.publicKey) : m.publicKey; - if (o.oneofs) - d._publicKey = "publicKey"; - } - if (m.listenAddrs && m.listenAddrs.length) { - d.listenAddrs = []; - for (var j = 0; j < m.listenAddrs.length; ++j) { - d.listenAddrs[j] = o.bytes === String ? $util.base64.encode(m.listenAddrs[j], 0, m.listenAddrs[j].length) : o.bytes === Array ? Array.prototype.slice.call(m.listenAddrs[j]) : m.listenAddrs[j]; - } - } - if (m.protocols && m.protocols.length) { - d.protocols = []; - for (var j = 0; j < m.protocols.length; ++j) { - d.protocols[j] = m.protocols[j]; - } - } - if (m.observedAddr != null && m.hasOwnProperty("observedAddr")) { - d.observedAddr = o.bytes === String ? $util.base64.encode(m.observedAddr, 0, m.observedAddr.length) : o.bytes === Array ? Array.prototype.slice.call(m.observedAddr) : m.observedAddr; - if (o.oneofs) - d._observedAddr = "observedAddr"; - } - if (m.protocolVersion != null && m.hasOwnProperty("protocolVersion")) { - d.protocolVersion = m.protocolVersion; - if (o.oneofs) - d._protocolVersion = "protocolVersion"; - } - if (m.agentVersion != null && m.hasOwnProperty("agentVersion")) { - d.agentVersion = m.agentVersion; - if (o.oneofs) - d._agentVersion = "agentVersion"; - } - if (m.signedPeerRecord != null && m.hasOwnProperty("signedPeerRecord")) { - d.signedPeerRecord = o.bytes === String ? $util.base64.encode(m.signedPeerRecord, 0, m.signedPeerRecord.length) : o.bytes === Array ? Array.prototype.slice.call(m.signedPeerRecord) : m.signedPeerRecord; - if (o.oneofs) - d._signedPeerRecord = "signedPeerRecord"; - } - return d; - }; - - /** - * Converts this Identify to JSON. - * @function toJSON - * @memberof Identify - * @instance - * @returns {Object.} JSON object - */ - Identify.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return Identify; -})(); - -export { $root as default }; diff --git a/src/identify/pb/message.ts b/src/identify/pb/message.ts new file mode 100644 index 0000000000..7270135656 --- /dev/null +++ b/src/identify/pb/message.ts @@ -0,0 +1,36 @@ +/* eslint-disable import/export */ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { encodeMessage, decodeMessage, message, string, bytes } from 'protons-runtime' + +export interface Identify { + protocolVersion?: string + agentVersion?: string + publicKey?: Uint8Array + listenAddrs: Uint8Array[] + observedAddr?: Uint8Array + protocols: string[] + signedPeerRecord?: Uint8Array +} + +export namespace Identify { + export const codec = () => { + return message({ + 5: { name: 'protocolVersion', codec: string, optional: true }, + 6: { name: 'agentVersion', codec: string, optional: true }, + 1: { name: 'publicKey', codec: bytes, optional: true }, + 2: { name: 'listenAddrs', codec: bytes, repeats: true }, + 4: { name: 'observedAddr', codec: bytes, optional: true }, + 3: { name: 'protocols', codec: string, repeats: true }, + 8: { name: 'signedPeerRecord', codec: bytes, optional: true } + }) + } + + export const encode = (obj: Identify): Uint8Array => { + return encodeMessage(obj, Identify.codec()) + } + + export const decode = (buf: Uint8Array): Identify => { + return decodeMessage(buf, Identify.codec()) + } +} diff --git a/src/insecure/index.ts b/src/insecure/index.ts index 3c941beff2..76dc0ca379 100644 --- a/src/insecure/index.ts +++ b/src/insecure/index.ts @@ -2,7 +2,7 @@ import { logger } from '@libp2p/logger' import { handshake } from 'it-handshake' import * as lp from 'it-length-prefixed' import { UnexpectedPeerError, InvalidCryptoExchangeError } from '@libp2p/interfaces/connection-encrypter/errors' -import { Exchange, IExchange, KeyType } from './pb/proto.js' +import { Exchange, KeyType } from './pb/proto.js' import type { PeerId } from '@libp2p/interfaces/peer-id' import { peerIdFromBytes, peerIdFromKeys } from '@libp2p/peer-id' import type { ConnectionEncrypter, SecuredConnection } from '@libp2p/interfaces/connection-encrypter' @@ -11,8 +11,8 @@ import type { Duplex } from 'it-stream-types' const log = logger('libp2p:plaintext') const PROTOCOL = '/plaintext/2.0.0' -function lpEncodeExchange (exchange: IExchange) { - const pb = Exchange.encode(exchange).finish() +function lpEncodeExchange (exchange: Exchange) { + const pb = Exchange.encode(exchange) return lp.encode.single(pb) } @@ -37,7 +37,7 @@ async function encrypt (localId: PeerId, conn: Duplex, remoteId?: Pe id: localId.toBytes(), pubkey: { Type: type, - Data: localId.publicKey + Data: localId.publicKey ?? new Uint8Array(0) } }).slice() ) @@ -52,6 +52,10 @@ async function encrypt (localId: PeerId, conn: Duplex, remoteId?: Pe let peerId try { + if (id.pubkey == null) { + throw new Error('Public key missing') + } + if (id.pubkey.Data.length === 0) { throw new Error('Public key data too short') } diff --git a/src/insecure/pb/proto.d.ts b/src/insecure/pb/proto.d.ts deleted file mode 100644 index 191c866968..0000000000 --- a/src/insecure/pb/proto.d.ts +++ /dev/null @@ -1,134 +0,0 @@ -import * as $protobuf from "protobufjs"; -/** Properties of an Exchange. */ -export interface IExchange { - - /** Exchange id */ - id?: (Uint8Array|null); - - /** Exchange pubkey */ - pubkey?: (IPublicKey|null); -} - -/** Represents an Exchange. */ -export class Exchange implements IExchange { - - /** - * Constructs a new Exchange. - * @param [p] Properties to set - */ - constructor(p?: IExchange); - - /** Exchange id. */ - public id?: (Uint8Array|null); - - /** Exchange pubkey. */ - public pubkey?: (IPublicKey|null); - - /** Exchange _id. */ - public _id?: "id"; - - /** Exchange _pubkey. */ - public _pubkey?: "pubkey"; - - /** - * Encodes the specified Exchange message. Does not implicitly {@link Exchange.verify|verify} messages. - * @param m Exchange message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: IExchange, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes an Exchange message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns Exchange - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Exchange; - - /** - * Creates an Exchange message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns Exchange - */ - public static fromObject(d: { [k: string]: any }): Exchange; - - /** - * Creates a plain object from an Exchange message. Also converts values to other types if specified. - * @param m Exchange - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: Exchange, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this Exchange to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; -} - -/** KeyType enum. */ -export enum KeyType { - RSA = 0, - Ed25519 = 1, - Secp256k1 = 2, - ECDSA = 3 -} - -/** Represents a PublicKey. */ -export class PublicKey implements IPublicKey { - - /** - * Constructs a new PublicKey. - * @param [p] Properties to set - */ - constructor(p?: IPublicKey); - - /** PublicKey Type. */ - public Type: KeyType; - - /** PublicKey Data. */ - public Data: Uint8Array; - - /** - * Encodes the specified PublicKey message. Does not implicitly {@link PublicKey.verify|verify} messages. - * @param m PublicKey message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: IPublicKey, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a PublicKey message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns PublicKey - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): PublicKey; - - /** - * Creates a PublicKey message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns PublicKey - */ - public static fromObject(d: { [k: string]: any }): PublicKey; - - /** - * Creates a plain object from a PublicKey message. Also converts values to other types if specified. - * @param m PublicKey - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: PublicKey, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this PublicKey to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; -} diff --git a/src/insecure/pb/proto.js b/src/insecure/pb/proto.js deleted file mode 100644 index 54fa39cf41..0000000000 --- a/src/insecure/pb/proto.js +++ /dev/null @@ -1,388 +0,0 @@ -/*eslint-disable*/ -import $protobuf from "protobufjs/minimal.js"; - -// Common aliases -const $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; - -// Exported root namespace -const $root = $protobuf.roots["libp2p-plaintext"] || ($protobuf.roots["libp2p-plaintext"] = {}); - -export const Exchange = $root.Exchange = (() => { - - /** - * Properties of an Exchange. - * @exports IExchange - * @interface IExchange - * @property {Uint8Array|null} [id] Exchange id - * @property {IPublicKey|null} [pubkey] Exchange pubkey - */ - - /** - * Constructs a new Exchange. - * @exports Exchange - * @classdesc Represents an Exchange. - * @implements IExchange - * @constructor - * @param {IExchange=} [p] Properties to set - */ - function Exchange(p) { - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * Exchange id. - * @member {Uint8Array|null|undefined} id - * @memberof Exchange - * @instance - */ - Exchange.prototype.id = null; - - /** - * Exchange pubkey. - * @member {IPublicKey|null|undefined} pubkey - * @memberof Exchange - * @instance - */ - Exchange.prototype.pubkey = null; - - // OneOf field names bound to virtual getters and setters - let $oneOfFields; - - /** - * Exchange _id. - * @member {"id"|undefined} _id - * @memberof Exchange - * @instance - */ - Object.defineProperty(Exchange.prototype, "_id", { - get: $util.oneOfGetter($oneOfFields = ["id"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Exchange _pubkey. - * @member {"pubkey"|undefined} _pubkey - * @memberof Exchange - * @instance - */ - Object.defineProperty(Exchange.prototype, "_pubkey", { - get: $util.oneOfGetter($oneOfFields = ["pubkey"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Encodes the specified Exchange message. Does not implicitly {@link Exchange.verify|verify} messages. - * @function encode - * @memberof Exchange - * @static - * @param {IExchange} m Exchange message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Exchange.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.id != null && Object.hasOwnProperty.call(m, "id")) - w.uint32(10).bytes(m.id); - if (m.pubkey != null && Object.hasOwnProperty.call(m, "pubkey")) - $root.PublicKey.encode(m.pubkey, w.uint32(18).fork()).ldelim(); - return w; - }; - - /** - * Decodes an Exchange message from the specified reader or buffer. - * @function decode - * @memberof Exchange - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {Exchange} Exchange - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Exchange.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.Exchange(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.id = r.bytes(); - break; - case 2: - m.pubkey = $root.PublicKey.decode(r, r.uint32()); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates an Exchange message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof Exchange - * @static - * @param {Object.} d Plain object - * @returns {Exchange} Exchange - */ - Exchange.fromObject = function fromObject(d) { - if (d instanceof $root.Exchange) - return d; - var m = new $root.Exchange(); - if (d.id != null) { - if (typeof d.id === "string") - $util.base64.decode(d.id, m.id = $util.newBuffer($util.base64.length(d.id)), 0); - else if (d.id.length) - m.id = d.id; - } - if (d.pubkey != null) { - if (typeof d.pubkey !== "object") - throw TypeError(".Exchange.pubkey: object expected"); - m.pubkey = $root.PublicKey.fromObject(d.pubkey); - } - return m; - }; - - /** - * Creates a plain object from an Exchange message. Also converts values to other types if specified. - * @function toObject - * @memberof Exchange - * @static - * @param {Exchange} m Exchange - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - Exchange.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (m.id != null && m.hasOwnProperty("id")) { - d.id = o.bytes === String ? $util.base64.encode(m.id, 0, m.id.length) : o.bytes === Array ? Array.prototype.slice.call(m.id) : m.id; - if (o.oneofs) - d._id = "id"; - } - if (m.pubkey != null && m.hasOwnProperty("pubkey")) { - d.pubkey = $root.PublicKey.toObject(m.pubkey, o); - if (o.oneofs) - d._pubkey = "pubkey"; - } - return d; - }; - - /** - * Converts this Exchange to JSON. - * @function toJSON - * @memberof Exchange - * @instance - * @returns {Object.} JSON object - */ - Exchange.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return Exchange; -})(); - -/** - * KeyType enum. - * @exports KeyType - * @enum {number} - * @property {number} RSA=0 RSA value - * @property {number} Ed25519=1 Ed25519 value - * @property {number} Secp256k1=2 Secp256k1 value - * @property {number} ECDSA=3 ECDSA value - */ -export const KeyType = $root.KeyType = (() => { - const valuesById = {}, values = Object.create(valuesById); - values[valuesById[0] = "RSA"] = 0; - values[valuesById[1] = "Ed25519"] = 1; - values[valuesById[2] = "Secp256k1"] = 2; - values[valuesById[3] = "ECDSA"] = 3; - return values; -})(); - -export const PublicKey = $root.PublicKey = (() => { - - /** - * Properties of a PublicKey. - * @exports IPublicKey - * @interface IPublicKey - * @property {KeyType|null} [Type] PublicKey Type - * @property {Uint8Array|null} [Data] PublicKey Data - */ - - /** - * Constructs a new PublicKey. - * @exports PublicKey - * @classdesc Represents a PublicKey. - * @implements IPublicKey - * @constructor - * @param {IPublicKey=} [p] Properties to set - */ - function PublicKey(p) { - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * PublicKey Type. - * @member {KeyType} Type - * @memberof PublicKey - * @instance - */ - PublicKey.prototype.Type = 0; - - /** - * PublicKey Data. - * @member {Uint8Array} Data - * @memberof PublicKey - * @instance - */ - PublicKey.prototype.Data = $util.newBuffer([]); - - /** - * Encodes the specified PublicKey message. Does not implicitly {@link PublicKey.verify|verify} messages. - * @function encode - * @memberof PublicKey - * @static - * @param {IPublicKey} m PublicKey message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - PublicKey.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.Type != null && Object.hasOwnProperty.call(m, "Type")) - w.uint32(8).int32(m.Type); - if (m.Data != null && Object.hasOwnProperty.call(m, "Data")) - w.uint32(18).bytes(m.Data); - return w; - }; - - /** - * Decodes a PublicKey message from the specified reader or buffer. - * @function decode - * @memberof PublicKey - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {PublicKey} PublicKey - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - PublicKey.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.PublicKey(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.Type = r.int32(); - break; - case 2: - m.Data = r.bytes(); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a PublicKey message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof PublicKey - * @static - * @param {Object.} d Plain object - * @returns {PublicKey} PublicKey - */ - PublicKey.fromObject = function fromObject(d) { - if (d instanceof $root.PublicKey) - return d; - var m = new $root.PublicKey(); - switch (d.Type) { - case "RSA": - case 0: - m.Type = 0; - break; - case "Ed25519": - case 1: - m.Type = 1; - break; - case "Secp256k1": - case 2: - m.Type = 2; - break; - case "ECDSA": - case 3: - m.Type = 3; - break; - } - if (d.Data != null) { - if (typeof d.Data === "string") - $util.base64.decode(d.Data, m.Data = $util.newBuffer($util.base64.length(d.Data)), 0); - else if (d.Data.length) - m.Data = d.Data; - } - return m; - }; - - /** - * Creates a plain object from a PublicKey message. Also converts values to other types if specified. - * @function toObject - * @memberof PublicKey - * @static - * @param {PublicKey} m PublicKey - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - PublicKey.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.defaults) { - d.Type = o.enums === String ? "RSA" : 0; - if (o.bytes === String) - d.Data = ""; - else { - d.Data = []; - if (o.bytes !== Array) - d.Data = $util.newBuffer(d.Data); - } - } - if (m.Type != null && m.hasOwnProperty("Type")) { - d.Type = o.enums === String ? $root.KeyType[m.Type] : m.Type; - } - if (m.Data != null && m.hasOwnProperty("Data")) { - d.Data = o.bytes === String ? $util.base64.encode(m.Data, 0, m.Data.length) : o.bytes === Array ? Array.prototype.slice.call(m.Data) : m.Data; - } - return d; - }; - - /** - * Converts this PublicKey to JSON. - * @function toJSON - * @memberof PublicKey - * @instance - * @returns {Object.} JSON object - */ - PublicKey.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return PublicKey; -})(); - -export { $root as default }; diff --git a/src/insecure/pb/proto.ts b/src/insecure/pb/proto.ts new file mode 100644 index 0000000000..03909f9ea3 --- /dev/null +++ b/src/insecure/pb/proto.ts @@ -0,0 +1,61 @@ +/* eslint-disable import/export */ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { encodeMessage, decodeMessage, message, bytes, enumeration } from 'protons-runtime' + +export interface Exchange { + id?: Uint8Array + pubkey?: PublicKey +} + +export namespace Exchange { + export const codec = () => { + return message({ + 1: { name: 'id', codec: bytes, optional: true }, + 2: { name: 'pubkey', codec: PublicKey.codec(), optional: true } + }) + } + + export const encode = (obj: Exchange): Uint8Array => { + return encodeMessage(obj, Exchange.codec()) + } + + export const decode = (buf: Uint8Array): Exchange => { + return decodeMessage(buf, Exchange.codec()) + } +} + +export enum KeyType { + RSA = 'RSA', + Ed25519 = 'Ed25519', + Secp256k1 = 'Secp256k1', + ECDSA = 'ECDSA' +} + +export namespace KeyType { + export const codec = () => { + return enumeration(KeyType) + } +} + +export interface PublicKey { + Type: KeyType + Data: Uint8Array +} + +export namespace PublicKey { + export const codec = () => { + return message({ + 1: { name: 'Type', codec: KeyType.codec() }, + 2: { name: 'Data', codec: bytes } + }) + } + + export const encode = (obj: PublicKey): Uint8Array => { + return encodeMessage(obj, PublicKey.codec()) + } + + export const decode = (buf: Uint8Array): PublicKey => { + return decodeMessage(buf, PublicKey.codec()) + } +} diff --git a/src/keychain/index.ts b/src/keychain/index.ts index b95cfb8adf..b2b99126e6 100644 --- a/src/keychain/index.ts +++ b/src/keychain/index.ts @@ -171,7 +171,7 @@ export class KeyChain { /** * Generates the options for a keychain. A random salt is produced. * - * @returns {Object} + * @returns {object} */ static generateOptions (): KeyChainInit { const options = Object.assign({}, defaultOptions) @@ -184,7 +184,7 @@ export class KeyChain { * Gets an object that can encrypt/decrypt protected data. * The default options for a keychain. * - * @returns {Object} + * @returns {object} */ static get options () { return defaultOptions diff --git a/test/addresses/address-manager.spec.ts b/test/addresses/address-manager.spec.ts index a667e6d9fc..55e5326fb2 100644 --- a/test/addresses/address-manager.spec.ts +++ b/test/addresses/address-manager.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import { Multiaddr, protocols } from '@multiformats/multiaddr' import { AddressFilter, DefaultAddressManager } from '../../src/address-manager/index.js' import { createNode } from '../utils/creators/peer.js' diff --git a/test/addresses/addresses.node.ts b/test/addresses/addresses.node.ts index d656dde870..836a0f11fa 100644 --- a/test/addresses/addresses.node.ts +++ b/test/addresses/addresses.node.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import sinon from 'sinon' import { Multiaddr, protocols } from '@multiformats/multiaddr' import { isLoopback } from '@libp2p/utils/multiaddr/is-loopback' diff --git a/test/configuration/protocol-prefix.node.ts b/test/configuration/protocol-prefix.node.ts index 3b9ab32cad..a48bbee1ce 100644 --- a/test/configuration/protocol-prefix.node.ts +++ b/test/configuration/protocol-prefix.node.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import mergeOptions from 'merge-options' import { validateConfig } from '../../src/config.js' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' diff --git a/test/configuration/pubsub.spec.ts b/test/configuration/pubsub.spec.ts index 762a3fcc38..dc20cb5b3e 100644 --- a/test/configuration/pubsub.spec.ts +++ b/test/configuration/pubsub.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import mergeOptions from 'merge-options' import pDefer from 'p-defer' import delay from 'delay' diff --git a/test/connection-manager/auto-dialler.spec.ts b/test/connection-manager/auto-dialler.spec.ts index 0ed22920db..066e535a7c 100644 --- a/test/connection-manager/auto-dialler.spec.ts +++ b/test/connection-manager/auto-dialler.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import { AutoDialler } from '../../src/connection-manager/auto-dialler.js' import pWaitFor from 'p-wait-for' import delay from 'delay' diff --git a/test/connection-manager/index.node.ts b/test/connection-manager/index.node.ts index 6eaacf8767..89d69d7291 100644 --- a/test/connection-manager/index.node.ts +++ b/test/connection-manager/index.node.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import { createNode, createPeerId } from '../utils/creators/peer.js' import { mockConnection, mockDuplex, mockMultiaddrConnection, mockUpgrader } from '@libp2p/interface-compliance-tests/mocks' import { createBaseOptions } from '../utils/base-options.browser.js' diff --git a/test/connection-manager/index.spec.ts b/test/connection-manager/index.spec.ts index 6f6dba9198..85891f7eb9 100644 --- a/test/connection-manager/index.spec.ts +++ b/test/connection-manager/index.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import sinon from 'sinon' import { createNode } from '../utils/creators/peer.js' import { createBaseOptions } from '../utils/base-options.browser.js' diff --git a/test/content-routing/content-routing.node.ts b/test/content-routing/content-routing.node.ts index 25f429dc22..8ced7d6f22 100644 --- a/test/content-routing/content-routing.node.ts +++ b/test/content-routing/content-routing.node.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import nock from 'nock' import sinon from 'sinon' import pDefer from 'p-defer' diff --git a/test/content-routing/dht/configuration.node.ts b/test/content-routing/dht/configuration.node.ts index 57b58c90d4..26f87e62d8 100644 --- a/test/content-routing/dht/configuration.node.ts +++ b/test/content-routing/dht/configuration.node.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import { createLibp2p, Libp2p } from '../../../src/index.js' import { createSubsystemOptions } from './utils.js' diff --git a/test/content-routing/dht/operation.node.ts b/test/content-routing/dht/operation.node.ts index e3fa534b95..7ec3087ed9 100644 --- a/test/content-routing/dht/operation.node.ts +++ b/test/content-routing/dht/operation.node.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import { Multiaddr } from '@multiformats/multiaddr' import pWaitFor from 'p-wait-for' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' diff --git a/test/core/encryption.spec.ts b/test/core/encryption.spec.ts index 6d2ac81703..a2cca6a5d8 100644 --- a/test/core/encryption.spec.ts +++ b/test/core/encryption.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import { WebSockets } from '@libp2p/websockets' import { NOISE } from '@chainsafe/libp2p-noise' import { createLibp2p, Libp2pOptions } from '../../src/index.js' diff --git a/test/core/listening.node.ts b/test/core/listening.node.ts index 04f81c9105..26b0162ec9 100644 --- a/test/core/listening.node.ts +++ b/test/core/listening.node.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import { TCP } from '@libp2p/tcp' import { NOISE } from '@chainsafe/libp2p-noise' import { createPeerId } from '../utils/creators/peer.js' diff --git a/test/core/ping.node.ts b/test/core/ping.node.ts index 45d5d5195e..8b64ffba60 100644 --- a/test/core/ping.node.ts +++ b/test/core/ping.node.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import pTimes from 'p-times' import { pipe } from 'it-pipe' import { createNode, populateAddressBooks } from '../utils/creators/peer.js' diff --git a/test/dialing/dial-request.spec.ts b/test/dialing/dial-request.spec.ts index c41d86368a..c5b56371db 100644 --- a/test/dialing/dial-request.spec.ts +++ b/test/dialing/dial-request.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import sinon from 'sinon' import { AbortError } from '@libp2p/interfaces/errors' import pDefer from 'p-defer' diff --git a/test/dialing/direct.node.ts b/test/dialing/direct.node.ts index e92985004e..a544b76838 100644 --- a/test/dialing/direct.node.ts +++ b/test/dialing/direct.node.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import sinon from 'sinon' import { TCP } from '@libp2p/tcp' import { Mplex } from '@libp2p/mplex' diff --git a/test/dialing/direct.spec.ts b/test/dialing/direct.spec.ts index b6e698da87..fb2f978d57 100644 --- a/test/dialing/direct.spec.ts +++ b/test/dialing/direct.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import sinon from 'sinon' import pDefer from 'p-defer' import delay from 'delay' diff --git a/test/dialing/resolver.spec.ts b/test/dialing/resolver.spec.ts index 9c0fc2707b..1dead4ae7b 100644 --- a/test/dialing/resolver.spec.ts +++ b/test/dialing/resolver.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import sinon from 'sinon' import { Multiaddr } from '@multiformats/multiaddr' import { codes as ErrorCodes } from '../../src/errors.js' diff --git a/test/fetch/fetch.node.ts b/test/fetch/fetch.node.ts index 2da2cd8118..c33b73a796 100644 --- a/test/fetch/fetch.node.ts +++ b/test/fetch/fetch.node.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' import { TCP } from '@libp2p/tcp' import { Mplex } from '@libp2p/mplex' diff --git a/test/identify/index.spec.ts b/test/identify/index.spec.ts index 0297a5aff6..bbe8130695 100644 --- a/test/identify/index.spec.ts +++ b/test/identify/index.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import sinon from 'sinon' import { Multiaddr } from '@multiformats/multiaddr' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' @@ -218,7 +218,7 @@ describe('Identify', () => { signedPeerRecord, observedAddr: connection.remoteAddr.bytes, protocols: [] - }).finish() + }) await pipe( [message], diff --git a/test/insecure/plaintext.spec.ts b/test/insecure/plaintext.spec.ts index d4198dabbe..2ad62faabb 100644 --- a/test/insecure/plaintext.spec.ts +++ b/test/insecure/plaintext.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import sinon from 'sinon' import Peers from '../fixtures/peers.js' import { Plaintext } from '../../src/insecure/index.js' diff --git a/test/interop.ts b/test/interop.ts index e457dc360b..901dc684d6 100644 --- a/test/interop.ts +++ b/test/interop.ts @@ -8,7 +8,7 @@ import { TCP } from '@libp2p/tcp' import { Multiaddr } from '@multiformats/multiaddr' import { KadDHT } from '@libp2p/kad-dht' import { path as p2pd } from 'go-libp2p' -import execa from 'execa' +import { execa } from 'execa' import pDefer from 'p-defer' import { logger } from '@libp2p/logger' import { Mplex } from '@libp2p/mplex' diff --git a/test/keychain/cms-interop.spec.ts b/test/keychain/cms-interop.spec.ts index 32748accf3..5a3494c7ef 100644 --- a/test/keychain/cms-interop.spec.ts +++ b/test/keychain/cms-interop.spec.ts @@ -1,7 +1,7 @@ /* eslint max-nested-callbacks: ["error", 8] */ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { MemoryDatastore } from 'datastore-core/memory' diff --git a/test/keychain/keychain.spec.ts b/test/keychain/keychain.spec.ts index 6a176ea45c..fc7feb796a 100644 --- a/test/keychain/keychain.spec.ts +++ b/test/keychain/keychain.spec.ts @@ -1,7 +1,7 @@ /* eslint max-nested-callbacks: ["error", 8] */ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { createNode } from '../utils/creators/peer.js' diff --git a/test/keychain/peerid.spec.ts b/test/keychain/peerid.spec.ts index 6d5d135419..c776bd901c 100644 --- a/test/keychain/peerid.spec.ts +++ b/test/keychain/peerid.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import { base58btc } from 'multiformats/bases/base58' import { supportedKeys, unmarshalPrivateKey, unmarshalPublicKey } from '@libp2p/crypto/keys' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' diff --git a/test/metrics/index.node.ts b/test/metrics/index.node.ts index 96871db43c..0f382dc158 100644 --- a/test/metrics/index.node.ts +++ b/test/metrics/index.node.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import sinon from 'sinon' import { randomBytes } from '@libp2p/crypto' import { pipe } from 'it-pipe' diff --git a/test/metrics/index.spec.ts b/test/metrics/index.spec.ts index c63fe14dfa..2efe63eee1 100644 --- a/test/metrics/index.spec.ts +++ b/test/metrics/index.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import sinon from 'sinon' import { randomBytes } from '@libp2p/crypto' import { duplexPair } from 'it-pair/duplex' diff --git a/test/nat-manager/nat-manager.node.ts b/test/nat-manager/nat-manager.node.ts index 3a610410ba..440cb29586 100644 --- a/test/nat-manager/nat-manager.node.ts +++ b/test/nat-manager/nat-manager.node.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import { DefaultAddressManager } from '../../src/address-manager/index.js' import { DefaultTransportManager, FaultTolerance } from '../../src/transport-manager.js' import { TCP } from '@libp2p/tcp' diff --git a/test/peer-discovery/index.node.ts b/test/peer-discovery/index.node.ts index 23af1eb79d..7701b47064 100644 --- a/test/peer-discovery/index.node.ts +++ b/test/peer-discovery/index.node.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import sinon from 'sinon' import defer from 'p-defer' import { Bootstrap } from '@libp2p/bootstrap' diff --git a/test/peer-discovery/index.spec.ts b/test/peer-discovery/index.spec.ts index f58fded8e5..a7d7cf824d 100644 --- a/test/peer-discovery/index.spec.ts +++ b/test/peer-discovery/index.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import sinon from 'sinon' import defer from 'p-defer' import { Multiaddr } from '@multiformats/multiaddr' diff --git a/test/peer-routing/peer-routing.node.ts b/test/peer-routing/peer-routing.node.ts index db94d64375..4472c87893 100644 --- a/test/peer-routing/peer-routing.node.ts +++ b/test/peer-routing/peer-routing.node.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import nock from 'nock' import sinon from 'sinon' import intoStream from 'into-stream' diff --git a/test/pnet/index.spec.ts b/test/pnet/index.spec.ts index 1cf1f3c12f..d1105bb7cb 100644 --- a/test/pnet/index.spec.ts +++ b/test/pnet/index.spec.ts @@ -1,5 +1,5 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import { pipe } from 'it-pipe' import all from 'it-all' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' diff --git a/test/registrar/registrar.spec.ts b/test/registrar/registrar.spec.ts index 6ed291b378..25f49c73ab 100644 --- a/test/registrar/registrar.spec.ts +++ b/test/registrar/registrar.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import pDefer from 'p-defer' import { MemoryDatastore } from 'datastore-core/memory' import { createTopology } from '@libp2p/topology' diff --git a/test/relay/auto-relay.node.ts b/test/relay/auto-relay.node.ts index 53e8a1b208..8b3b2a4637 100644 --- a/test/relay/auto-relay.node.ts +++ b/test/relay/auto-relay.node.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import defer from 'p-defer' import pWaitFor from 'p-wait-for' import sinon from 'sinon' diff --git a/test/relay/relay.node.ts b/test/relay/relay.node.ts index 7061f5dba0..aaf82796e5 100644 --- a/test/relay/relay.node.ts +++ b/test/relay/relay.node.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import sinon from 'sinon' import { Multiaddr } from '@multiformats/multiaddr' import { pipe } from 'it-pipe' diff --git a/test/transports/transport-manager.node.ts b/test/transports/transport-manager.node.ts index 0098382ed5..fd931bb9c0 100644 --- a/test/transports/transport-manager.node.ts +++ b/test/transports/transport-manager.node.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import { MemoryDatastore } from 'datastore-core/memory' import { DefaultAddressManager } from '../../src/address-manager/index.js' import { DefaultTransportManager } from '../../src/transport-manager.js' diff --git a/test/transports/transport-manager.spec.ts b/test/transports/transport-manager.spec.ts index 0edb29d3b6..19d0902d2e 100644 --- a/test/transports/transport-manager.spec.ts +++ b/test/transports/transport-manager.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import sinon from 'sinon' import { Multiaddr } from '@multiformats/multiaddr' import { WebSockets } from '@libp2p/websockets' diff --git a/test/upgrading/upgrader.spec.ts b/test/upgrading/upgrader.spec.ts index 6afb6cfc8a..536b2c53bc 100644 --- a/test/upgrading/upgrader.spec.ts +++ b/test/upgrading/upgrader.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import sinon from 'sinon' import { Mplex } from '@libp2p/mplex' import { Multiaddr } from '@multiformats/multiaddr' diff --git a/tsconfig.json b/tsconfig.json index 634994043c..fe4fd056b1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,7 @@ { "extends": "aegir/src/config/tsconfig.aegir.json", "compilerOptions": { - "outDir": "dist", - "emitDeclarationOnly": false, - "module": "ES2020", - "lib": ["ES2021", "ES2021.Promise", "ES2021.String", "ES2020.BigInt", "DOM", "DOM.Iterable"] + "outDir": "dist" }, "include": [ "src", From 1b9bab68ed3d2473541a1f487eee10291bc3c9f2 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 14 Apr 2022 08:05:43 +0100 Subject: [PATCH 353/447] chore: update deps (#1190) --- package.json | 4 ++-- src/metrics/stats.ts | 7 +------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 58efc772a7..ee96cd9833 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "@achingbrain/nat-port-mapper": "^1.0.0", "@libp2p/connection": "^1.1.4", "@libp2p/crypto": "^0.22.9", - "@libp2p/interfaces": "^1.3.17", + "@libp2p/interfaces": "^1.3.21", "@libp2p/logger": "^1.1.3", "@libp2p/multistream-select": "^1.0.3", "@libp2p/peer-id": "^1.1.8", @@ -164,7 +164,7 @@ "@libp2p/floodsub": "^1.0.2", "@libp2p/interface-compliance-tests": "^1.1.20", "@libp2p/interop": "^1.0.3", - "@libp2p/kad-dht": "^1.0.3", + "@libp2p/kad-dht": "^1.0.5", "@libp2p/mdns": "^1.0.3", "@libp2p/mplex": "^1.0.1", "@libp2p/pubsub": "^1.2.14", diff --git a/src/metrics/stats.ts b/src/metrics/stats.ts index 56b3d4ca79..9806ab61ec 100644 --- a/src/metrics/stats.ts +++ b/src/metrics/stats.ts @@ -2,7 +2,7 @@ import { CustomEvent, EventEmitter } from '@libp2p/interfaces' import { createMovingAverage } from './moving-average.js' // @ts-expect-error no types import retimer from 'retimer' -import type { MovingAverages, Stats } from '@libp2p/interfaces/metrics' +import type { MovingAverages, Stats, TransferStats } from '@libp2p/interfaces/metrics' export interface StatsEvents { 'update': CustomEvent @@ -16,11 +16,6 @@ export interface StatsInit { computeThrottleTimeout: number } -export interface TransferStats { - dataReceived: BigInt - dataSent: BigInt -} - export class DefaultStats extends EventEmitter implements Stats { private readonly enabled: boolean public queue: Array<[string, number, number]> From 147304449e5f8d3acb8b00bdd9588b56830667c6 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 14 Apr 2022 18:00:21 +0100 Subject: [PATCH 354/447] fix: expose getPublicKey (#1188) This is used externally by IPFS so expose the method --- src/dialer/index.ts | 3 -- src/index.ts | 6 +++ src/libp2p.ts | 8 +++- test/core/get-public-key.spec.ts | 78 ++++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 test/core/get-public-key.spec.ts diff --git a/src/dialer/index.ts b/src/dialer/index.ts index 30dc731e03..a0352fd69d 100644 --- a/src/dialer/index.ts +++ b/src/dialer/index.ts @@ -244,9 +244,6 @@ export class DefaultDialer implements Dialer, Startable { const addrs: Multiaddr[] = [] for (const a of knownAddrs) { const resolvedAddrs = await this._resolve(a) - - log('resolved %s to %s', a, resolvedAddrs) - resolvedAddrs.forEach(ra => addrs.push(ra)) } diff --git a/src/index.ts b/src/index.ts index d64bb22480..7196ba3fa2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -223,6 +223,12 @@ export interface Libp2p extends Startable, EventEmitter { * Sends a request to fetch the value associated with the given key from the given peer. */ fetch: (peer: PeerId | Multiaddr | string, key: string) => Promise + + /** + * Returns the public key for the passed PeerId. If the PeerId is of the 'RSA' type + * this may mean searching the DHT if the key is not present in the KeyStore. + */ + getPublicKey: (peer: PeerId, options?: AbortOptions) => Promise } export type Libp2pOptions = RecursivePartial diff --git a/src/libp2p.ts b/src/libp2p.ts index 62f412bf20..c26dc0acca 100644 --- a/src/libp2p.ts +++ b/src/libp2p.ts @@ -412,9 +412,13 @@ export class Libp2pNode extends EventEmitter implements Libp2p { /** * Get the public key for the given peer id */ - async getPublicKey (peer: PeerId, options: AbortOptions = {}) { + async getPublicKey (peer: PeerId, options: AbortOptions = {}): Promise { log('getPublicKey %p', peer) + if (peer.publicKey != null) { + return peer.publicKey + } + const peerInfo = await this.peerStore.get(peer) if (peerInfo.pubKey != null) { @@ -437,7 +441,7 @@ export class Libp2pNode extends EventEmitter implements Libp2p { await this.peerStore.keyBook.set(peer, event.value) - return key + return key.bytes } } diff --git a/test/core/get-public-key.spec.ts b/test/core/get-public-key.spec.ts new file mode 100644 index 0000000000..c72f695dd4 --- /dev/null +++ b/test/core/get-public-key.spec.ts @@ -0,0 +1,78 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/chai' +import { WebSockets } from '@libp2p/websockets' +import { NOISE } from '@chainsafe/libp2p-noise' +import { createPeerId } from '../utils/creators/peer.js' +import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' +import type { Libp2pOptions } from '../../src/index.js' +import sinon from 'sinon' +import { KadDHT } from '@libp2p/kad-dht' + +describe('getPublicKey', () => { + let libp2p: Libp2pNode + + beforeEach(async () => { + const peerId = await createPeerId() + const config: Libp2pOptions = { + peerId, + transports: [ + new WebSockets() + ], + connectionEncryption: [ + NOISE + ], + dht: new KadDHT() + } + libp2p = await createLibp2pNode(config) + + await libp2p.start() + }) + + afterEach(async () => { + await libp2p.stop() + }) + + it('should extract embedded public key', async () => { + const otherPeer = await createPeerId() + + const key = await libp2p.getPublicKey(otherPeer) + + expect(otherPeer.publicKey).to.equalBytes(key) + }) + + it('should get key from the keystore', async () => { + const otherPeer = await createPeerId({ opts: { type: 'rsa' } }) + + if (otherPeer.publicKey == null) { + throw new Error('Public key was missing') + } + + await libp2p.peerStore.keyBook.set(otherPeer, otherPeer.publicKey) + + const key = await libp2p.getPublicKey(otherPeer) + + expect(otherPeer.publicKey).to.equalBytes(key) + }) + + it('should query the DHT when the key is not in the keystore', async () => { + const otherPeer = await createPeerId({ opts: { type: 'rsa' } }) + + if (otherPeer.publicKey == null) { + throw new Error('Public key was missing') + } + + if (libp2p.dht == null) { + throw new Error('DHT was not configured') + } + + libp2p.dht.get = sinon.stub().returns([{ + name: 'VALUE', + value: otherPeer.publicKey + }]) + + const key = await libp2p.getPublicKey(otherPeer) + + expect(otherPeer.publicKey).to.equalBytes(key) + }) +}) From 5397137c654dfdec431e0c9ba4b1ff9dee19abf1 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 21 Apr 2022 15:46:06 +0100 Subject: [PATCH 355/447] fix: use placeholder dht/pubsub (#1193) Instead of making the `.dht` and `.pubsub` properties optional, use dummy implementations that throw exceptions if they are not configured. This way we don't have to null guard everywhere they are accessed. --- examples/discovery-mechanisms/3.js | 6 +-- examples/package.json | 2 +- examples/pubsub/1.js | 17 ++++--- examples/pubsub/message-filtering/1.js | 9 ++-- package.json | 47 +++++++++-------- src/dht/dummy-dht.ts | 51 +++++++++++++++++++ src/errors.ts | 2 + src/index.ts | 5 +- src/libp2p.ts | 14 +++-- src/pubsub/dummy-pubsub.ts | 51 +++++++++++++++++++ test/configuration/pubsub.spec.ts | 31 ++++++----- .../content-routing/dht/configuration.node.ts | 10 ++-- test/content-routing/dht/operation.node.ts | 16 +++--- test/interop.ts | 3 +- 14 files changed, 186 insertions(+), 78 deletions(-) create mode 100644 src/dht/dummy-dht.ts create mode 100644 src/pubsub/dummy-pubsub.ts diff --git a/examples/discovery-mechanisms/3.js b/examples/discovery-mechanisms/3.js index 5cf6522bf8..c12d5f73ae 100644 --- a/examples/discovery-mechanisms/3.js +++ b/examples/discovery-mechanisms/3.js @@ -4,7 +4,7 @@ import { createLibp2p } from 'libp2p' import { TCP } from '@libp2p/tcp' import { Mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' -import { Gossipsub } from '@achingbrain/libp2p-gossipsub' +import { FloodSub } from '@libp2p/floodsub' import { Bootstrap } from '@libp2p/bootstrap' import { PubSubPeerDiscovery } from '@libp2p/pubsub-peer-discovery' @@ -16,7 +16,7 @@ const createNode = async (bootstrappers) => { transports: [new TCP()], streamMuxers: [new Mplex()], connectionEncryption: [new Noise()], - pubsub: new Gossipsub(), + pubsub: new FloodSub(), peerDiscovery: [ new Bootstrap({ list: bootstrappers @@ -40,7 +40,7 @@ const createNode = async (bootstrappers) => { transports: [new TCP()], streamMuxers: [new Mplex()], connectionEncryption: [new Noise()], - pubsub: new Gossipsub(), + pubsub: new FloodSub(), peerDiscovery: [ new PubSubPeerDiscovery({ interval: 1000 diff --git a/examples/package.json b/examples/package.json index 596714c06a..3563775104 100644 --- a/examples/package.json +++ b/examples/package.json @@ -9,8 +9,8 @@ }, "license": "MIT", "dependencies": { - "@achingbrain/libp2p-gossipsub": "^0.13.5", "@libp2p/pubsub-peer-discovery": "^5.0.1", + "@libp2p/floodsub": "^1.0.5", "execa": "^2.1.0", "fs-extra": "^8.1.0", "libp2p": "../", diff --git a/examples/pubsub/1.js b/examples/pubsub/1.js index 53d7739e9e..1541711292 100644 --- a/examples/pubsub/1.js +++ b/examples/pubsub/1.js @@ -4,10 +4,9 @@ import { createLibp2p } from 'libp2p' import { TCP } from '@libp2p/tcp' import { Mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' -import { Gossipsub } from '@achingbrain/libp2p-gossipsub' +import { FloodSub } from '@libp2p/floodsub' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' -import { CustomEvent } from '@libp2p/interfaces' const createNode = async () => { const node = await createLibp2p({ @@ -17,7 +16,7 @@ const createNode = async () => { transports: [new TCP()], streamMuxers: [new Mplex()], connectionEncryption: [new Noise()], - pubsub: new Gossipsub() + pubsub: new FloodSub() }) await node.start() @@ -36,17 +35,19 @@ const createNode = async () => { await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs()) await node1.dial(node2.peerId) - node1.pubsub.addEventListener(topic, (evt) => { - console.log(`node1 received: ${uint8ArrayToString(evt.detail.data)}`) + node1.pubsub.subscribe(topic) + node1.pubsub.addEventListener('message', (evt) => { + console.log(`node1 received: ${uint8ArrayToString(evt.detail.data)} on topic ${evt.detail.topic}`) }) // Will not receive own published messages by default - node2.pubsub.addEventListener(topic, (evt) => { - console.log(`node2 received: ${uint8ArrayToString(evt.detail.data)}`) + node2.pubsub.subscribe(topic) + node2.pubsub.addEventListener('message', (evt) => { + console.log(`node2 received: ${uint8ArrayToString(evt.detail.data)} on topic ${evt.detail.topic}`) }) // node2 publishes "news" every second setInterval(() => { - node2.pubsub.dispatchEvent(new CustomEvent(topic, { detail: uint8ArrayFromString('Bird bird bird, bird is the word!') })) + node2.pubsub.publish(topic, uint8ArrayFromString('Bird bird bird, bird is the word!')) }, 1000) })() diff --git a/examples/pubsub/message-filtering/1.js b/examples/pubsub/message-filtering/1.js index 32665c1287..efe1d835eb 100644 --- a/examples/pubsub/message-filtering/1.js +++ b/examples/pubsub/message-filtering/1.js @@ -4,10 +4,9 @@ import { createLibp2p } from 'libp2p' import { TCP } from '@libp2p/tcp' import { Mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' -import { Gossipsub } from '@achingbrain/libp2p-gossipsub' +import { FloodSub } from '@libp2p/floodsub' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' -import { CustomEvent } from '@libp2p/interfaces' const createNode = async () => { const node = await createLibp2p({ @@ -17,7 +16,7 @@ const createNode = async () => { transports: [new TCP()], streamMuxers: [new Mplex()], connectionEncryption: [new Noise()], - pubsub: new Gossipsub() + pubsub: new FloodSub() }) await node.start() @@ -45,7 +44,7 @@ const createNode = async () => { // Will not receive own published messages by default console.log(`node1 received: ${uint8ArrayToString(evt.detail.data)}`) }) - await node1.pubsub.subscribe(topic) + node1.pubsub.subscribe(topic) node2.pubsub.addEventListener(topic, (evt) => { console.log(`node2 received: ${uint8ArrayToString(evt.detail.data)}`) @@ -75,7 +74,7 @@ const createNode = async () => { // car is not a fruit ! setInterval(() => { console.log('############## fruit ' + myFruits[count] + ' ##############') - node1.pubsub.dispatchEvent(new CustomEvent(topic, { detail: uint8ArrayFromString(myFruits[count]) })) + node1.pubsub.publish(topic, uint8ArrayFromString(myFruits[count])) count++ if (count == myFruits.length) { count = 0 diff --git a/package.json b/package.json index ee96cd9833..73cd91684e 100644 --- a/package.json +++ b/package.json @@ -96,16 +96,17 @@ }, "dependencies": { "@achingbrain/nat-port-mapper": "^1.0.0", - "@libp2p/connection": "^1.1.4", - "@libp2p/crypto": "^0.22.9", - "@libp2p/interfaces": "^1.3.21", - "@libp2p/logger": "^1.1.3", - "@libp2p/multistream-select": "^1.0.3", - "@libp2p/peer-id": "^1.1.8", - "@libp2p/peer-id-factory": "^1.0.8", + "@libp2p/connection": "^1.1.5", + "@libp2p/crypto": "^0.22.11", + "@libp2p/interfaces": "^1.3.22", + "@libp2p/logger": "^1.1.4", + "@libp2p/multistream-select": "^1.0.4", + "@libp2p/peer-id": "^1.1.10", + "@libp2p/peer-id-factory": "^1.0.9", "@libp2p/peer-record": "^1.0.8", - "@libp2p/peer-store": "^1.0.6", - "@libp2p/utils": "^1.0.9", + "@libp2p/peer-store": "^1.0.10", + "@libp2p/tracked-map": "^1.0.5", + "@libp2p/utils": "^1.0.10", "@multiformats/mafmt": "^11.0.2", "@multiformats/multiaddr": "^10.1.8", "abortable-iterator": "^4.0.2", @@ -128,6 +129,7 @@ "it-length-prefixed": "^7.0.1", "it-map": "^1.0.6", "it-merge": "^1.0.3", + "it-pair": "^2.0.2", "it-pipe": "^2.0.3", "it-sort": "^1.0.1", "it-stream-types": "^1.0.4", @@ -154,25 +156,23 @@ "xsalsa20": "^1.1.0" }, "devDependencies": { - "@achingbrain/libp2p-gossipsub": "^0.13.5", "@chainsafe/libp2p-noise": "^6.0.1", - "@libp2p/bootstrap": "^1.0.2", - "@libp2p/daemon-client": "^1.0.0", - "@libp2p/daemon-server": "^1.0.0", + "@libp2p/bootstrap": "^1.0.3", + "@libp2p/daemon-client": "^1.0.2", + "@libp2p/daemon-server": "^1.0.2", "@libp2p/delegated-content-routing": "^1.0.2", "@libp2p/delegated-peer-routing": "^1.0.2", - "@libp2p/floodsub": "^1.0.2", - "@libp2p/interface-compliance-tests": "^1.1.20", + "@libp2p/floodsub": "^1.0.5", + "@libp2p/interface-compliance-tests": "^1.1.23", "@libp2p/interop": "^1.0.3", - "@libp2p/kad-dht": "^1.0.5", - "@libp2p/mdns": "^1.0.3", - "@libp2p/mplex": "^1.0.1", - "@libp2p/pubsub": "^1.2.14", - "@libp2p/tcp": "^1.0.6", + "@libp2p/kad-dht": "^1.0.7", + "@libp2p/mdns": "^1.0.4", + "@libp2p/mplex": "^1.0.3", + "@libp2p/pubsub": "^1.2.18", + "@libp2p/tcp": "^1.0.8", "@libp2p/topology": "^1.1.7", - "@libp2p/tracked-map": "^1.0.4", - "@libp2p/webrtc-star": "^1.0.3", - "@libp2p/websockets": "^1.0.3", + "@libp2p/webrtc-star": "^1.0.7", + "@libp2p/websockets": "^1.0.6", "@nodeutils/defaults-deep": "^1.1.0", "@types/node": "^16.11.26", "@types/node-forge": "^1.0.0", @@ -187,7 +187,6 @@ "go-libp2p": "^0.0.6", "into-stream": "^7.0.0", "ipfs-http-client": "^56.0.1", - "it-pair": "^2.0.2", "it-pushable": "^2.0.1", "nock": "^13.0.3", "npm-run-all": "^4.1.5", diff --git a/src/dht/dummy-dht.ts b/src/dht/dummy-dht.ts new file mode 100644 index 0000000000..77a9c4a3df --- /dev/null +++ b/src/dht/dummy-dht.ts @@ -0,0 +1,51 @@ +import type { DualDHT, QueryEvent, SingleDHT } from '@libp2p/interfaces/dht' +import type { PeerDiscoveryEvents } from '@libp2p/interfaces/peer-discovery' +import errCode from 'err-code' +import { messages, codes } from '../errors.js' +import { EventEmitter } from '@libp2p/interfaces' + +export class DummyDHT extends EventEmitter implements DualDHT { + get wan (): SingleDHT { + throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED) + } + + get lan (): SingleDHT { + throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED) + } + + get (): AsyncIterable { + throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED) + } + + findProviders (): AsyncIterable { + throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED) + } + + findPeer (): AsyncIterable { + throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED) + } + + getClosestPeers (): AsyncIterable { + throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED) + } + + provide (): AsyncIterable { + throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED) + } + + put (): AsyncIterable { + throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED) + } + + async getMode (): Promise<'client' | 'server'> { + throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED) + } + + async setMode (): Promise { + throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED) + } + + async refreshRoutingTable (): Promise { + throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED) + } +} diff --git a/src/errors.ts b/src/errors.ts index 8b1eff04f6..b5048e65d3 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,6 +1,7 @@ export enum messages { NOT_STARTED_YET = 'The libp2p node is not started yet', DHT_DISABLED = 'DHT is not available', + PUBSUB_DISABLED = 'PubSub is not available', CONN_ENCRYPTION_REQUIRED = 'At least one connection encryption module is required', ERR_TRANSPORTS_REQUIRED = 'At least one transport module is required', ERR_PROTECTOR_REQUIRED = 'Private network is enforced, but no protector was provided', @@ -9,6 +10,7 @@ export enum messages { export enum codes { DHT_DISABLED = 'ERR_DHT_DISABLED', + ERR_PUBSUB_DISABLED = 'ERR_PUBSUB_DISABLED', PUBSUB_NOT_STARTED = 'ERR_PUBSUB_NOT_STARTED', DHT_NOT_STARTED = 'ERR_DHT_NOT_STARTED', CONN_ENCRYPTION_REQUIRED = 'ERR_CONN_ENCRYPTION_REQUIRED', diff --git a/src/index.ts b/src/index.ts index 7196ba3fa2..0bc4eb1739 100644 --- a/src/index.ts +++ b/src/index.ts @@ -154,9 +154,8 @@ export interface Libp2p extends Startable, EventEmitter { connectionManager: ConnectionManager registrar: Registrar metrics?: Metrics - - pubsub?: PubSub - dht?: DualDHT + pubsub: PubSub + dht: DualDHT /** * Load keychain keys from the datastore. diff --git a/src/libp2p.ts b/src/libp2p.ts index c26dc0acca..cca67615ed 100644 --- a/src/libp2p.ts +++ b/src/libp2p.ts @@ -44,13 +44,15 @@ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import errCode from 'err-code' import { unmarshalPublicKey } from '@libp2p/crypto/keys' import type { Metrics } from '@libp2p/interfaces/metrics' +import { DummyDHT } from './dht/dummy-dht.js' +import { DummyPubSub } from './pubsub/dummy-pubsub.js' const log = logger('libp2p') export class Libp2pNode extends EventEmitter implements Libp2p { public peerId: PeerId - public dht?: DualDHT - public pubsub?: PubSub + public dht: DualDHT + public pubsub: PubSub public identifyService?: IdentifyService public fetchService: FetchService public pingService: PingService @@ -168,11 +170,15 @@ export class Libp2pNode extends EventEmitter implements Libp2p { // dht provided components (peerRouting, contentRouting, dht) if (init.dht != null) { this.dht = this.components.setDHT(this.configureComponent(init.dht)) + } else { + this.dht = new DummyDHT() } // Create pubsub if provided if (init.pubsub != null) { this.pubsub = this.components.setPubSub(this.configureComponent(init.pubsub)) + } else { + this.pubsub = new DummyPubSub() } // Attach remaining APIs @@ -180,7 +186,7 @@ export class Libp2pNode extends EventEmitter implements Libp2p { const peerRouters: PeerRouting[] = (init.peerRouters ?? []).map(component => this.configureComponent(component)) - if (this.dht != null) { + if (init.dht != null) { // add dht to routers peerRouters.push(this.configureComponent(new DHTPeerRouting(this.dht))) @@ -197,7 +203,7 @@ export class Libp2pNode extends EventEmitter implements Libp2p { const contentRouters: ContentRouting[] = (init.contentRouters ?? []).map(component => this.configureComponent(component)) - if (this.dht != null) { + if (init.dht != null) { // add dht to routers contentRouters.push(this.configureComponent(new DHTContentRouting(this.dht))) } diff --git a/src/pubsub/dummy-pubsub.ts b/src/pubsub/dummy-pubsub.ts new file mode 100644 index 0000000000..82014760f4 --- /dev/null +++ b/src/pubsub/dummy-pubsub.ts @@ -0,0 +1,51 @@ +import { EventEmitter } from '@libp2p/interfaces' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PubSub, PubSubEvents, StrictNoSign, StrictSign } from '@libp2p/interfaces/pubsub' +import errCode from 'err-code' +import { messages, codes } from '../errors.js' + +export class DummyPubSub extends EventEmitter implements PubSub { + isStarted (): boolean { + return false + } + + start (): void | Promise { + + } + + stop (): void | Promise { + + } + + get globalSignaturePolicy (): typeof StrictSign | typeof StrictNoSign { + throw errCode(new Error(messages.PUBSUB_DISABLED), codes.ERR_PUBSUB_DISABLED) + } + + get multicodecs (): string[] { + throw errCode(new Error(messages.PUBSUB_DISABLED), codes.ERR_PUBSUB_DISABLED) + } + + getPeers (): PeerId[] { + throw errCode(new Error(messages.PUBSUB_DISABLED), codes.ERR_PUBSUB_DISABLED) + } + + getTopics (): string[] { + throw errCode(new Error(messages.PUBSUB_DISABLED), codes.ERR_PUBSUB_DISABLED) + } + + subscribe (): void { + throw errCode(new Error(messages.PUBSUB_DISABLED), codes.ERR_PUBSUB_DISABLED) + } + + unsubscribe (): void { + throw errCode(new Error(messages.PUBSUB_DISABLED), codes.ERR_PUBSUB_DISABLED) + } + + getSubscribers (): PeerId[] { + throw errCode(new Error(messages.PUBSUB_DISABLED), codes.ERR_PUBSUB_DISABLED) + } + + publish (): void { + throw errCode(new Error(messages.PUBSUB_DISABLED), codes.ERR_PUBSUB_DISABLED) + } +} diff --git a/test/configuration/pubsub.spec.ts b/test/configuration/pubsub.spec.ts index dc20cb5b3e..6d0c6e68d2 100644 --- a/test/configuration/pubsub.spec.ts +++ b/test/configuration/pubsub.spec.ts @@ -7,7 +7,6 @@ import delay from 'delay' import { createLibp2p, Libp2p } from '../../src/index.js' import { baseOptions, pubsubSubsystemOptions } from './utils.js' import { createPeerId } from '../utils/creators/peer.js' -import { CustomEvent } from '@libp2p/interfaces' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { FloodSub } from '@libp2p/floodsub' import type { PubSub } from '@libp2p/interfaces/pubsub' @@ -21,14 +20,16 @@ describe('Pubsub subsystem is configurable', () => { } }) - it('should not exist if no module is provided', async () => { + it('should throw if no module is provided', async () => { libp2p = await createLibp2p(baseOptions) - expect(libp2p.pubsub).to.not.exist() + await libp2p.start() + expect(() => libp2p.pubsub.getTopics()).to.throw() }) - it('should exist if the module is provided', async () => { + it('should not throw if the module is provided', async () => { libp2p = await createLibp2p(pubsubSubsystemOptions) - expect(libp2p.pubsub).to.exist() + await libp2p.start() + expect(libp2p.pubsub.getTopics()).to.be.empty() }) it('should start and stop by default once libp2p starts', async () => { @@ -39,13 +40,13 @@ describe('Pubsub subsystem is configurable', () => { }) libp2p = await createLibp2p(customOptions) - expect(libp2p.pubsub?.isStarted()).to.equal(false) + expect(libp2p.pubsub.isStarted()).to.equal(false) await libp2p.start() - expect(libp2p.pubsub?.isStarted()).to.equal(true) + expect(libp2p.pubsub.isStarted()).to.equal(true) await libp2p.stop() - expect(libp2p.pubsub?.isStarted()).to.equal(false) + expect(libp2p.pubsub.isStarted()).to.equal(false) }) }) @@ -87,16 +88,14 @@ describe('Pubsub subscription handlers adapter', () => { throw new Error('Pubsub was not enabled') } - pubsub.addEventListener(topic, handler) - pubsub.dispatchEvent(new CustomEvent(topic, { - detail: uint8ArrayFromString('useless-data') - })) + pubsub.subscribe(topic) + pubsub.addEventListener('message', handler) + pubsub.publish(topic, uint8ArrayFromString('useless-data')) await defer.promise - pubsub.removeEventListener(topic, handler) - pubsub.dispatchEvent(new CustomEvent(topic, { - detail: uint8ArrayFromString('useless-data') - })) + pubsub.unsubscribe(topic) + pubsub.removeEventListener('message', handler) + pubsub.publish(topic, uint8ArrayFromString('useless-data')) // wait to guarantee that the handler is not called twice await delay(100) diff --git a/test/content-routing/dht/configuration.node.ts b/test/content-routing/dht/configuration.node.ts index 26f87e62d8..5e3ed5ebe1 100644 --- a/test/content-routing/dht/configuration.node.ts +++ b/test/content-routing/dht/configuration.node.ts @@ -13,15 +13,17 @@ describe('DHT subsystem is configurable', () => { } }) - it('should not exist if no module is provided', async () => { + it('should throw if no module is provided', async () => { libp2p = await createLibp2p(createSubsystemOptions({ dht: undefined })) - expect(libp2p.dht).to.not.exist() + await libp2p.start() + await expect(libp2p.dht.getMode()).to.eventually.be.rejected() }) - it('should exist if the module is provided', async () => { + it('should not throw if the module is provided', async () => { libp2p = await createLibp2p(createSubsystemOptions()) - expect(libp2p.dht).to.exist() + await libp2p.start() + await expect(libp2p.dht.getMode()).to.eventually.equal('client') }) }) diff --git a/test/content-routing/dht/operation.node.ts b/test/content-routing/dht/operation.node.ts index 7ec3087ed9..f3fed7d2b7 100644 --- a/test/content-routing/dht/operation.node.ts +++ b/test/content-routing/dht/operation.node.ts @@ -78,8 +78,8 @@ describe('DHT subsystem operates correctly', () => { expect(connection).to.exist() return await Promise.all([ - pWaitFor(() => libp2p.dht?.lan.routingTable.size === 1), - pWaitFor(() => remoteLibp2p.dht?.lan.routingTable.size === 1) + pWaitFor(() => libp2p.dht.lan.routingTable.size === 1), + pWaitFor(() => remoteLibp2p.dht.lan.routingTable.size === 1) ]) }) @@ -89,8 +89,8 @@ describe('DHT subsystem operates correctly', () => { await libp2p.dialProtocol(remAddr, subsystemMulticodecs) await Promise.all([ - pWaitFor(() => libp2p.dht?.lan.routingTable.size === 1), - pWaitFor(() => remoteLibp2p.dht?.lan.routingTable.size === 1) + pWaitFor(() => libp2p.dht.lan.routingTable.size === 1), + pWaitFor(() => remoteLibp2p.dht.lan.routingTable.size === 1) ]) await libp2p.components.getContentRouting().put(key, value) @@ -141,7 +141,7 @@ describe('DHT subsystem operates correctly', () => { const connection = await libp2p.dial(remAddr) expect(connection).to.exist() - expect(libp2p.dht?.lan.routingTable).to.be.empty() + expect(libp2p.dht.lan.routingTable).to.be.empty() const dht = remoteLibp2p.dht @@ -151,9 +151,9 @@ describe('DHT subsystem operates correctly', () => { // should be 0 directly after start - TODO this may be susceptible to timing bugs, we should have // the ability to report stats on the DHT routing table instead of reaching into it's heart like this - expect(remoteLibp2p.dht?.lan.routingTable).to.be.empty() + expect(remoteLibp2p.dht.lan.routingTable).to.be.empty() - return await pWaitFor(() => libp2p.dht?.lan.routingTable.size === 1) + return await pWaitFor(() => libp2p.dht.lan.routingTable.size === 1) }) it('should put on a peer and get from the other', async () => { @@ -168,7 +168,7 @@ describe('DHT subsystem operates correctly', () => { await dht.start() } - await pWaitFor(() => libp2p.dht?.lan.routingTable.size === 1) + await pWaitFor(() => libp2p.dht.lan.routingTable.size === 1) await libp2p.components.getContentRouting().put(key, value) const fetchedValue = await remoteLibp2p.components.getContentRouting().get(key) diff --git a/test/interop.ts b/test/interop.ts index 901dc684d6..511b6a07d8 100644 --- a/test/interop.ts +++ b/test/interop.ts @@ -17,7 +17,6 @@ import { unmarshalPrivateKey } from '@libp2p/crypto/keys' import type { PeerId } from '@libp2p/interfaces/peer-id' import { peerIdFromKeys } from '@libp2p/peer-id' import { FloodSub } from '@libp2p/floodsub' -import { Gossipsub } from '@achingbrain/libp2p-gossipsub' // IPFS_LOGGING=debug DEBUG=libp2p*,go-libp2p:* npm run test:interop @@ -122,7 +121,7 @@ async function createJsPeer (options: SpawnOptions): Promise { if (options.pubsubRouter === 'floodsub') { opts.pubsub = new FloodSub() } else { - opts.pubsub = new Gossipsub() + opts.pubsub = new FloodSub() } } From fab4f1385cf61b7b16719b9aacdfe03146a3f260 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 22 Apr 2022 20:49:35 +0100 Subject: [PATCH 356/447] fix: update pubsub interfaces (#1194) Update to latest version of pubsub interface --- examples/package.json | 2 +- examples/pubsub/1.js | 4 ++- examples/pubsub/README.md | 4 ++- examples/pubsub/message-filtering/1.js | 4 ++- examples/pubsub/message-filtering/README.md | 4 ++- package.json | 6 ++-- src/pubsub/dummy-pubsub.ts | 4 +-- test/configuration/pubsub.spec.ts | 4 +-- test/configuration/utils.ts | 10 ++++-- test/identify/index.spec.ts | 40 +++------------------ 10 files changed, 32 insertions(+), 50 deletions(-) diff --git a/examples/package.json b/examples/package.json index 3563775104..c94da14d0b 100644 --- a/examples/package.json +++ b/examples/package.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@libp2p/pubsub-peer-discovery": "^5.0.1", - "@libp2p/floodsub": "^1.0.5", + "@libp2p/floodsub": "^1.0.6", "execa": "^2.1.0", "fs-extra": "^8.1.0", "libp2p": "../", diff --git a/examples/pubsub/1.js b/examples/pubsub/1.js index 1541711292..bb89977e2a 100644 --- a/examples/pubsub/1.js +++ b/examples/pubsub/1.js @@ -48,6 +48,8 @@ const createNode = async () => { // node2 publishes "news" every second setInterval(() => { - node2.pubsub.publish(topic, uint8ArrayFromString('Bird bird bird, bird is the word!')) + node2.pubsub.publish(topic, uint8ArrayFromString('Bird bird bird, bird is the word!')).catch(err => { + console.error(err) + }) }, 1000) })() diff --git a/examples/pubsub/README.md b/examples/pubsub/README.md index 9dec1f973c..0913e1fde0 100644 --- a/examples/pubsub/README.md +++ b/examples/pubsub/README.md @@ -69,7 +69,9 @@ await node2.pubsub.subscribe(topic) // node2 publishes "news" every second setInterval(() => { - node2.pubsub.publish(topic, fromString('Bird bird bird, bird is the word!')) + node2.pubsub.publish(topic, fromString('Bird bird bird, bird is the word!')).catch(err => { + console.error(err) + }) }, 1000) ``` diff --git a/examples/pubsub/message-filtering/1.js b/examples/pubsub/message-filtering/1.js index efe1d835eb..f83ecc98a5 100644 --- a/examples/pubsub/message-filtering/1.js +++ b/examples/pubsub/message-filtering/1.js @@ -74,7 +74,9 @@ const createNode = async () => { // car is not a fruit ! setInterval(() => { console.log('############## fruit ' + myFruits[count] + ' ##############') - node1.pubsub.publish(topic, uint8ArrayFromString(myFruits[count])) + node1.pubsub.publish(topic, uint8ArrayFromString(myFruits[count])).catch(err => { + console.info(err) + }) count++ if (count == myFruits.length) { count = 0 diff --git a/examples/pubsub/message-filtering/README.md b/examples/pubsub/message-filtering/README.md index 9eecb87313..99cec8aa7d 100644 --- a/examples/pubsub/message-filtering/README.md +++ b/examples/pubsub/message-filtering/README.md @@ -88,7 +88,9 @@ const myFruits = ['banana', 'apple', 'car', 'orange']; setInterval(() => { console.log('############## fruit ' + myFruits[count] + ' ##############') - node1.pubsub.publish(topic, new TextEncoder().encode(myFruits[count])) + node1.pubsub.publish(topic, new TextEncoder().encode(myFruits[count])).catch(err => { + console.error(err) + }) count++ if (count == myFruits.length) { count = 0 diff --git a/package.json b/package.json index 73cd91684e..788201202d 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "@achingbrain/nat-port-mapper": "^1.0.0", "@libp2p/connection": "^1.1.5", "@libp2p/crypto": "^0.22.11", - "@libp2p/interfaces": "^1.3.22", + "@libp2p/interfaces": "^1.3.24", "@libp2p/logger": "^1.1.4", "@libp2p/multistream-select": "^1.0.4", "@libp2p/peer-id": "^1.1.10", @@ -162,8 +162,8 @@ "@libp2p/daemon-server": "^1.0.2", "@libp2p/delegated-content-routing": "^1.0.2", "@libp2p/delegated-peer-routing": "^1.0.2", - "@libp2p/floodsub": "^1.0.5", - "@libp2p/interface-compliance-tests": "^1.1.23", + "@libp2p/floodsub": "^1.0.6", + "@libp2p/interface-compliance-tests": "^1.1.25", "@libp2p/interop": "^1.0.3", "@libp2p/kad-dht": "^1.0.7", "@libp2p/mdns": "^1.0.4", diff --git a/src/pubsub/dummy-pubsub.ts b/src/pubsub/dummy-pubsub.ts index 82014760f4..15fe053679 100644 --- a/src/pubsub/dummy-pubsub.ts +++ b/src/pubsub/dummy-pubsub.ts @@ -1,6 +1,6 @@ import { EventEmitter } from '@libp2p/interfaces' import type { PeerId } from '@libp2p/interfaces/peer-id' -import type { PubSub, PubSubEvents, StrictNoSign, StrictSign } from '@libp2p/interfaces/pubsub' +import type { PublishResult, PubSub, PubSubEvents, StrictNoSign, StrictSign } from '@libp2p/interfaces/pubsub' import errCode from 'err-code' import { messages, codes } from '../errors.js' @@ -45,7 +45,7 @@ export class DummyPubSub extends EventEmitter implements PubSub { throw errCode(new Error(messages.PUBSUB_DISABLED), codes.ERR_PUBSUB_DISABLED) } - publish (): void { + async publish (): Promise { throw errCode(new Error(messages.PUBSUB_DISABLED), codes.ERR_PUBSUB_DISABLED) } } diff --git a/test/configuration/pubsub.spec.ts b/test/configuration/pubsub.spec.ts index 6d0c6e68d2..080503b366 100644 --- a/test/configuration/pubsub.spec.ts +++ b/test/configuration/pubsub.spec.ts @@ -90,12 +90,12 @@ describe('Pubsub subscription handlers adapter', () => { pubsub.subscribe(topic) pubsub.addEventListener('message', handler) - pubsub.publish(topic, uint8ArrayFromString('useless-data')) + await pubsub.publish(topic, uint8ArrayFromString('useless-data')) await defer.promise pubsub.unsubscribe(topic) pubsub.removeEventListener('message', handler) - pubsub.publish(topic, uint8ArrayFromString('useless-data')) + await pubsub.publish(topic, uint8ArrayFromString('useless-data')) // wait to guarantee that the handler is not called twice await delay(100) diff --git a/test/configuration/utils.ts b/test/configuration/utils.ts index 3965d6b095..b7cbacf82c 100644 --- a/test/configuration/utils.ts +++ b/test/configuration/utils.ts @@ -5,7 +5,7 @@ import { WebSockets } from '@libp2p/websockets' import * as filters from '@libp2p/websockets/filters' import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' import mergeOptions from 'merge-options' -import type { Message, PubSubInit, PubSubRPC, PubSubRPCMessage } from '@libp2p/interfaces/pubsub' +import type { Message, PublishResult, PubSubInit, PubSubRPC, PubSubRPCMessage } from '@libp2p/interfaces/pubsub' import type { Libp2pInit, Libp2pOptions } from '../../src/index.js' import type { PeerId } from '@libp2p/interfaces/peer-id' import * as cborg from 'cborg' @@ -44,11 +44,12 @@ class MockPubSub extends PubSubBaseProtocol { return cborg.encode(rpc) } - async publishMessage (from: PeerId, message: Message): Promise { + async publishMessage (from: PeerId, message: Message): Promise { const peers = this.getSubscribers(message.topic) + const recipients: PeerId[] = [] if (peers == null || peers.length === 0) { - return + return { recipients } } peers.forEach(id => { @@ -60,8 +61,11 @@ class MockPubSub extends PubSubBaseProtocol { return } + recipients.push(id) this.send(id, { messages: [message] }) }) + + return { recipients } } } diff --git a/test/identify/index.spec.ts b/test/identify/index.spec.ts index bbe8130695..c1ae112a83 100644 --- a/test/identify/index.spec.ts +++ b/test/identify/index.spec.ts @@ -114,13 +114,7 @@ describe('Identify', () => { await localIdentify.start() await remoteIdentify.start() - const [localToRemote] = connectionPair({ - peerId: localComponents.getPeerId(), - registrar: localComponents.getRegistrar() - }, { - peerId: remoteComponents.getPeerId(), - registrar: remoteComponents.getRegistrar() - }) + const [localToRemote] = connectionPair(localComponents, remoteComponents) const localAddressBookConsumePeerRecordSpy = sinon.spy(localComponents.getPeerStore().addressBook, 'consumePeerRecord') const localProtoBookSetSpy = sinon.spy(localComponents.getPeerStore().protoBook, 'set') @@ -161,13 +155,7 @@ describe('Identify', () => { }) await remoteIdentify.start() - const [localToRemote] = connectionPair({ - peerId: localComponents.getPeerId(), - registrar: localComponents.getRegistrar() - }, { - peerId: remoteComponents.getPeerId(), - registrar: remoteComponents.getRegistrar() - }) + const [localToRemote] = connectionPair(localComponents, remoteComponents) sinon.stub(localComponents.getPeerStore().addressBook, 'consumePeerRecord').throws() @@ -194,13 +182,7 @@ describe('Identify', () => { await localIdentify.start() await remoteIdentify.start() - const [localToRemote] = connectionPair({ - peerId: localComponents.getPeerId(), - registrar: localComponents.getRegistrar() - }, { - peerId: remoteComponents.getPeerId(), - registrar: remoteComponents.getRegistrar() - }) + const [localToRemote] = connectionPair(localComponents, remoteComponents) // send an invalid message await remoteComponents.getRegistrar().unhandle(MULTICODEC_IDENTIFY) @@ -267,13 +249,7 @@ describe('Identify', () => { await localIdentify.start() await remoteIdentify.start() - const [localToRemote, remoteToLocal] = connectionPair({ - peerId: localComponents.getPeerId(), - registrar: localComponents.getRegistrar() - }, { - peerId: remoteComponents.getPeerId(), - registrar: remoteComponents.getRegistrar() - }) + const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) // ensure connections are registered by connection manager localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { @@ -353,13 +329,7 @@ describe('Identify', () => { await localIdentify.start() await remoteIdentify.start() - const [localToRemote, remoteToLocal] = connectionPair({ - peerId: localComponents.getPeerId(), - registrar: localComponents.getRegistrar() - }, { - peerId: remoteComponents.getPeerId(), - registrar: remoteComponents.getRegistrar() - }) + const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) // ensure connections are registered by connection manager localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { From d16817ca443443e88803ee8096d45debb14af91b Mon Sep 17 00:00:00 2001 From: tuyennhv Date: Sat, 23 Apr 2022 02:56:47 +0700 Subject: [PATCH 357/447] fix: emit peer:connect after all (#1171) **Motivation** In lodestar, when we handle "peer:connect" event, we dial the peer which gives another "peer:connect" event and it causes other issues **Motivation** In `onConnect` function, "peer:connect" event should be emitted after we add connection to the `connections` map so that when app dial the peer in "peer:connect" event handler, it uses the same/existing connection --- package.json | 2 ++ src/connection-manager/index.ts | 3 +-- test/dialing/direct.spec.ts | 11 +++-------- test/identify/index.spec.ts | 10 ++++++++-- test/upgrading/upgrader.spec.ts | 3 +++ 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 788201202d..48ca92d13e 100644 --- a/package.json +++ b/package.json @@ -135,6 +135,8 @@ "it-stream-types": "^1.0.4", "it-take": "^1.0.2", "it-to-buffer": "^2.0.2", + "@libp2p/tracked-map": "^1.0.4", + "it-pair": "^2.0.2", "merge-options": "^3.0.4", "mortice": "^3.0.0", "multiformats": "^9.6.3", diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index ad165d87e8..1bfb2f0601 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -267,8 +267,6 @@ export class DefaultConnectionManager extends EventEmitter('peer:connect', { detail: connection })) - if (storedConns != null) { storedConns.push(connection) } else { @@ -284,6 +282,7 @@ export class DefaultConnectionManager extends EventEmitter('peer:connect', { detail: connection })) } /** diff --git a/test/dialing/direct.spec.ts b/test/dialing/direct.spec.ts index fb2f978d57..66430cd52d 100644 --- a/test/dialing/direct.spec.ts +++ b/test/dialing/direct.spec.ts @@ -29,6 +29,7 @@ import { createFromJSON } from '@libp2p/peer-id-factory' import Peers from '../fixtures/peers.js' import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' import type { PeerId } from '@libp2p/interfaces/peer-id' +import { pEvent } from 'p-event' const unsupportedAddr = new Multiaddr('/ip4/127.0.0.1/tcp/9999') @@ -430,21 +431,15 @@ describe('libp2p.dialer (direct, WebSockets)', () => { const identifySpy = sinon.spy(libp2p.identifyService, 'identify') const protobookSetSpy = sinon.spy(libp2p.components.getPeerStore().protoBook, 'set') - const connectionPromise = pDefer() + const connectionPromise = pEvent(libp2p.connectionManager, 'peer:connect') await libp2p.start() - libp2p.components.getUpgrader().addEventListener('connection', () => { - connectionPromise.resolve() - }, { - once: true - }) - const connection = await libp2p.dial(MULTIADDRS_WEBSOCKETS[0]) expect(connection).to.exist() // Wait for connection event to be emitted - await connectionPromise.promise + await connectionPromise expect(identifySpy.callCount).to.equal(1) await identifySpy.firstCall.returnValue diff --git a/test/identify/index.spec.ts b/test/identify/index.spec.ts index c1ae112a83..9a2ab97266 100644 --- a/test/identify/index.spec.ts +++ b/test/identify/index.spec.ts @@ -496,9 +496,12 @@ describe('Identify', () => { const identityServiceIdentifySpy = sinon.spy(libp2p.identifyService, 'identify') const identityServicePushSpy = sinon.spy(libp2p.identifyService, 'push') - + const connectionPromise = pEvent(libp2p.connectionManager, 'peer:connect') const connection = await libp2p.dial(remoteAddr) + expect(connection).to.exist() + // Wait for connection event to be emitted + await connectionPromise // Wait for identify to finish await identityServiceIdentifySpy.firstCall.returnValue @@ -560,9 +563,12 @@ describe('Identify', () => { const identityServiceIdentifySpy = sinon.spy(libp2p.identifyService, 'identify') const identityServicePushSpy = sinon.spy(libp2p.identifyService, 'push') - + const connectionPromise = pEvent(libp2p.connectionManager, 'peer:connect') const connection = await libp2p.dial(remoteAddr) + expect(connection).to.exist() + // Wait for connection event to be emitted + await connectionPromise // Wait for identify to finish await identityServiceIdentifySpy.firstCall.returnValue diff --git a/test/upgrading/upgrader.spec.ts b/test/upgrading/upgrader.spec.ts index 536b2c53bc..56e5eb968d 100644 --- a/test/upgrading/upgrader.spec.ts +++ b/test/upgrading/upgrader.spec.ts @@ -26,6 +26,7 @@ import type { StreamMuxer, StreamMuxerFactory, StreamMuxerInit } from '@libp2p/i import type { Stream } from '@libp2p/interfaces/connection' import pDefer from 'p-defer' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' +import { pEvent } from 'p-event' const addrs = [ new Multiaddr('/ip4/127.0.0.1/tcp/0'), @@ -495,10 +496,12 @@ describe('libp2p.upgrader', () => { const connectionManagerDispatchEventSpy = sinon.spy(libp2p.components.getConnectionManager(), 'dispatchEvent') // Upgrade and check the connect event + const connectionPromise = pEvent(libp2p.connectionManager, 'peer:connect') const connections = await Promise.all([ libp2p.components.getUpgrader().upgradeOutbound(outbound), remoteLibp2p.components.getUpgrader().upgradeInbound(inbound) ]) + await connectionPromise expect(connectionManagerDispatchEventSpy.callCount).to.equal(1) let [event] = connectionManagerDispatchEventSpy.getCall(0).args From a15254fdd478a336edf1e1196b721dc56888b2ea Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 4 May 2022 10:19:04 +0100 Subject: [PATCH 358/447] fix: update to new interfaces (#1206) Notably removes the `Dialer` interface as the `ConnectionManager` is now in charge of managing connections. --- .aegir.js | 1 + package.json | 9 +- src/circuit/auto-relay.ts | 14 +- src/circuit/circuit/hop.ts | 10 +- src/circuit/listener.ts | 21 +- src/circuit/transport.ts | 11 +- src/config.ts | 12 +- src/connection-manager/auto-dialler.ts | 8 +- .../dialer/auto-dialer.ts | 27 ++- .../dialer/dial-request.ts | 8 +- src/{ => connection-manager}/dialer/index.ts | 82 ++++---- src/connection-manager/index.ts | 199 +++++++++++++----- src/fetch/index.ts | 2 +- src/identify/index.ts | 8 +- src/index.ts | 31 +-- src/libp2p.ts | 113 +++++----- src/ping/index.ts | 3 +- src/registrar.ts | 2 +- test/configuration/pubsub.spec.ts | 3 + test/connection-manager/auto-dialler.spec.ts | 15 +- test/connection-manager/index.node.ts | 45 ++-- test/connection-manager/index.spec.ts | 4 +- test/content-routing/dht/operation.node.ts | 10 +- test/dialing/dial-request.spec.ts | 15 +- test/dialing/direct.node.ts | 47 +++-- test/dialing/direct.spec.ts | 92 +++++--- test/dialing/resolver.spec.ts | 20 +- test/identify/index.spec.ts | 77 ++++--- test/metrics/index.node.ts | 11 +- test/peer-discovery/index.spec.ts | 14 +- test/registrar/registrar.spec.ts | 15 +- test/relay/auto-relay.node.ts | 2 +- test/relay/relay.node.ts | 16 +- test/transports/transport-manager.node.ts | 7 +- 34 files changed, 561 insertions(+), 393 deletions(-) rename src/{ => connection-manager}/dialer/auto-dialer.ts (54%) rename src/{ => connection-manager}/dialer/dial-request.ts (95%) rename src/{ => connection-manager}/dialer/index.ts (89%) diff --git a/.aegir.js b/.aegir.js index 6186529b07..66bccedf2f 100644 --- a/.aegir.js +++ b/.aegir.js @@ -11,6 +11,7 @@ export default { }, test: { before: async () => { + // use dynamic import because we only want to reference these files during the test run, e.g. after building const { createLibp2p } = await import('./dist/src/index.js') const { MULTIADDRS_WEBSOCKETS } = await import('./dist/test/fixtures/browser.js') const { Plaintext } = await import('./dist/src/insecure/index.js') diff --git a/package.json b/package.json index 48ca92d13e..07f344c8aa 100644 --- a/package.json +++ b/package.json @@ -98,9 +98,10 @@ "@achingbrain/nat-port-mapper": "^1.0.0", "@libp2p/connection": "^1.1.5", "@libp2p/crypto": "^0.22.11", - "@libp2p/interfaces": "^1.3.24", + "@libp2p/interfaces": "^1.3.30", "@libp2p/logger": "^1.1.4", "@libp2p/multistream-select": "^1.0.4", + "@libp2p/peer-collections": "^1.0.2", "@libp2p/peer-id": "^1.1.10", "@libp2p/peer-id-factory": "^1.0.9", "@libp2p/peer-record": "^1.0.8", @@ -135,8 +136,6 @@ "it-stream-types": "^1.0.4", "it-take": "^1.0.2", "it-to-buffer": "^2.0.2", - "@libp2p/tracked-map": "^1.0.4", - "it-pair": "^2.0.2", "merge-options": "^3.0.4", "mortice": "^3.0.0", "multiformats": "^9.6.3", @@ -165,9 +164,9 @@ "@libp2p/delegated-content-routing": "^1.0.2", "@libp2p/delegated-peer-routing": "^1.0.2", "@libp2p/floodsub": "^1.0.6", - "@libp2p/interface-compliance-tests": "^1.1.25", + "@libp2p/interface-compliance-tests": "^1.1.31", "@libp2p/interop": "^1.0.3", - "@libp2p/kad-dht": "^1.0.7", + "@libp2p/kad-dht": "^1.0.8", "@libp2p/mdns": "^1.0.4", "@libp2p/mplex": "^1.0.3", "@libp2p/pubsub": "^1.2.18", diff --git a/src/circuit/auto-relay.ts b/src/circuit/auto-relay.ts index 8d84a8e980..04b6fa3c64 100644 --- a/src/circuit/auto-relay.ts +++ b/src/circuit/auto-relay.ts @@ -85,12 +85,14 @@ export class AutoRelay { // If protocol, check if can hop, store info in the metadataBook and listen on it try { - const connection = this.components.getConnectionManager().getConnection(peerId) + const connections = this.components.getConnectionManager().getConnections(peerId) - if (connection == null) { + if (connections.length === 0) { return } + const connection = connections[0] + // 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`) @@ -223,15 +225,15 @@ export class AutoRelay { continue } - const connection = this.components.getConnectionManager().getConnection(id) + const connections = this.components.getConnectionManager().getConnections(id) // If not connected, store for possible later use. - if (connection == null) { + if (connections.length === 0) { knownHopsToDial.push(id) continue } - await this._addListenRelay(connection, idStr) + await this._addListenRelay(connections[0], idStr) // Check if already listening on enough relays if (this.listenRelays.size >= this.maxListeners) { @@ -274,7 +276,7 @@ export class AutoRelay { async _tryToListenOnRelay (peerId: PeerId) { try { - const connection = await this.components.getDialer().dial(peerId) + const connection = await this.components.getConnectionManager().openConnection(peerId) await this._addListenRelay(connection, peerId.toString()) } catch (err: any) { log.error('Could not use %p as relay', peerId, err) diff --git a/src/circuit/circuit/hop.ts b/src/circuit/circuit/hop.ts index 8d0558842f..02ed844b26 100644 --- a/src/circuit/circuit/hop.ts +++ b/src/circuit/circuit/hop.ts @@ -11,7 +11,7 @@ import type { Connection } from '@libp2p/interfaces/connection' import { peerIdFromBytes } from '@libp2p/peer-id' import type { Duplex } from 'it-stream-types' import type { Circuit } from '../transport.js' -import type { ConnectionManager } from '@libp2p/interfaces/registrar' +import type { ConnectionManager } from '@libp2p/interfaces/connection-manager' const log = logger('libp2p:circuit:hop') @@ -58,8 +58,8 @@ export async function handleHop (hopRequest: HopRequest) { // Get the connection to the destination (stop) peer const destinationPeer = peerIdFromBytes(request.dstPeer.id) - const destinationConnection = connectionManager.getConnection(destinationPeer) - if (destinationConnection == null && !circuit.hopActive()) { + const destinationConnections = connectionManager.getConnections(destinationPeer) + if (destinationConnections.length === 0 && !circuit.hopActive()) { log('HOP request received but we are not connected to the destination peer') return streamHandler.end({ type: CircuitPB.Type.STATUS, @@ -68,7 +68,7 @@ export async function handleHop (hopRequest: HopRequest) { } // TODO: Handle being an active relay - if (destinationConnection == null) { + if (destinationConnections.length === 0) { log('did not have connection to remote peer') return streamHandler.end({ type: CircuitPB.Type.STATUS, @@ -87,7 +87,7 @@ export async function handleHop (hopRequest: HopRequest) { try { log('performing STOP request') const result = await stop({ - connection: destinationConnection, + connection: destinationConnections[0], request: stopRequest }) diff --git a/src/circuit/listener.ts b/src/circuit/listener.ts index 7de4fbf9e8..495458771f 100644 --- a/src/circuit/listener.ts +++ b/src/circuit/listener.ts @@ -1,11 +1,12 @@ import { CustomEvent, EventEmitter } from '@libp2p/interfaces' -import type { ConnectionManager } from '@libp2p/interfaces/registrar' -import type { Dialer } from '@libp2p/interfaces/dialer' +import type { ConnectionManager } from '@libp2p/interfaces/connection-manager' +import type { PeerStore } from '@libp2p/interfaces/peer-store' import type { Listener } from '@libp2p/interfaces/transport' +import { peerIdFromString } from '@libp2p/peer-id' import { Multiaddr } from '@multiformats/multiaddr' export interface ListenerOptions { - dialer: Dialer + peerStore: PeerStore connectionManager: ConnectionManager } @@ -17,7 +18,19 @@ export function createListener (options: ListenerOptions): Listener { */ async function listen (addr: Multiaddr): Promise { const addrString = addr.toString().split('/p2p-circuit').find(a => a !== '') - const relayConn = await options.dialer.dial(new Multiaddr(addrString)) + const ma = new Multiaddr(addrString) + + const relayPeerStr = ma.getPeerId() + + if (relayPeerStr == null) { + throw new Error('Could not determine relay peer from multiaddr') + } + + const relayPeerId = peerIdFromString(relayPeerStr) + + await options.peerStore.addressBook.add(relayPeerId, [ma]) + + const relayConn = await options.connectionManager.openConnection(relayPeerId) const relayedAddr = relayConn.remoteAddr.encapsulate('/p2p-circuit') listeningAddrs.set(relayConn.remotePeer.toString(), relayedAddr) diff --git a/src/circuit/transport.ts b/src/circuit/transport.ts index 9722d576b5..4b4940d294 100644 --- a/src/circuit/transport.ts +++ b/src/circuit/transport.ts @@ -149,9 +149,12 @@ export class Circuit implements Transport, Initializable { const destinationPeer = peerIdFromString(destinationId) let disconnectOnFailure = false - let relayConnection = this.components.getConnectionManager().getConnection(relayPeer) + const relayConnections = this.components.getConnectionManager().getConnections(relayPeer) + let relayConnection = relayConnections[0] + if (relayConnection == null) { - relayConnection = await this.components.getDialer().dial(relayAddr, options) + await this.components.getPeerStore().addressBook.add(relayPeer, [relayAddr]) + relayConnection = await this.components.getConnectionManager().openConnection(relayPeer, options) disconnectOnFailure = true } @@ -195,8 +198,8 @@ export class Circuit implements Transport, Initializable { this.handler = options.handler return createListener({ - dialer: this.components.getDialer(), - connectionManager: this.components.getConnectionManager() + connectionManager: this.components.getConnectionManager(), + peerStore: this.components.getPeerStore() }) } diff --git a/src/config.ts b/src/config.ts index 7f77235cc2..8707d43349 100644 --- a/src/config.ts +++ b/src/config.ts @@ -21,14 +21,8 @@ const DefaultConfig: Partial = { connectionManager: { maxConnections: 300, minConnections: 50, + autoDial: true, autoDialInterval: 10000, - autoDial: true - }, - connectionGater: {}, - transportManager: { - faultTolerance: FaultTolerance.FATAL_ALL - }, - dialer: { maxParallelDials: Constants.MAX_PARALLEL_DIALS, maxDialsPerPeer: Constants.MAX_PER_PEER_DIALS, dialTimeout: Constants.DIAL_TIMEOUT, @@ -37,6 +31,10 @@ const DefaultConfig: Partial = { }, addressSorter: publicAddressesFirst }, + connectionGater: {}, + transportManager: { + faultTolerance: FaultTolerance.FATAL_ALL + }, host: { agentVersion: AGENT_VERSION }, diff --git a/src/connection-manager/auto-dialler.ts b/src/connection-manager/auto-dialler.ts index d14cbbe51b..523a710479 100644 --- a/src/connection-manager/auto-dialler.ts +++ b/src/connection-manager/auto-dialler.ts @@ -102,7 +102,7 @@ export class AutoDialler implements Startable { const minConnections = this.options.minConnections // Already has enough connections - if (this.components.getConnectionManager().getConnectionList().length >= minConnections) { + if (this.components.getConnectionManager().getConnections().length >= minConnections) { this.autoDialTimeout = retimer(this._autoDial, this.options.autoDialInterval) return @@ -126,7 +126,7 @@ export class AutoDialler implements Startable { async (source) => await all(source) ) - for (let i = 0; this.running && i < peers.length && this.components.getConnectionManager().getConnectionList().length < minConnections; i++) { + for (let i = 0; this.running && i < peers.length && this.components.getConnectionManager().getConnections().length < minConnections; i++) { // Connection Manager was stopped during async dial if (!this.running) { return @@ -134,10 +134,10 @@ export class AutoDialler implements Startable { const peer = peers[i] - if (this.components.getConnectionManager().getConnection(peer.id) == null) { + if (this.components.getConnectionManager().getConnections(peer.id).length === 0) { log('connecting to a peerStore stored peer %p', peer.id) try { - await this.components.getDialer().dial(peer.id) + await this.components.getConnectionManager().openConnection(peer.id) } catch (err: any) { log.error('could not connect to peerStore stored peer', err) } diff --git a/src/dialer/auto-dialer.ts b/src/connection-manager/dialer/auto-dialer.ts similarity index 54% rename from src/dialer/auto-dialer.ts rename to src/connection-manager/dialer/auto-dialer.ts index 5674a94d60..bb790a243f 100644 --- a/src/dialer/auto-dialer.ts +++ b/src/connection-manager/dialer/auto-dialer.ts @@ -1,39 +1,58 @@ import type { PeerInfo } from '@libp2p/interfaces/peer-info' import { logger } from '@libp2p/logger' import type { Components } from '@libp2p/interfaces/components' +import { TimeoutController } from 'timeout-abort-controller' const log = logger('libp2p:dialer:auto-dialer') export interface AutoDialerInit { enabled: boolean minConnections: number + dialTimeout: number } export class AutoDialer { private readonly components: Components private readonly enabled: boolean private readonly minConnections: number + private readonly dialTimeout: number constructor (components: Components, init: AutoDialerInit) { this.components = components this.enabled = init.enabled this.minConnections = init.minConnections + this.dialTimeout = init.dialTimeout } public handle (evt: CustomEvent) { const { detail: peer } = evt + if (!this.enabled) { + return + } + + const connections = this.components.getConnectionManager().getConnections(peer.id) + // If auto dialing is on and we have no connection to the peer, check if we should dial - if (this.enabled && this.components.getConnectionManager().getConnection(peer.id) == null) { + if (connections.length === 0) { const minConnections = this.minConnections ?? 0 - if (minConnections > this.components.getConnectionManager().getConnectionList().length) { - log('auto-dialing discovered peer %p', peer.id) + const allConnections = this.components.getConnectionManager().getConnections() - void this.components.getDialer().dial(peer.id) + if (minConnections > allConnections.length) { + log('auto-dialing discovered peer %p with timeout %d', peer.id, this.dialTimeout) + + const controller = new TimeoutController(this.dialTimeout) + + void this.components.getConnectionManager().openConnection(peer.id, { + signal: controller.signal + }) .catch(err => { log.error('could not connect to discovered peer %p with %o', peer.id, err) }) + .finally(() => { + controller.clear() + }) } } } diff --git a/src/dialer/dial-request.ts b/src/connection-manager/dialer/dial-request.ts similarity index 95% rename from src/dialer/dial-request.ts rename to src/connection-manager/dialer/dial-request.ts index 23c007a4dc..650e825a98 100644 --- a/src/dialer/dial-request.ts +++ b/src/connection-manager/dialer/dial-request.ts @@ -3,12 +3,12 @@ import { anySignal } from 'any-signal' import FIFO from 'p-fifo' // @ts-expect-error setMaxListeners is missing from the node 16 types import { setMaxListeners } from 'events' -import { codes } from '../errors.js' +import { codes } from '../../errors.js' import { logger } from '@libp2p/logger' import type { Multiaddr } from '@multiformats/multiaddr' import type { Connection } from '@libp2p/interfaces/connection' import type { AbortOptions } from '@libp2p/interfaces' -import type { DefaultDialer } from './index.js' +import type { Dialer } from './index.js' const log = logger('libp2p:dialer:dial-request') @@ -19,12 +19,12 @@ export interface DialAction { export interface DialRequestOptions { addrs: Multiaddr[] dialAction: DialAction - dialer: DefaultDialer + dialer: Dialer } export class DialRequest { private readonly addrs: Multiaddr[] - private readonly dialer: DefaultDialer + private readonly dialer: Dialer private readonly dialAction: DialAction /** diff --git a/src/dialer/index.ts b/src/connection-manager/dialer/index.ts similarity index 89% rename from src/dialer/index.ts rename to src/connection-manager/dialer/index.ts index a0352fd69d..1e54f184de 100644 --- a/src/dialer/index.ts +++ b/src/connection-manager/dialer/index.ts @@ -3,7 +3,7 @@ import all from 'it-all' import filter from 'it-filter' import { pipe } from 'it-pipe' import errCode from 'err-code' -import { Multiaddr } from '@multiformats/multiaddr' +import { Multiaddr, Resolver } from '@multiformats/multiaddr' import { TimeoutController } from 'timeout-abort-controller' import { AbortError } from '@libp2p/interfaces/errors' import { anySignal } from 'any-signal' @@ -12,22 +12,22 @@ import { setMaxListeners } from 'events' import { DialAction, DialRequest } from './dial-request.js' import { publicAddressesFirst } from '@libp2p/utils/address-sort' import { trackedMap } from '@libp2p/tracked-map' -import { codes } from '../errors.js' +import { codes } from '../../errors.js' import { DIAL_TIMEOUT, MAX_PARALLEL_DIALS, MAX_PER_PEER_DIALS, MAX_ADDRS_TO_DIAL -} from '../constants.js' +} from '../../constants.js' import type { Connection } from '@libp2p/interfaces/connection' import type { AbortOptions, Startable } from '@libp2p/interfaces' import type { PeerId } from '@libp2p/interfaces/peer-id' -import { getPeer } from '../get-peer.js' +import { getPeer } from '../../get-peer.js' import sort from 'it-sort' -import type { Components } from '@libp2p/interfaces/components' -import type { Dialer, DialerInit } from '@libp2p/interfaces/dialer' +import { Components, Initializable } from '@libp2p/interfaces/components' import map from 'it-map' import type { AddressSorter } from '@libp2p/interfaces/peer-store' +import type { ComponentMetricsTracker } from '@libp2p/interfaces/metrics' const log = logger('libp2p:dialer') @@ -52,8 +52,41 @@ export interface PendingDialTarget { reject: (err: Error) => void } -export class DefaultDialer implements Dialer, Startable { - private readonly components: Components +export interface DialerInit { + /** + * Sort the known addresses of a peer before trying to dial + */ + addressSorter?: AddressSorter + + /** + * Number of max concurrent dials + */ + maxParallelDials?: number + + /** + * Number of max addresses to dial for a given peer + */ + maxAddrsToDial?: number + + /** + * How long a dial attempt is allowed to take + */ + dialTimeout?: number + + /** + * Number of max concurrent dials per peer + */ + maxDialsPerPeer?: number + + /** + * Multiaddr resolvers to use when dialing + */ + resolvers?: Record + metrics?: ComponentMetricsTracker +} + +export class Dialer implements Startable, Initializable { + private components: Components = new Components() private readonly addressSorter: AddressSorter private readonly maxAddrsToDial: number private readonly timeout: number @@ -63,8 +96,7 @@ export class DefaultDialer implements Dialer, Startable { public pendingDialTargets: Map private started: boolean - constructor (components: Components, init: DialerInit = {}) { - this.components = components + constructor (init: DialerInit = {}) { this.started = false this.addressSorter = init.addressSorter ?? publicAddressesFirst this.maxAddrsToDial = init.maxAddrsToDial ?? MAX_ADDRS_TO_DIAL @@ -87,6 +119,10 @@ export class DefaultDialer implements Dialer, Startable { } } + init (components: Components): void { + this.components = components + } + isStarted () { return this.started } @@ -139,16 +175,6 @@ export class DefaultDialer implements Dialer, Startable { throw errCode(new Error('The dial request is blocked by gater.allowDialPeer'), codes.ERR_PEER_DIAL_INTERCEPTED) } - log('dial to %p', id) - - const existingConnection = this.components.getConnectionManager().getConnection(id) - - if (existingConnection != null) { - log('had an existing connection to %p', id) - - return existingConnection - } - log('creating dial target for %p', id) const dialTarget = await this._createCancellableDialTarget(id) @@ -176,22 +202,6 @@ export class DefaultDialer implements Dialer, Startable { } } - async dialProtocol (peer: PeerId | Multiaddr, protocols: string | string[], options: AbortOptions = {}) { - if (protocols == null) { - throw errCode(new Error('no protocols were provided to open a stream'), codes.ERR_INVALID_PROTOCOLS_FOR_STREAM) - } - - protocols = Array.isArray(protocols) ? protocols : [protocols] - - if (protocols.length === 0) { - throw errCode(new Error('no protocols were provided to open a stream'), codes.ERR_INVALID_PROTOCOLS_FOR_STREAM) - } - - const connection = await this.dial(peer, options) - - return await connection.newStream(protocols) - } - /** * Connects to a given `peer` by dialing all of its known addresses. * The dial to the first address that is successfully able to upgrade a connection diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index 1bfb2f0601..b6ca836490 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -4,16 +4,19 @@ import mergeOptions from 'merge-options' import { LatencyMonitor, SummaryObject } from './latency-monitor.js' // @ts-expect-error retimer does not have types import retimer from 'retimer' -import { CustomEvent, EventEmitter, Startable } from '@libp2p/interfaces' +import { AbortOptions, CustomEvent, EventEmitter, Startable } from '@libp2p/interfaces' import { trackedMap } from '@libp2p/tracked-map' import { codes } from '../errors.js' import { isPeerId, PeerId } from '@libp2p/interfaces/peer-id' // @ts-expect-error setMaxListeners is missing from the node 16 types import { setMaxListeners } from 'events' import type { Connection } from '@libp2p/interfaces/connection' -import type { ConnectionManager } from '@libp2p/interfaces/registrar' -import type { Components } from '@libp2p/interfaces/components' +import type { ConnectionManager } from '@libp2p/interfaces/connection-manager' +import { Components, Initializable } from '@libp2p/interfaces/components' import * as STATUS from '@libp2p/interfaces/connection/status' +import { Dialer } from './dialer/index.js' +import type { AddressSorter } from '@libp2p/interfaces/peer-store' +import type { Resolver } from '@multiformats/multiaddr' const log = logger('libp2p:connection-manager') @@ -34,21 +37,16 @@ const METRICS_COMPONENT = 'connection-manager' const METRICS_PEER_CONNECTIONS = 'peer-connections' const METRICS_PEER_VALUES = 'peer-values' -export interface ConnectionManagerEvents { - 'peer:connect': CustomEvent - 'peer:disconnect': CustomEvent -} - export interface ConnectionManagerInit { /** - * The maximum number of connections allowed + * The maximum number of connections to keep open */ - maxConnections?: number + maxConnections: number /** - * The minimum number of connections to avoid pruning + * The minimum number of connections to keep open */ - minConnections?: number + minConnections: number /** * The max data (in and out), per average interval to allow @@ -86,39 +84,75 @@ export interface ConnectionManagerInit { defaultPeerValue?: number /** - * Should preemptively guarantee connections are above the low watermark + * If true, try to connect to all discovered peers up to the connection manager limit */ autoDial?: boolean /** - * How often, in milliseconds, it should preemptively guarantee connections are above the low watermark + * How long to wait between attempting to keep our number of concurrent connections + * above minConnections + */ + autoDialInterval: number + + /** + * Sort the known addresses of a peer before trying to dial */ - autoDialInterval?: number + addressSorter?: AddressSorter + + /** + * Number of max concurrent dials + */ + maxParallelDials?: number + + /** + * Number of max addresses to dial for a given peer + */ + maxAddrsToDial?: number + + /** + * How long a dial attempt is allowed to take + */ + dialTimeout?: number + + /** + * Number of max concurrent dials per peer + */ + maxDialsPerPeer?: number + + /** + * Multiaddr resolvers to use when dialing + */ + resolvers?: Record +} + +export interface ConnectionManagerEvents { + 'peer:connect': CustomEvent + 'peer:disconnect': CustomEvent } /** * Responsible for managing known connections. */ -export class DefaultConnectionManager extends EventEmitter implements ConnectionManager, Startable { - private readonly components: Components - private readonly init: Required +export class DefaultConnectionManager extends EventEmitter implements ConnectionManager, Startable, Initializable { + public readonly dialer: Dialer + private components = new Components() + private readonly opts: Required private readonly peerValues: Map private readonly connections: Map private started: boolean private timer?: ReturnType private readonly latencyMonitor: LatencyMonitor - constructor (components: Components, init: ConnectionManagerInit = {}) { + constructor (init: ConnectionManagerInit) { super() - this.components = components - this.init = mergeOptions.call({ ignoreUndefined: true }, defaultOptions, init) + this.opts = mergeOptions.call({ ignoreUndefined: true }, defaultOptions, init) - if (this.init.maxConnections < this.init.minConnections) { + if (this.opts.maxConnections < this.opts.minConnections) { throw errCode(new Error('Connection Manager maxConnections must be greater than minConnections'), codes.ERR_INVALID_PARAMETERS) } - log('options: %o', this.init) + log('options: %o', this.opts) /** * Map of peer identifiers to their peer value for pruning connections. @@ -153,12 +187,16 @@ export class DefaultConnectionManager extends EventEmitter { - void this.onConnect(evt).catch(err => { - log.error(err) - }) - }) - this.components.getUpgrader().addEventListener('connectionEnd', this.onDisconnect.bind(this)) + this.dialer = new Dialer(this.opts) + + this.onConnect = this.onConnect.bind(this) + this.onDisconnect = this.onDisconnect.bind(this) + } + + init (components: Components): void { + this.components = components + + this.dialer.init(components) } isStarted () { @@ -171,18 +209,29 @@ export class DefaultConnectionManager extends EventEmitter) { + void this._onConnect(evt).catch(err => { + log.error(err) + }) + } + /** * Tracks the incoming connection and check the connection limit */ - async onConnect (evt: CustomEvent) { + async _onConnect (evt: CustomEvent) { const { detail: connection } = evt if (!this.started) { @@ -278,10 +334,10 @@ export class DefaultConnectionManager extends EventEmitter('peer:connect', { detail: connection })) } @@ -311,35 +367,64 @@ export class DefaultConnectionManager extends EventEmitter { - return this.connections - } + getConnections (peerId?: PeerId): Connection[] { + if (peerId != null) { + return this.connections.get(peerId.toString()) ?? [] + } - getConnectionList (): Connection[] { - let output: Connection[] = [] + let conns: Connection[] = [] - for (const connections of this.connections.values()) { - output = output.concat(connections) + for (const c of this.connections.values()) { + conns = conns.concat(c) } - return output + return conns } - getConnections (peerId: PeerId): Connection[] { - return this.connections.get(peerId.toString()) ?? [] - } + async openConnection (peerId: PeerId, options?: AbortOptions): Promise { + log('dial to %p', peerId) + const existingConnections = this.getConnections(peerId) - /** - * Get a connection with a peer - */ - getConnection (peerId: PeerId): Connection | undefined { - const connections = this.getAll(peerId) + if (existingConnections.length > 0) { + log('had an existing connection to %p', peerId) + + return existingConnections[0] + } - if (connections.length > 0) { - return connections[0] + const connection = await this.dialer.dial(peerId, options) + let peerConnections = this.connections.get(peerId.toString()) + + if (peerConnections == null) { + peerConnections = [] + this.connections.set(peerId.toString(), peerConnections) } - return undefined + // we get notified of connections via the Upgrader emitting "connection" + // events, double check we aren't already tracking this connection before + // storing it + let trackedConnection = false + + for (const conn of peerConnections) { + if (conn.id === connection.id) { + trackedConnection = true + } + } + + if (!trackedConnection) { + peerConnections.push(connection) + } + + return connection + } + + async closeConnections (peerId: PeerId): Promise { + const connections = this.connections.get(peerId.toString()) ?? [] + + await Promise.all( + connections.map(async connection => { + return await connection.close() + }) + ) } /** @@ -377,7 +462,7 @@ export class DefaultConnectionManager extends EventEmitter limit) { log('%s: limit exceeded: %p, %d', this.components.getPeerId(), name, value) @@ -390,7 +475,7 @@ export class DefaultConnectionManager extends EventEmitter a[1] - b[1]))) log('%p: sorted peer values: %j', this.components.getPeerId(), peerValues) diff --git a/src/fetch/index.ts b/src/fetch/index.ts index 69e14147f8..4e4e54fdf9 100644 --- a/src/fetch/index.ts +++ b/src/fetch/index.ts @@ -70,7 +70,7 @@ export class FetchService implements Startable { async fetch (peer: PeerId, key: string): Promise { log('dialing %s to %p', this.protocol, peer) - const connection = await this.components.getDialer().dial(peer) + const connection = await this.components.getConnectionManager().openConnection(peer) const { stream } = await connection.newStream([this.protocol]) const shake = handshake(stream) diff --git a/src/identify/index.ts b/src/identify/index.ts index 5cbc448499..879b9ca69c 100644 --- a/src/identify/index.ts +++ b/src/identify/index.ts @@ -23,7 +23,7 @@ import { codes } from '../errors.js' import type { IncomingStreamData } from '@libp2p/interfaces/registrar' import type { Connection } from '@libp2p/interfaces/connection' import type { Startable } from '@libp2p/interfaces' -import { peerIdFromKeys, peerIdFromString } from '@libp2p/peer-id' +import { peerIdFromKeys } from '@libp2p/peer-id' import type { Components } from '@libp2p/interfaces/components' const log = logger('libp2p:identify') @@ -161,15 +161,15 @@ export class IdentifyService implements Startable { const connections: Connection[] = [] - for (const [peerIdStr, conns] of this.components.getConnectionManager().getConnectionMap().entries()) { - const peerId = peerIdFromString(peerIdStr) + for (const conn of this.components.getConnectionManager().getConnections()) { + const peerId = conn.remotePeer const peer = await this.components.getPeerStore().get(peerId) if (!peer.protocols.includes(this.identifyPushProtocolStr)) { continue } - connections.push(...conns) + connections.push(conn) } await this.push(connections) diff --git a/src/index.ts b/src/index.ts index 0bc4eb1739..94e236f17f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,11 +16,12 @@ import type { ConnectionEncrypter } from '@libp2p/interfaces/connection-encrypte import type { PeerRouting } from '@libp2p/interfaces/peer-routing' import type { ContentRouting } from '@libp2p/interfaces/content-routing' import type { PubSub } from '@libp2p/interfaces/pubsub' -import type { ConnectionManager, Registrar, StreamHandler } from '@libp2p/interfaces/registrar' +import type { Registrar, StreamHandler } from '@libp2p/interfaces/registrar' +import type { ConnectionManager } from '@libp2p/interfaces/connection-manager' import type { Metrics, MetricsInit } from '@libp2p/interfaces/metrics' import type { PeerInfo } from '@libp2p/interfaces/peer-info' -import type { DialerInit } from '@libp2p/interfaces/dialer' import type { KeyChain } from './keychain/index.js' +import type { ConnectionManagerInit } from './connection-manager/index.js' export interface PersistentPeerStoreOptions { threshold?: number @@ -71,29 +72,6 @@ export interface AddressesConfig { announceFilter: (multiaddrs: Multiaddr[]) => Multiaddr[] } -export interface ConnectionManagerConfig { - /** - * If true, try to connect to all discovered peers up to the connection manager limit - */ - autoDial?: boolean - - /** - * The maximum number of connections to keep open - */ - maxConnections: number - - /** - * The minimum number of connections to keep open - */ - minConnections: number - - /** - * How long to wait between attempting to keep our number of concurrent connections - * above minConnections - */ - autoDialInterval: number -} - export interface TransportManagerConfig { faultTolerance?: FaultTolerance } @@ -117,11 +95,10 @@ export interface Libp2pInit { peerId: PeerId host: HostProperties addresses: AddressesConfig - connectionManager: ConnectionManagerConfig + connectionManager: ConnectionManagerInit connectionGater: Partial transportManager: TransportManagerConfig datastore: Datastore - dialer: DialerInit metrics: MetricsInit peerStore: PeerStoreInit peerRouting: PeerRoutingConfig diff --git a/src/libp2p.ts b/src/libp2p.ts index cca67615ed..1f4e23eb46 100644 --- a/src/libp2p.ts +++ b/src/libp2p.ts @@ -11,7 +11,6 @@ import { DefaultConnectionManager } from './connection-manager/index.js' import { AutoDialler } from './connection-manager/auto-dialler.js' import { Circuit } from './circuit/transport.js' import { Relay } from './circuit/index.js' -import { DefaultDialer } from './dialer/index.js' import { KeyChain } from './keychain/index.js' import { DefaultMetrics } from './metrics/index.js' import { DefaultTransportManager } from './transport-manager.js' @@ -25,14 +24,15 @@ import { PeerRecordUpdater } from './peer-record-updater.js' import { DHTPeerRouting } from './dht/dht-peer-routing.js' import { PersistentPeerStore } from '@libp2p/peer-store' import { DHTContentRouting } from './dht/dht-content-routing.js' -import { AutoDialer } from './dialer/auto-dialer.js' +import { AutoDialer } from './connection-manager/dialer/auto-dialer.js' import { Initializable, Components, isInitializable } from '@libp2p/interfaces/components' import type { PeerId } from '@libp2p/interfaces/peer-id' import type { Connection } from '@libp2p/interfaces/connection' import type { PeerRouting } from '@libp2p/interfaces/peer-routing' import type { ContentRouting } from '@libp2p/interfaces/content-routing' import type { PubSub } from '@libp2p/interfaces/pubsub' -import type { ConnectionManager, Registrar, StreamHandler } from '@libp2p/interfaces/registrar' +import type { Registrar, StreamHandler } from '@libp2p/interfaces/registrar' +import type { ConnectionManager } from '@libp2p/interfaces/connection-manager' import type { PeerInfo } from '@libp2p/interfaces/peer-info' import type { Libp2p, Libp2pEvents, Libp2pInit, Libp2pOptions } from './index.js' import { validateConfig } from './config.js' @@ -46,6 +46,7 @@ import { unmarshalPublicKey } from '@libp2p/crypto/keys' import type { Metrics } from '@libp2p/interfaces/metrics' import { DummyDHT } from './dht/dummy-dht.js' import { DummyPubSub } from './pubsub/dummy-pubsub.js' +import { PeerSet } from '@libp2p/peer-collections' const log = logger('libp2p') @@ -72,34 +73,40 @@ export class Libp2pNode extends EventEmitter implements Libp2p { constructor (init: Libp2pInit) { super() - this.services = [] this.initializables = [] this.started = false this.peerId = init.peerId this.components = new Components({ peerId: init.peerId, - datastore: init.datastore ?? new MemoryDatastore() + datastore: init.datastore ?? new MemoryDatastore(), + connectionGater: { + denyDialPeer: async () => await Promise.resolve(false), + denyDialMultiaddr: async () => await Promise.resolve(false), + denyInboundConnection: async () => await Promise.resolve(false), + denyOutboundConnection: async () => await Promise.resolve(false), + denyInboundEncryptedConnection: async () => await Promise.resolve(false), + denyOutboundEncryptedConnection: async () => await Promise.resolve(false), + denyInboundUpgradedConnection: async () => await Promise.resolve(false), + denyOutboundUpgradedConnection: async () => await Promise.resolve(false), + filterMultiaddrForPeer: async () => await Promise.resolve(true), + ...init.connectionGater + } }) + this.components.setPeerStore(new PersistentPeerStore({ + addressFilter: this.components.getConnectionGater().filterMultiaddrForPeer, + ...init.peerStore + })) + + this.services = [ + this.components + ] // Create Metrics if (init.metrics.enabled) { - this.metrics = this.components.setMetrics(this.configureComponent(new DefaultMetrics(init.metrics))) + this.metrics = this.components.setMetrics(new DefaultMetrics(init.metrics)) } - this.components.setConnectionGater(this.configureComponent({ - denyDialPeer: async () => await Promise.resolve(false), - denyDialMultiaddr: async () => await Promise.resolve(false), - denyInboundConnection: async () => await Promise.resolve(false), - denyOutboundConnection: async () => await Promise.resolve(false), - denyInboundEncryptedConnection: async () => await Promise.resolve(false), - denyOutboundEncryptedConnection: async () => await Promise.resolve(false), - denyInboundUpgradedConnection: async () => await Promise.resolve(false), - denyOutboundUpgradedConnection: async () => await Promise.resolve(false), - filterMultiaddrForPeer: async () => await Promise.resolve(true), - ...init.connectionGater - })) - - this.peerStore = this.components.setPeerStore(this.configureComponent(new PersistentPeerStore(this.components, init.peerStore))) + this.peerStore = this.components.getPeerStore() this.peerStore.addEventListener('peer', evt => { const { detail: peerData } = evt @@ -109,32 +116,30 @@ export class Libp2pNode extends EventEmitter implements Libp2p { // Set up connection protector if configured if (init.connectionProtector != null) { - this.components.setConnectionProtector(this.configureComponent(init.connectionProtector)) + this.components.setConnectionProtector(init.connectionProtector) } // Set up the Upgrader - this.components.setUpgrader(this.configureComponent(new DefaultUpgrader(this.components, { + this.components.setUpgrader(new DefaultUpgrader(this.components, { connectionEncryption: (init.connectionEncryption ?? []).map(component => this.configureComponent(component)), muxers: (init.streamMuxers ?? []).map(component => this.configureComponent(component)) - }))) + })) // Create the Connection Manager - this.connectionManager = this.components.setConnectionManager(this.configureComponent(new DefaultConnectionManager(this.components, init.connectionManager))) + this.connectionManager = this.components.setConnectionManager(new DefaultConnectionManager(init.connectionManager)) // Create the Registrar - this.registrar = this.components.setRegistrar(this.configureComponent(new DefaultRegistrar(this.components))) + this.registrar = this.components.setRegistrar(new DefaultRegistrar(this.components)) // Setup the transport manager - this.components.setTransportManager(this.configureComponent(new DefaultTransportManager(this.components, init.transportManager))) + this.components.setTransportManager(new DefaultTransportManager(this.components, init.transportManager)) // Addresses {listen, announce, noAnnounce} - this.components.setAddressManager(this.configureComponent(new DefaultAddressManager(this.components, init.addresses))) + this.components.setAddressManager(new DefaultAddressManager(this.components, init.addresses)) // update our peer record when addresses change this.configureComponent(new PeerRecordUpdater(this.components)) - this.components.setDialer(this.configureComponent(new DefaultDialer(this.components, init.dialer))) - this.configureComponent(new AutoDialler(this.components, { enabled: init.connectionManager.autoDial, minConnections: init.connectionManager.minConnections, @@ -169,14 +174,14 @@ export class Libp2pNode extends EventEmitter implements Libp2p { // dht provided components (peerRouting, contentRouting, dht) if (init.dht != null) { - this.dht = this.components.setDHT(this.configureComponent(init.dht)) + this.dht = this.components.setDHT(init.dht) } else { this.dht = new DummyDHT() } // Create pubsub if provided if (init.pubsub != null) { - this.pubsub = this.components.setPubSub(this.configureComponent(init.pubsub)) + this.pubsub = this.components.setPubSub(init.pubsub) } else { this.pubsub = new DummyPubSub() } @@ -216,7 +221,7 @@ export class Libp2pNode extends EventEmitter implements Libp2p { this.components.getTransportManager().add(this.configureComponent(new Circuit())) this.configureComponent(new Relay(this.components, { - addressSorter: init.dialer.addressSorter, + addressSorter: init.connectionManager.addressSorter, ...init.relay })) } @@ -231,7 +236,8 @@ export class Libp2pNode extends EventEmitter implements Libp2p { const autoDialer = this.configureComponent(new AutoDialer(this.components, { enabled: init.connectionManager.autoDial !== false, - minConnections: init.connectionManager.minConnections ?? Infinity + minConnections: init.connectionManager.minConnections, + dialTimeout: init.connectionManager.dialTimeout ?? 30000 })) this.addEventListener('peer:discovery', evt => { @@ -379,24 +385,41 @@ export class Libp2pNode extends EventEmitter implements Libp2p { } getConnections (peerId?: PeerId): Connection[] { - if (peerId == null) { - return this.components.getConnectionManager().getConnectionList() - } - return this.components.getConnectionManager().getConnections(peerId) } getPeers (): PeerId[] { - return this.components.getConnectionManager().getConnectionList() - .map(conn => conn.remotePeer) + const peerSet = new PeerSet() + + for (const conn of this.components.getConnectionManager().getConnections()) { + peerSet.add(conn.remotePeer) + } + + return Array.from(peerSet) } async dial (peer: PeerId | Multiaddr, options: AbortOptions = {}): Promise { - return await this.components.getDialer().dial(peer, options) + const { id, multiaddrs } = getPeer(peer) + + await this.components.getPeerStore().addressBook.add(id, multiaddrs) + + return await this.components.getConnectionManager().openConnection(id, options) } async dialProtocol (peer: PeerId | Multiaddr, protocols: string | string[], options: AbortOptions = {}) { - return await this.components.getDialer().dialProtocol(peer, protocols, options) + if (protocols == null) { + throw errCode(new Error('no protocols were provided to open a stream'), codes.ERR_INVALID_PROTOCOLS_FOR_STREAM) + } + + protocols = Array.isArray(protocols) ? protocols : [protocols] + + if (protocols.length === 0) { + throw errCode(new Error('no protocols were provided to open a stream'), codes.ERR_INVALID_PROTOCOLS_FOR_STREAM) + } + + const connection = await this.dial(peer) + + return await connection.newStream(protocols) } getMultiaddrs (): Multiaddr[] { @@ -406,13 +429,7 @@ export class Libp2pNode extends EventEmitter implements Libp2p { async hangUp (peer: PeerId | Multiaddr | string): Promise { const { id } = getPeer(peer) - const connections = this.components.getConnectionManager().getConnections(id) - - await Promise.all( - connections.map(async connection => { - return await connection.close() - }) - ) + await this.components.getConnectionManager().closeConnections(id) } /** diff --git a/src/ping/index.ts b/src/ping/index.ts index 745d1dc404..72c909b72e 100644 --- a/src/ping/index.ts +++ b/src/ping/index.ts @@ -63,7 +63,8 @@ export class PingService implements Startable { async ping (peer: PeerId): Promise { log('dialing %s to %p', this.protocol, peer) - const { stream } = await this.components.getDialer().dialProtocol(peer, this.protocol) + const connection = await this.components.getConnectionManager().openConnection(peer) + const { stream } = await connection.newStream([this.protocol]) const start = Date.now() const data = randomBytes(PING_LENGTH) diff --git a/src/registrar.ts b/src/registrar.ts index 20d960f500..1e9e4d7c93 100644 --- a/src/registrar.ts +++ b/src/registrar.ts @@ -192,7 +192,7 @@ export class DefaultRegistrar implements Registrar { for (const { topology, protocols } of this.topologies.values()) { if (supportsProtocol(added, protocols)) { - const connection = this.components.getConnectionManager().getConnection(peerId) + const connection = this.components.getConnectionManager().getConnections(peerId)[0] if (connection == null) { continue diff --git a/test/configuration/pubsub.spec.ts b/test/configuration/pubsub.spec.ts index 080503b366..f3ca170f0d 100644 --- a/test/configuration/pubsub.spec.ts +++ b/test/configuration/pubsub.spec.ts @@ -40,12 +40,15 @@ describe('Pubsub subsystem is configurable', () => { }) libp2p = await createLibp2p(customOptions) + // @ts-expect-error not part of interface expect(libp2p.pubsub.isStarted()).to.equal(false) await libp2p.start() + // @ts-expect-error not part of interface expect(libp2p.pubsub.isStarted()).to.equal(true) await libp2p.stop() + // @ts-expect-error not part of interface expect(libp2p.pubsub.isStarted()).to.equal(false) }) }) diff --git a/test/connection-manager/auto-dialler.spec.ts b/test/connection-manager/auto-dialler.spec.ts index 066e535a7c..e0e360522c 100644 --- a/test/connection-manager/auto-dialler.spec.ts +++ b/test/connection-manager/auto-dialler.spec.ts @@ -7,9 +7,8 @@ import delay from 'delay' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { Components } from '@libp2p/interfaces/components' import { stubInterface } from 'ts-sinon' -import type { ConnectionManager } from '@libp2p/interfaces/registrar' +import type { ConnectionManager } from '@libp2p/interfaces/connection-manager' import type { PeerStore, Peer } from '@libp2p/interfaces/peer-store' -import type { Dialer } from '@libp2p/interfaces/dialer' describe('Auto-dialler', () => { it('should not dial self', async () => { @@ -36,26 +35,24 @@ describe('Auto-dialler', () => { ])) const connectionManager = stubInterface() - connectionManager.getConnectionList.returns([]) - const dialer = stubInterface() + connectionManager.getConnections.returns([]) const autoDialler = new AutoDialler(new Components({ peerId: self.id, peerStore, - connectionManager, - dialer + connectionManager }), { minConnections: 10 }) await autoDialler.start() - await pWaitFor(() => dialer.dial.callCount === 1) + await pWaitFor(() => connectionManager.openConnection.callCount === 1) await delay(1000) await autoDialler.stop() - expect(dialer.dial.callCount).to.equal(1) - expect(dialer.dial.calledWith(self.id)).to.be.false() + expect(connectionManager.openConnection.callCount).to.equal(1) + expect(connectionManager.openConnection.calledWith(self.id)).to.be.false() }) }) diff --git a/test/connection-manager/index.node.ts b/test/connection-manager/index.node.ts index 89d69d7291..5aaa18c551 100644 --- a/test/connection-manager/index.node.ts +++ b/test/connection-manager/index.node.ts @@ -18,6 +18,7 @@ import type { Connection } from '@libp2p/interfaces/connection' import delay from 'delay' import type { Libp2pNode } from '../../src/libp2p.js' import { codes } from '../../src/errors.js' +import { start } from '@libp2p/interface-compliance-tests' describe('Connection Manager', () => { let libp2p: Libp2p @@ -50,9 +51,14 @@ describe('Connection Manager', () => { const peerStore = stubInterface() peerStore.keyBook = stubInterface() - const connectionManager = new DefaultConnectionManager(new Components({ upgrader, peerStore })) + const connectionManager = new DefaultConnectionManager({ + maxConnections: 1000, + minConnections: 50, + autoDialInterval: 1000 + }) + connectionManager.init(new Components({ upgrader, peerStore })) - await connectionManager.start() + await start(connectionManager) const conn1 = await mockConnection(mockMultiaddrConnection(mockDuplex(), peerIds[1])) const conn2 = await mockConnection(mockMultiaddrConnection(mockDuplex(), peerIds[1])) @@ -80,9 +86,14 @@ describe('Connection Manager', () => { const peerStore = stubInterface() peerStore.keyBook = stubInterface() - const connectionManager = new DefaultConnectionManager(new Components({ upgrader, peerStore })) + const connectionManager = new DefaultConnectionManager({ + maxConnections: 1000, + minConnections: 50, + autoDialInterval: 1000 + }) + connectionManager.init(new Components({ upgrader, peerStore })) - await connectionManager.start() + await start(connectionManager) const conn1 = await mockConnection(mockMultiaddrConnection(mockDuplex(), peerIds[1])) const conn2 = await mockConnection(mockMultiaddrConnection(mockDuplex(), peerIds[1])) @@ -224,11 +235,11 @@ describe('libp2p.connections', () => { await libp2p.start() // Wait for peer to connect - await pWaitFor(() => libp2p.components.getConnectionManager().getConnectionMap().size === minConnections) + await pWaitFor(() => libp2p.components.getConnectionManager().getConnections().length === minConnections) // Wait more time to guarantee no other connection happened await delay(200) - expect(libp2p.components.getConnectionManager().getConnectionMap().size).to.eql(minConnections) + expect(libp2p.components.getConnectionManager().getConnections().length).to.eql(minConnections) await libp2p.stop() }) @@ -257,11 +268,11 @@ describe('libp2p.connections', () => { await libp2p.start() // Wait for peer to connect - await pWaitFor(() => libp2p.components.getConnectionManager().getConnectionMap().size === minConnections) + await pWaitFor(() => libp2p.components.getConnectionManager().getConnections().length === minConnections) // Should have connected to the peer with protocols - expect(libp2p.components.getConnectionManager().getConnection(nodes[0].peerId)).to.not.exist() - expect(libp2p.components.getConnectionManager().getConnection(nodes[1].peerId)).to.exist() + expect(libp2p.components.getConnectionManager().getConnections(nodes[0].peerId)).to.be.empty() + expect(libp2p.components.getConnectionManager().getConnections(nodes[1].peerId)).to.not.be.empty() await libp2p.stop() }) @@ -287,15 +298,15 @@ describe('libp2p.connections', () => { // Wait for peer to connect const conn = await libp2p.dial(nodes[0].peerId) - expect(libp2p.components.getConnectionManager().getConnection(nodes[0].peerId)).to.exist() + expect(libp2p.components.getConnectionManager().getConnections(nodes[0].peerId)).to.not.be.empty() await conn.close() // Closed - await pWaitFor(() => libp2p.components.getConnectionManager().getConnectionMap().size === 0) + await pWaitFor(() => libp2p.components.getConnectionManager().getConnections().length === 0) // Connected - await pWaitFor(() => libp2p.components.getConnectionManager().getConnectionMap().size === 1) + await pWaitFor(() => libp2p.components.getConnectionManager().getConnections().length === 1) - expect(libp2p.components.getConnectionManager().getConnection(nodes[0].peerId)).to.exist() + expect(libp2p.components.getConnectionManager().getConnections(nodes[0].peerId)).to.not.be.empty() await libp2p.stop() }) @@ -321,9 +332,7 @@ describe('libp2p.connections', () => { await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.getMultiaddrs()) await libp2p.dial(remoteLibp2p.peerId) - const totalConns = Array.from(libp2p.components.getConnectionManager().getConnectionMap().values()) - expect(totalConns.length).to.eql(1) - const conns = totalConns[0] + const conns = libp2p.components.getConnectionManager().getConnections() expect(conns.length).to.eql(1) const conn = conns[0] @@ -394,7 +403,7 @@ describe('libp2p.connections', () => { }) }) await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.getMultiaddrs()) - await libp2p.components.getDialer().dial(remoteLibp2p.peerId) + await libp2p.components.getConnectionManager().openConnection(remoteLibp2p.peerId) for (const multiaddr of remoteLibp2p.getMultiaddrs()) { expect(denyDialMultiaddr.calledWith(remoteLibp2p.peerId, multiaddr)).to.be.true() @@ -418,7 +427,7 @@ describe('libp2p.connections', () => { const fullMultiaddr = remoteLibp2p.getMultiaddrs()[0] - await libp2p.components.getDialer().dial(fullMultiaddr) + await libp2p.dial(fullMultiaddr) expect(filterMultiaddrForPeer.callCount).to.equal(2) diff --git a/test/connection-manager/index.spec.ts b/test/connection-manager/index.spec.ts index 85891f7eb9..10cd669e6b 100644 --- a/test/connection-manager/index.spec.ts +++ b/test/connection-manager/index.spec.ts @@ -79,7 +79,7 @@ describe('Connection Manager', () => { const value = Math.random() spies.set(value, spy) connectionManager.setPeerValue(connection.remotePeer, value) - await connectionManager.onConnect(new CustomEvent('connection', { detail: connection })) + await connectionManager._onConnect(new CustomEvent('connection', { detail: connection })) })) // get the lowest value @@ -122,7 +122,7 @@ describe('Connection Manager', () => { await Promise.all([...new Array(max + 1)].map(async () => { const connection = mockConnection(mockMultiaddrConnection(mockDuplex(), await createEd25519PeerId())) sinon.stub(connection, 'close').callsFake(async () => spy()) // eslint-disable-line - await connectionManager.onConnect(new CustomEvent('connection', { detail: connection })) + await connectionManager._onConnect(new CustomEvent('connection', { detail: connection })) })) expect(connectionManagerMaybeDisconnectOneSpy.callCount).to.equal(1) diff --git a/test/content-routing/dht/operation.node.ts b/test/content-routing/dht/operation.node.ts index f3fed7d2b7..e57a06f97a 100644 --- a/test/content-routing/dht/operation.node.ts +++ b/test/content-routing/dht/operation.node.ts @@ -8,7 +8,7 @@ import { subsystemMulticodecs, createSubsystemOptions } from './utils.js' import { createPeerId } from '../../utils/creators/peer.js' import type { PeerId } from '@libp2p/interfaces/peer-id' import { createLibp2pNode, Libp2pNode } from '../../../src/libp2p.js' -import { isStartable } from '@libp2p/interfaces' +import { start } from '@libp2p/interface-compliance-tests' const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/8000') const remoteListenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/8001') @@ -145,9 +145,7 @@ describe('DHT subsystem operates correctly', () => { const dht = remoteLibp2p.dht - if (isStartable(dht)) { - await dht.start() - } + await start(dht) // should be 0 directly after start - TODO this may be susceptible to timing bugs, we should have // the ability to report stats on the DHT routing table instead of reaching into it's heart like this @@ -164,9 +162,7 @@ describe('DHT subsystem operates correctly', () => { const dht = remoteLibp2p.dht - if (isStartable(dht)) { - await dht.start() - } + await start(dht) await pWaitFor(() => libp2p.dht.lan.routingTable.size === 1) await libp2p.components.getContentRouting().put(key, value) diff --git a/test/dialing/dial-request.spec.ts b/test/dialing/dial-request.spec.ts index c5b56371db..ea8c6f6e69 100644 --- a/test/dialing/dial-request.spec.ts +++ b/test/dialing/dial-request.spec.ts @@ -5,12 +5,11 @@ import sinon from 'sinon' import { AbortError } from '@libp2p/interfaces/errors' import pDefer from 'p-defer' import delay from 'delay' -import { DialAction, DialRequest } from '../../src/dialer/dial-request.js' +import { DialAction, DialRequest } from '../../src/connection-manager/dialer/dial-request.js' import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-compliance-tests/mocks' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { Multiaddr } from '@multiformats/multiaddr' -import { DefaultDialer } from '../../src/dialer/index.js' -import { Components } from '@libp2p/interfaces/components' +import { Dialer } from '../../src/connection-manager/dialer/index.js' const error = new Error('dial failure') describe('Dial Request', () => { @@ -23,7 +22,7 @@ describe('Dial Request', () => { } const dialAction: DialAction = async (num) => await actions[num.toString()]() const controller = new AbortController() - const dialer = new DefaultDialer(new Components(), { + const dialer = new Dialer({ maxParallelDials: 2 }) const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') @@ -56,7 +55,7 @@ describe('Dial Request', () => { } const dialAction: DialAction = async (num) => await actions[num.toString()]() const controller = new AbortController() - const dialer = new DefaultDialer(new Components(), { + const dialer = new Dialer({ maxParallelDials: 2 }) const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') @@ -99,7 +98,7 @@ describe('Dial Request', () => { const dialAction: DialAction = async (num) => await actions[num.toString()]() const addrs = Object.keys(actions) const controller = new AbortController() - const dialer = new DefaultDialer(new Components(), { + const dialer = new Dialer({ maxParallelDials: 2 }) const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') @@ -139,7 +138,7 @@ describe('Dial Request', () => { const dialAction: DialAction = async (num) => await actions[num.toString()]() const controller = new AbortController() - const dialer = new DefaultDialer(new Components(), { + const dialer = new Dialer({ maxParallelDials: 2 }) const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') @@ -185,7 +184,7 @@ describe('Dial Request', () => { const dialAction: DialAction = async (num) => await actions[num.toString()]() const addrs = Object.keys(actions) const controller = new AbortController() - const dialer = new DefaultDialer(new Components(), { + const dialer = new Dialer({ maxParallelDials: 2 }) const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') diff --git a/test/dialing/direct.node.ts b/test/dialing/direct.node.ts index a544b76838..9c84988307 100644 --- a/test/dialing/direct.node.ts +++ b/test/dialing/direct.node.ts @@ -17,7 +17,7 @@ import { Connection, isConnection } from '@libp2p/interfaces/connection' import { AbortError } from '@libp2p/interfaces/errors' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { MemoryDatastore } from 'datastore-core/memory' -import { DefaultDialer } from '../../src/dialer/index.js' +import { Dialer } from '../../src/connection-manager/dialer/index.js' import { DefaultAddressManager } from '../../src/address-manager/index.js' import { PersistentPeerStore } from '@libp2p/peer-store' import { DefaultTransportManager } from '../../src/transport-manager.js' @@ -25,7 +25,6 @@ import { codes as ErrorCodes } from '../../src/errors.js' import { mockConnectionGater, mockDuplex, mockMultiaddrConnection, mockUpgrader, mockConnection } from '@libp2p/interface-compliance-tests/mocks' import Peers from '../fixtures/peers.js' import { Components } from '@libp2p/interfaces/components' -import type { PeerStore } from '@libp2p/interfaces/peer-store' import { createFromJSON } from '@libp2p/peer-id-factory' import type { PeerId } from '@libp2p/interfaces/peer-id' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' @@ -40,7 +39,6 @@ const unsupportedAddr = new Multiaddr('/ip4/127.0.0.1/tcp/9999/ws/p2p/QmckxVrJw1 describe('Dialing (direct, TCP)', () => { let remoteTM: DefaultTransportManager let localTM: DefaultTransportManager - let peerStore: PeerStore let remoteAddr: Multiaddr let remoteComponents: Components let localComponents: Components @@ -55,17 +53,14 @@ describe('Dialing (direct, TCP)', () => { peerId: remotePeerId, datastore: new MemoryDatastore(), upgrader: mockUpgrader(), - connectionGater: mockConnectionGater() + connectionGater: mockConnectionGater(), + peerStore: new PersistentPeerStore() }) remoteComponents.setAddressManager(new DefaultAddressManager(remoteComponents, { listen: [ listenAddr.toString() ] })) - peerStore = new PersistentPeerStore(remoteComponents, { - addressFilter: remoteComponents.getConnectionGater().filterMultiaddrForPeer - }) - remoteComponents.setPeerStore(peerStore) remoteTM = new DefaultTransportManager(remoteComponents) remoteTM.add(new TCP()) @@ -75,10 +70,12 @@ describe('Dialing (direct, TCP)', () => { upgrader: mockUpgrader(), connectionGater: mockConnectionGater() }) - localComponents.setPeerStore(new PersistentPeerStore(localComponents, { - addressFilter: localComponents.getConnectionGater().filterMultiaddrForPeer + localComponents.setPeerStore(new PersistentPeerStore()) + localComponents.setConnectionManager(new DefaultConnectionManager({ + maxConnections: 100, + minConnections: 50, + autoDialInterval: 1000 })) - localComponents.setConnectionManager(new DefaultConnectionManager(localComponents)) localTM = new DefaultTransportManager(localComponents) localTM.add(new TCP()) @@ -97,7 +94,8 @@ describe('Dialing (direct, TCP)', () => { }) it('should be able to connect to a remote node via its multiaddr', async () => { - const dialer = new DefaultDialer(localComponents) + const dialer = new Dialer() + dialer.init(localComponents) const connection = await dialer.dial(remoteAddr) expect(connection).to.exist() @@ -105,7 +103,8 @@ describe('Dialing (direct, TCP)', () => { }) it('should fail to connect to an unsupported multiaddr', async () => { - const dialer = new DefaultDialer(localComponents) + const dialer = new Dialer() + dialer.init(localComponents) await expect(dialer.dial(unsupportedAddr)) .to.eventually.be.rejectedWith(Error) @@ -113,7 +112,8 @@ describe('Dialing (direct, TCP)', () => { }) it('should fail to connect if peer has no known addresses', async () => { - const dialer = new DefaultDialer(localComponents) + const dialer = new Dialer() + dialer.init(localComponents) const peerId = await createFromJSON(Peers[1]) await expect(dialer.dial(peerId)) @@ -124,7 +124,8 @@ describe('Dialing (direct, TCP)', () => { it('should be able to connect to a given peer id', async () => { await localComponents.getPeerStore().addressBook.set(remoteComponents.getPeerId(), remoteTM.getAddrs()) - const dialer = new DefaultDialer(localComponents) + const dialer = new Dialer() + dialer.init(localComponents) const connection = await dialer.dial(remoteComponents.getPeerId()) expect(connection).to.exist() @@ -134,7 +135,8 @@ describe('Dialing (direct, TCP)', () => { it('should fail to connect to a given peer with unsupported addresses', async () => { await localComponents.getPeerStore().addressBook.add(remoteComponents.getPeerId(), [unsupportedAddr]) - const dialer = new DefaultDialer(localComponents) + const dialer = new Dialer() + dialer.init(localComponents) await expect(dialer.dial(remoteComponents.getPeerId())) .to.eventually.be.rejectedWith(Error) @@ -147,7 +149,8 @@ describe('Dialing (direct, TCP)', () => { const peerId = await createFromJSON(Peers[1]) await localComponents.getPeerStore().addressBook.add(peerId, [...remoteAddrs, unsupportedAddr]) - const dialer = new DefaultDialer(localComponents) + const dialer = new Dialer() + dialer.init(localComponents) sinon.spy(localTM, 'dial') const connection = await dialer.dial(peerId) @@ -158,9 +161,10 @@ describe('Dialing (direct, TCP)', () => { }) it('should abort dials on queue task timeout', async () => { - const dialer = new DefaultDialer(localComponents, { + const dialer = new Dialer({ dialTimeout: 50 }) + dialer.init(localComponents) sinon.stub(localTM, 'dial').callsFake(async (addr, options = {}) => { expect(options.signal).to.exist() @@ -186,9 +190,10 @@ describe('Dialing (direct, TCP)', () => { await localComponents.getPeerStore().addressBook.add(peerId, addrs) - const dialer = new DefaultDialer(localComponents, { + const dialer = new Dialer({ maxParallelDials: 2 }) + dialer.init(localComponents) expect(dialer.tokens).to.have.lengthOf(2) @@ -298,7 +303,7 @@ describe('libp2p.dialer (direct, TCP)', () => { await libp2p.start() - const dialerDialSpy = sinon.spy(libp2p.components.getDialer(), 'dial') + const dialerDialSpy = sinon.spy(libp2p.components.getConnectionManager(), 'openConnection') const connection = await libp2p.dial(remoteAddr) expect(connection).to.exist() @@ -325,7 +330,7 @@ describe('libp2p.dialer (direct, TCP)', () => { await libp2p.start() - const dialerDialSpy = sinon.spy(libp2p.components.getDialer(), 'dial') + const dialerDialSpy = sinon.spy(libp2p.components.getConnectionManager(), 'openConnection') await libp2p.components.getPeerStore().addressBook.set(remotePeerId, remoteLibp2p.getMultiaddrs()) diff --git a/test/dialing/direct.spec.ts b/test/dialing/direct.spec.ts index 66430cd52d..444c9d4193 100644 --- a/test/dialing/direct.spec.ts +++ b/test/dialing/direct.spec.ts @@ -13,7 +13,7 @@ import { AbortError } from '@libp2p/interfaces/errors' import { MemoryDatastore } from 'datastore-core/memory' import { codes as ErrorCodes } from '../../src/errors.js' import * as Constants from '../../src/constants.js' -import { DefaultDialer, DialTarget } from '../../src/dialer/index.js' +import { Dialer, DialTarget } from '../../src/connection-manager/dialer/index.js' import { publicAddressesFirst } from '@libp2p/utils/address-sort' import { PersistentPeerStore } from '@libp2p/peer-store' import { DefaultTransportManager } from '../../src/transport-manager.js' @@ -46,10 +46,14 @@ describe('Dialing (direct, WebSockets)', () => { upgrader: mockUpgrader(), connectionGater: mockConnectionGater() }) - localComponents.setPeerStore(new PersistentPeerStore(localComponents, { + localComponents.setPeerStore(new PersistentPeerStore({ addressFilter: localComponents.getConnectionGater().filterMultiaddrForPeer })) - localComponents.setConnectionManager(new DefaultConnectionManager(localComponents)) + localComponents.setConnectionManager(new DefaultConnectionManager({ + maxConnections: 100, + minConnections: 50, + autoDialInterval: 1000 + })) localTM = new DefaultTransportManager(localComponents) localTM.add(new WebSockets({ filter: filters.all })) @@ -67,7 +71,8 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should limit the number of tokens it provides', () => { - const dialer = new DefaultDialer(localComponents) + const dialer = new Dialer() + dialer.init(localComponents) const maxPerPeer = Constants.MAX_PER_PEER_DIALS expect(dialer.tokens).to.have.lengthOf(Constants.MAX_PARALLEL_DIALS) @@ -77,9 +82,10 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should not return tokens if none are left', () => { - const dialer = new DefaultDialer(localComponents, { + const dialer = new Dialer({ maxDialsPerPeer: Infinity }) + dialer.init(localComponents) const maxTokens = dialer.tokens.length @@ -90,7 +96,8 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should NOT be able to return a token twice', () => { - const dialer = new DefaultDialer(localComponents) + const dialer = new Dialer() + dialer.init(localComponents) const tokens = dialer.getTokens(1) expect(tokens).to.have.length(1) @@ -101,7 +108,9 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should be able to connect to a remote node via its multiaddr', async () => { - const dialer = new DefaultDialer(localComponents) + const dialer = new Dialer() + dialer.init(localComponents) + const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') await localComponents.getPeerStore().addressBook.set(remotePeerId, [remoteAddr]) @@ -111,7 +120,8 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should fail to connect to an unsupported multiaddr', async () => { - const dialer = new DefaultDialer(localComponents) + const dialer = new Dialer() + dialer.init(localComponents) await expect(dialer.dial(unsupportedAddr.encapsulate(`/p2p/${remoteComponents.getPeerId().toString()}`))) .to.eventually.be.rejectedWith(Error) @@ -119,7 +129,9 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should be able to connect to a given peer', async () => { - const dialer = new DefaultDialer(localComponents) + const dialer = new Dialer() + dialer.init(localComponents) + const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') await localComponents.getPeerStore().addressBook.set(remotePeerId, [remoteAddr]) @@ -129,7 +141,9 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should fail to connect to a given peer with unsupported addresses', async () => { - const dialer = new DefaultDialer(localComponents) + const dialer = new Dialer() + dialer.init(localComponents) + const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') await localComponents.getPeerStore().addressBook.set(remotePeerId, [unsupportedAddr]) @@ -139,9 +153,11 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should abort dials on queue task timeout', async () => { - const dialer = new DefaultDialer(localComponents, { + const dialer = new Dialer({ dialTimeout: 50 }) + dialer.init(localComponents) + const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') await localComponents.getPeerStore().addressBook.set(remotePeerId, [remoteAddr]) @@ -160,9 +176,11 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should throw when a peer advertises more than the allowed number of peers', async () => { - const dialer = new DefaultDialer(localComponents, { + const dialer = new Dialer({ maxAddrsToDial: 10 }) + dialer.init(localComponents) + const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') await localComponents.getPeerStore().addressBook.set(remotePeerId, Array.from({ length: 11 }, (_, i) => new Multiaddr(`/ip4/127.0.0.1/tcp/1500${i}/ws/p2p/12D3KooWHFKTMzwerBtsVmtz4ZZEQy2heafxzWw6wNn5PPYkBxJ5`))) @@ -181,10 +199,11 @@ describe('Dialing (direct, WebSockets)', () => { const publicAddressesFirstSpy = sinon.spy(publicAddressesFirst) const localTMDialStub = sinon.stub(localTM, 'dial').callsFake(async (ma) => mockConnection(mockMultiaddrConnection(mockDuplex(), peerIdFromString(ma.getPeerId() ?? '')))) - const dialer = new DefaultDialer(localComponents, { + const dialer = new Dialer({ addressSorter: publicAddressesFirstSpy, maxParallelDials: 3 }) + dialer.init(localComponents) // Inject data in the AddressBook await localComponents.getPeerStore().addressBook.add(remoteComponents.getPeerId(), peerMultiaddrs) @@ -209,9 +228,10 @@ describe('Dialing (direct, WebSockets)', () => { ] const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') - const dialer = new DefaultDialer(localComponents, { + const dialer = new Dialer({ maxParallelDials: 2 }) + dialer.init(localComponents) // Inject data in the AddressBook await localComponents.getPeerStore().addressBook.add(remotePeerId, addrs) @@ -247,9 +267,10 @@ describe('Dialing (direct, WebSockets)', () => { new Multiaddr('/ip4/0.0.0.0/tcp/8001/ws'), new Multiaddr('/ip4/0.0.0.0/tcp/8002/ws') ] - const dialer = new DefaultDialer(localComponents, { + const dialer = new Dialer({ maxParallelDials: 2 }) + dialer.init(localComponents) // Inject data in the AddressBook await localComponents.getPeerStore().addressBook.add(remoteComponents.getPeerId(), addrs) @@ -287,7 +308,8 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should cancel pending dial targets before proceeding', async () => { - const dialer = new DefaultDialer(localComponents) + const dialer = new Dialer() + dialer.init(localComponents) sinon.stub(dialer, '_createDialTarget').callsFake(async () => { const deferredDial = pDefer() @@ -309,7 +331,7 @@ describe('Dialing (direct, WebSockets)', () => { }) describe('libp2p.dialer (direct, WebSockets)', () => { - const connectionGater = mockConnectionGater() + // const connectionGater = mockConnectionGater() let libp2p: Libp2pNode let peerId: PeerId @@ -338,14 +360,16 @@ describe('libp2p.dialer (direct, WebSockets)', () => { ], connectionEncryption: [ NOISE - ], - connectionGater + ] }) - expect(libp2p.components.getDialer()).to.exist() - expect(libp2p.components.getDialer()).to.have.property('tokens').with.lengthOf(Constants.MAX_PARALLEL_DIALS) - expect(libp2p.components.getDialer()).to.have.property('maxDialsPerPeer', Constants.MAX_PER_PEER_DIALS) - expect(libp2p.components.getDialer()).to.have.property('timeout', Constants.DIAL_TIMEOUT) + const connectionManager = libp2p.components.getConnectionManager() as DefaultConnectionManager + const dialer = connectionManager.dialer + + expect(dialer).to.exist() + expect(dialer).to.have.property('tokens').with.lengthOf(Constants.MAX_PARALLEL_DIALS) + expect(dialer).to.have.property('maxDialsPerPeer', Constants.MAX_PER_PEER_DIALS) + expect(dialer).to.have.property('timeout', Constants.DIAL_TIMEOUT) }) it('should be able to override dialer options', async () => { @@ -362,7 +386,7 @@ describe('libp2p.dialer (direct, WebSockets)', () => { connectionEncryption: [ NOISE ], - dialer: { + connectionManager: { maxParallelDials: 10, maxDialsPerPeer: 1, dialTimeout: 1e3 // 30 second dial timeout per peer @@ -370,10 +394,13 @@ describe('libp2p.dialer (direct, WebSockets)', () => { } libp2p = await createLibp2pNode(config) - expect(libp2p.components.getDialer()).to.exist() - expect(libp2p.components.getDialer()).to.have.property('tokens').with.lengthOf(config.dialer.maxParallelDials) - expect(libp2p.components.getDialer()).to.have.property('maxDialsPerPeer', config.dialer.maxDialsPerPeer) - expect(libp2p.components.getDialer()).to.have.property('timeout', config.dialer.dialTimeout) + const connectionManager = libp2p.components.getConnectionManager() as DefaultConnectionManager + const dialer = connectionManager.dialer + + expect(dialer).to.exist() + expect(dialer).to.have.property('tokens').with.lengthOf(config.connectionManager.maxParallelDials) + expect(dialer).to.have.property('maxDialsPerPeer', config.connectionManager.maxDialsPerPeer) + expect(dialer).to.have.property('timeout', config.connectionManager.dialTimeout) }) it('should use the dialer for connecting', async () => { @@ -392,7 +419,8 @@ describe('libp2p.dialer (direct, WebSockets)', () => { ] }) - const dialerDialSpy = sinon.spy(libp2p.components.getDialer(), 'dial') + const connectionManager = libp2p.components.getConnectionManager() as DefaultConnectionManager + const dialerDialSpy = sinon.spy(connectionManager.dialer, 'dial') const addressBookAddSpy = sinon.spy(libp2p.components.getPeerStore().addressBook, 'add') await libp2p.start() @@ -514,7 +542,8 @@ describe('libp2p.dialer (direct, WebSockets)', () => { ] }) - sinon.stub(libp2p.components.getDialer() as DefaultDialer, '_createDialTarget').callsFake(async () => { + const connectionManager = libp2p.components.getConnectionManager() as DefaultConnectionManager + sinon.stub(connectionManager.dialer, '_createDialTarget').callsFake(async () => { const deferredDial = pDefer() return await deferredDial.promise }) @@ -552,7 +581,8 @@ describe('libp2p.dialer (direct, WebSockets)', () => { await libp2p.start() - const dialerDestroyStub = sinon.spy(libp2p.components.getDialer() as DefaultDialer, 'stop') + const connectionManager = libp2p.components.getConnectionManager() as DefaultConnectionManager + const dialerDestroyStub = sinon.spy(connectionManager.dialer, 'stop') await libp2p.stop() diff --git a/test/dialing/resolver.spec.ts b/test/dialing/resolver.spec.ts index 1dead4ae7b..3e39b22702 100644 --- a/test/dialing/resolver.spec.ts +++ b/test/dialing/resolver.spec.ts @@ -46,18 +46,16 @@ describe('Dialing (resolvable addresses)', () => { listen: [`${relayAddr.toString()}/p2p-circuit`] }, connectionManager: { - autoDial: false + autoDial: false, + resolvers: { + dnsaddr: resolver + } }, relay: { enabled: true, hop: { enabled: false } - }, - dialer: { - resolvers: { - dnsaddr: resolver - } } }), started: true @@ -68,18 +66,16 @@ describe('Dialing (resolvable addresses)', () => { listen: [`${relayAddr.toString()}/p2p-circuit`] }, connectionManager: { - autoDial: false + autoDial: false, + resolvers: { + dnsaddr: resolver + } }, relay: { enabled: true, hop: { enabled: false } - }, - dialer: { - resolvers: { - dnsaddr: resolver - } } }), started: true diff --git a/test/identify/index.spec.ts b/test/identify/index.spec.ts index 9a2ab97266..228d6a6faf 100644 --- a/test/identify/index.spec.ts +++ b/test/identify/index.spec.ts @@ -27,13 +27,14 @@ import { } from '../../src/identify/consts.js' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' import { DefaultTransportManager } from '../../src/transport-manager.js' -import { CustomEvent, Startable } from '@libp2p/interfaces' +import { CustomEvent } from '@libp2p/interfaces' import delay from 'delay' import pWaitFor from 'p-wait-for' import { peerIdFromString } from '@libp2p/peer-id' import type { PeerId } from '@libp2p/interfaces/peer-id' import type { Libp2pNode } from '../../src/libp2p.js' import { pEvent } from 'p-event' +import { start, stop } from '@libp2p/interface-compliance-tests' const listenMaddrs = [new Multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] @@ -46,7 +47,7 @@ const defaultInit = { const protocols = [MULTICODEC_IDENTIFY, MULTICODEC_IDENTIFY_PUSH] -async function createComponents (index: number, services: Startable[]) { +async function createComponents (index: number) { const peerId = await createFromJSON(Peers[index]) const components = new Components({ @@ -54,25 +55,22 @@ async function createComponents (index: number, services: Startable[]) { datastore: new MemoryDatastore(), registrar: mockRegistrar(), upgrader: mockUpgrader(), - connectionGater: mockConnectionGater() - }) - const peerStore = new PersistentPeerStore(components, { - addressFilter: components.getConnectionGater().filterMultiaddrForPeer + connectionGater: mockConnectionGater(), + peerStore: new PersistentPeerStore(), + connectionManager: new DefaultConnectionManager({ + minConnections: 50, + maxConnections: 1000, + autoDialInterval: 1000 + }) }) - components.setPeerStore(peerStore) components.setAddressManager(new DefaultAddressManager(components, { announce: listenMaddrs.map(ma => ma.toString()) })) - const connectionManager = new DefaultConnectionManager(components) - services.push(connectionManager) - components.setConnectionManager(connectionManager) - const transportManager = new DefaultTransportManager(components) - services.push(transportManager) components.setTransportManager(transportManager) - await peerStore.protoBook.set(peerId, protocols) + await components.getPeerStore().protoBook.set(peerId, protocols) return components } @@ -83,36 +81,35 @@ describe('Identify', () => { let localPeerRecordUpdater: PeerRecordUpdater let remotePeerRecordUpdater: PeerRecordUpdater - let services: Startable[] beforeEach(async () => { - services = [] - - localComponents = await createComponents(0, services) - remoteComponents = await createComponents(1, services) + localComponents = await createComponents(0) + remoteComponents = await createComponents(1) localPeerRecordUpdater = new PeerRecordUpdater(localComponents) remotePeerRecordUpdater = new PeerRecordUpdater(remoteComponents) - await Promise.all( - services.map(s => s.start()) - ) + await Promise.all([ + start(localComponents), + start(remoteComponents) + ]) }) afterEach(async () => { sinon.restore() - await Promise.all( - services.map(s => s.stop()) - ) + await Promise.all([ + stop(localComponents), + stop(remoteComponents) + ]) }) it('should be able to identify another peer', async () => { const localIdentify = new IdentifyService(localComponents, defaultInit) const remoteIdentify = new IdentifyService(remoteComponents, defaultInit) - await localIdentify.start() - await remoteIdentify.start() + await start(localIdentify) + await start(remoteIdentify) const [localToRemote] = connectionPair(localComponents, remoteComponents) @@ -146,14 +143,14 @@ describe('Identify', () => { agentVersion: agentVersion } }) - await localIdentify.start() + await start(localIdentify) const remoteIdentify = new IdentifyService(remoteComponents, { protocolPrefix: 'ipfs', host: { agentVersion: agentVersion } }) - await remoteIdentify.start() + await start(remoteIdentify) const [localToRemote] = connectionPair(localComponents, remoteComponents) @@ -179,8 +176,8 @@ describe('Identify', () => { const localIdentify = new IdentifyService(localComponents, defaultInit) const remoteIdentify = new IdentifyService(remoteComponents, defaultInit) - await localIdentify.start() - await remoteIdentify.start() + await start(localIdentify) + await start(remoteIdentify) const [localToRemote] = connectionPair(localComponents, remoteComponents) @@ -231,14 +228,14 @@ describe('Identify', () => { await expect(localComponents.getPeerStore().metadataBook.getValue(localComponents.getPeerId(), 'ProtocolVersion')) .to.eventually.be.undefined() - await localIdentify.start() + await start(localIdentify) await expect(localComponents.getPeerStore().metadataBook.getValue(localComponents.getPeerId(), 'AgentVersion')) .to.eventually.deep.equal(uint8ArrayFromString(agentVersion)) await expect(localComponents.getPeerStore().metadataBook.getValue(localComponents.getPeerId(), 'ProtocolVersion')) .to.eventually.be.ok() - await localIdentify.stop() + await stop(localIdentify) }) describe('push', () => { @@ -246,8 +243,8 @@ describe('Identify', () => { const localIdentify = new IdentifyService(localComponents, defaultInit) const remoteIdentify = new IdentifyService(remoteComponents, defaultInit) - await localIdentify.start() - await remoteIdentify.start() + await start(localIdentify) + await start(remoteIdentify) const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) @@ -317,8 +314,8 @@ describe('Identify', () => { isCertified: true }]) - await localIdentify.stop() - await remoteIdentify.stop() + await stop(localIdentify) + await stop(remoteIdentify) }) // LEGACY @@ -326,8 +323,8 @@ describe('Identify', () => { const localIdentify = new IdentifyService(localComponents, defaultInit) const remoteIdentify = new IdentifyService(remoteComponents, defaultInit) - await localIdentify.start() - await remoteIdentify.start() + await start(localIdentify) + await start(remoteIdentify) const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) @@ -390,8 +387,8 @@ describe('Identify', () => { isCertified: false }]) - await localIdentify.stop() - await remoteIdentify.stop() + await stop(localIdentify) + await stop(remoteIdentify) }) }) diff --git a/test/metrics/index.node.ts b/test/metrics/index.node.ts index 0f382dc158..83f5783c8f 100644 --- a/test/metrics/index.node.ts +++ b/test/metrics/index.node.ts @@ -14,11 +14,16 @@ import type { DefaultMetrics } from '../../src/metrics/index.js' describe('libp2p.metrics', () => { let libp2p: Libp2pNode + let remoteLibp2p: Libp2pNode afterEach(async () => { if (libp2p != null) { await libp2p.stop() } + + if (remoteLibp2p != null) { + await remoteLibp2p.stop() + } }) it('should disable metrics by default', async () => { @@ -56,8 +61,7 @@ describe('libp2p.metrics', () => { }) it('should record metrics on connections and streams when enabled', async () => { - let remoteLibp2p: Libp2pNode - ;[libp2p, remoteLibp2p] = await Promise.all([ + [libp2p, remoteLibp2p] = await Promise.all([ createNode({ config: createBaseOptions({ metrics: { @@ -117,8 +121,7 @@ describe('libp2p.metrics', () => { }) it('should move disconnected peers to the old peers list', async () => { - let remoteLibp2p - ;[libp2p, remoteLibp2p] = await Promise.all([ + [libp2p, remoteLibp2p] = await Promise.all([ createNode({ config: createBaseOptions({ metrics: { diff --git a/test/peer-discovery/index.spec.ts b/test/peer-discovery/index.spec.ts index a7d7cf824d..83e81cfab6 100644 --- a/test/peer-discovery/index.spec.ts +++ b/test/peer-discovery/index.spec.ts @@ -9,6 +9,7 @@ import { createPeerId } from '../utils/creators/peer.js' import { isPeerId, PeerId } from '@libp2p/interfaces/peer-id' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-compliance-tests/mocks' +import type { Startable } from '@libp2p/interfaces' describe('peer discovery', () => { describe('basic functions', () => { @@ -42,7 +43,7 @@ describe('peer discovery', () => { await libp2p.peerStore.addressBook.set(remotePeerId, [new Multiaddr('/ip4/165.1.1.1/tcp/80')]) const deferred = defer() - sinon.stub(libp2p.components.getDialer(), 'dial').callsFake(async (id) => { + sinon.stub(libp2p.components.getConnectionManager(), 'openConnection').callsFake(async (id) => { if (!isPeerId(id)) { throw new Error('Tried to dial something that was not a peer ID') } @@ -69,13 +70,22 @@ describe('peer discovery', () => { let started = 0 let stopped = 0 - class MockDiscovery { + class MockDiscovery implements Startable { static tag = 'mock' + + started = false + + isStarted () { + return this.started + } + start () { + this.started = true started++ } stop () { + this.started = false stopped++ } diff --git a/test/registrar/registrar.spec.ts b/test/registrar/registrar.spec.ts index 25f49c73ab..a4837b7f6f 100644 --- a/test/registrar/registrar.spec.ts +++ b/test/registrar/registrar.spec.ts @@ -6,7 +6,7 @@ import { MemoryDatastore } from 'datastore-core/memory' import { createTopology } from '@libp2p/topology' import { PersistentPeerStore } from '@libp2p/peer-store' import { DefaultRegistrar } from '../../src/registrar.js' -import { mockConnectionGater, mockDuplex, mockMultiaddrConnection, mockUpgrader, mockConnection } from '@libp2p/interface-compliance-tests/mocks' +import { mockDuplex, mockMultiaddrConnection, mockUpgrader, mockConnection } from '@libp2p/interface-compliance-tests/mocks' import { createPeerId, createNode } from '../utils/creators/peer.js' import { createBaseOptions } from '../utils/base-options.browser.js' import type { Registrar } from '@libp2p/interfaces/registrar' @@ -24,7 +24,6 @@ import { Mplex } from '@libp2p/mplex' const protocol = '/test/1.0.0' describe('registrar', () => { - const connectionGater = mockConnectionGater() let components: Components let registrar: Registrar let peerId: PeerId @@ -38,12 +37,14 @@ describe('registrar', () => { components = new Components({ peerId, datastore: new MemoryDatastore(), - upgrader: mockUpgrader() + upgrader: mockUpgrader(), + peerStore: new PersistentPeerStore(), + connectionManager: new DefaultConnectionManager({ + minConnections: 50, + maxConnections: 1000, + autoDialInterval: 1000 + }) }) - components.setPeerStore(new PersistentPeerStore(components, { - addressFilter: connectionGater.filterMultiaddrForPeer - })) - components.setConnectionManager(new DefaultConnectionManager(components)) registrar = new DefaultRegistrar(components) }) diff --git a/test/relay/auto-relay.node.ts b/test/relay/auto-relay.node.ts index 8b3b2a4637..94dd19e429 100644 --- a/test/relay/auto-relay.node.ts +++ b/test/relay/auto-relay.node.ts @@ -263,7 +263,7 @@ describe('auto-relay', () => { await relayLibp2p1.hangUp(relayLibp2p3.peerId) // Stub dial - sinon.stub(relayLibp2p1.components.getDialer(), 'dial').callsFake(async () => { + sinon.stub(relayLibp2p1.components.getConnectionManager(), 'openConnection').callsFake(async () => { deferred.resolve() return await Promise.reject(new Error('failed to dial')) }) diff --git a/test/relay/relay.node.ts b/test/relay/relay.node.ts index aaf82796e5..8b33ff07bb 100644 --- a/test/relay/relay.node.ts +++ b/test/relay/relay.node.ts @@ -118,8 +118,8 @@ describe('Dialing (via relay, TCP)', () => { .and.to.have.nested.property('.errors[0].code', Errors.ERR_HOP_REQUEST_FAILED) // We should not be connected to the relay, because we weren't before the dial - const srcToRelayConn = srcLibp2p.components.getConnectionManager().getConnection(relayLibp2p.peerId) - expect(srcToRelayConn).to.not.exist() + const srcToRelayConns = srcLibp2p.components.getConnectionManager().getConnections(relayLibp2p.peerId) + expect(srcToRelayConns).to.be.empty() }) it('dialer should stay connected to an already connected relay on hop failure', async () => { @@ -135,9 +135,9 @@ describe('Dialing (via relay, TCP)', () => { .to.eventually.be.rejected() .and.to.have.nested.property('.errors[0].code', Errors.ERR_HOP_REQUEST_FAILED) - const srcToRelayConn = srcLibp2p.components.getConnectionManager().getConnection(relayLibp2p.peerId) - expect(srcToRelayConn).to.exist() - expect(srcToRelayConn?.stat.status).to.equal('OPEN') + const srcToRelayConn = srcLibp2p.components.getConnectionManager().getConnections(relayLibp2p.peerId) + expect(srcToRelayConn).to.have.lengthOf(1) + expect(srcToRelayConn).to.have.nested.property('[0].stat.status', 'OPEN') }) it('destination peer should stay connected to an already connected relay on hop failure', async () => { @@ -166,8 +166,8 @@ describe('Dialing (via relay, TCP)', () => { streamHandler.close() // should still be connected - const dstToRelayConn = dstLibp2p.components.getConnectionManager().getConnection(relayLibp2p.peerId) - expect(dstToRelayConn).to.exist() - expect(dstToRelayConn?.stat.status).to.equal('OPEN') + const dstToRelayConn = dstLibp2p.components.getConnectionManager().getConnections(relayLibp2p.peerId) + expect(dstToRelayConn).to.have.lengthOf(1) + expect(dstToRelayConn).to.have.nested.property('[0].stat.status', 'OPEN') }) }) diff --git a/test/transports/transport-manager.node.ts b/test/transports/transport-manager.node.ts index fd931bb9c0..ce0f57e938 100644 --- a/test/transports/transport-manager.node.ts +++ b/test/transports/transport-manager.node.ts @@ -8,7 +8,7 @@ import { PersistentPeerStore } from '@libp2p/peer-store' import { PeerRecord } from '@libp2p/peer-record' import { TCP } from '@libp2p/tcp' import { Multiaddr } from '@multiformats/multiaddr' -import { mockUpgrader, mockConnectionGater } from '@libp2p/interface-compliance-tests/mocks' +import { mockUpgrader } from '@libp2p/interface-compliance-tests/mocks' import sinon from 'sinon' import Peers from '../fixtures/peers.js' import pWaitFor from 'p-wait-for' @@ -23,7 +23,6 @@ const addrs = [ ] describe('Transport Manager (TCP)', () => { - const connectionGater = mockConnectionGater() let tm: DefaultTransportManager let localPeer: PeerId let components: Components @@ -39,9 +38,7 @@ describe('Transport Manager (TCP)', () => { upgrader: mockUpgrader() }) components.setAddressManager(new DefaultAddressManager(components, { listen: addrs.map(addr => addr.toString()) })) - components.setPeerStore(new PersistentPeerStore(components, { - addressFilter: connectionGater.filterMultiaddrForPeer - })) + components.setPeerStore(new PersistentPeerStore()) tm = new DefaultTransportManager(components) From da3d19b30977fd2c7e77d92aa8914b13e3179aaa Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 4 May 2022 16:03:43 +0100 Subject: [PATCH 359/447] fix: update interfaces (#1207) Update to the latest interfaces version --- examples/delegated-routing/package.json | 10 +++++----- examples/libp2p-in-the-browser/package.json | 10 +++++----- examples/package.json | 2 +- examples/webrtc-direct/package.json | 8 ++++---- package.json | 20 +++++++++---------- src/address-manager/index.ts | 3 ++- src/circuit/index.ts | 2 +- src/circuit/listener.ts | 2 +- src/connection-manager/auto-dialler.ts | 2 +- src/connection-manager/dialer/index.ts | 3 ++- src/connection-manager/index.ts | 4 +++- src/connection-manager/latency-monitor.ts | 2 +- .../visibility-change-emitter.ts | 2 +- src/content-routing/index.ts | 3 ++- src/dht/dummy-dht.ts | 2 +- src/fetch/index.ts | 2 +- src/identify/index.ts | 2 +- src/index.ts | 4 +++- src/libp2p.ts | 4 +++- src/metrics/index.ts | 2 +- src/metrics/stats.ts | 2 +- src/nat-manager.ts | 2 +- src/peer-record-updater.ts | 2 +- src/peer-routing.ts | 3 ++- src/ping/index.ts | 2 +- src/pubsub/dummy-pubsub.ts | 2 +- src/transport-manager.ts | 4 +++- src/upgrader.ts | 2 +- test/connection-manager/index.node.ts | 4 ++-- test/connection-manager/index.spec.ts | 2 +- test/content-routing/dht/operation.node.ts | 2 +- test/identify/index.spec.ts | 4 ++-- test/peer-discovery/index.node.ts | 2 +- test/peer-discovery/index.spec.ts | 2 +- test/registrar/registrar.spec.ts | 2 +- 35 files changed, 69 insertions(+), 57 deletions(-) diff --git a/examples/delegated-routing/package.json b/examples/delegated-routing/package.json index acdbb03590..22cd66fb05 100644 --- a/examples/delegated-routing/package.json +++ b/examples/delegated-routing/package.json @@ -3,15 +3,15 @@ "version": "0.1.0", "private": true, "dependencies": { - "@chainsafe/libp2p-noise": "^6.0.1", + "@chainsafe/libp2p-noise": "^6.1.1", "ipfs-core": "^0.14.1", "libp2p": "../../", "@libp2p/delegated-content-routing": "^1.0.1", "@libp2p/delegated-peer-routing": "^1.0.1", - "@libp2p/kad-dht": "^1.0.1", - "@libp2p/mplex": "^1.0.2", - "@libp2p/webrtc-star": "^1.0.6", - "@libp2p/websockets": "^1.0.3", + "@libp2p/kad-dht": "^1.0.9", + "@libp2p/mplex": "^1.0.4", + "@libp2p/webrtc-star": "^1.0.8", + "@libp2p/websockets": "^1.0.7", "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "5.0.0" diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json index f8ee1eef53..c182fea02a 100644 --- a/examples/libp2p-in-the-browser/package.json +++ b/examples/libp2p-in-the-browser/package.json @@ -9,11 +9,11 @@ }, "license": "ISC", "dependencies": { - "@chainsafe/libp2p-noise": "^6.0.1", - "@libp2p/bootstrap": "^1.0.1", - "@libp2p/mplex": "^1.0.2", - "@libp2p/webrtc-star": "^1.0.6", - "@libp2p/websockets": "^1.0.3", + "@chainsafe/libp2p-noise": "^6.1.1", + "@libp2p/bootstrap": "^1.0.4", + "@libp2p/mplex": "^1.0.4", + "@libp2p/webrtc-star": "^1.0.8", + "@libp2p/websockets": "^1.0.7", "libp2p": "../../" }, "devDependencies": { diff --git a/examples/package.json b/examples/package.json index c94da14d0b..ddeef8ea95 100644 --- a/examples/package.json +++ b/examples/package.json @@ -9,7 +9,7 @@ }, "license": "MIT", "dependencies": { - "@libp2p/pubsub-peer-discovery": "^5.0.1", + "@libp2p/pubsub-peer-discovery": "^5.0.2", "@libp2p/floodsub": "^1.0.6", "execa": "^2.1.0", "fs-extra": "^8.1.0", diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index 57e79c6e36..a888ea7fde 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -9,10 +9,10 @@ }, "license": "ISC", "dependencies": { - "@libp2p/webrtc-direct": "^1.0.0", - "@chainsafe/libp2p-noise": "^6.0.1", - "@libp2p/bootstrap": "^1.0.1", - "@libp2p/mplex": "^1.0.2", + "@libp2p/webrtc-direct": "^1.0.1", + "@chainsafe/libp2p-noise": "^6.1.1", + "@libp2p/bootstrap": "^1.0.4", + "@libp2p/mplex": "^1.0.4", "libp2p": "../../", "wrtc": "^0.4.7" }, diff --git a/package.json b/package.json index 07f344c8aa..b4be4f3863 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "@achingbrain/nat-port-mapper": "^1.0.0", "@libp2p/connection": "^1.1.5", "@libp2p/crypto": "^0.22.11", - "@libp2p/interfaces": "^1.3.30", + "@libp2p/interfaces": "^1.3.31", "@libp2p/logger": "^1.1.4", "@libp2p/multistream-select": "^1.0.4", "@libp2p/peer-collections": "^1.0.2", @@ -157,23 +157,23 @@ "xsalsa20": "^1.1.0" }, "devDependencies": { - "@chainsafe/libp2p-noise": "^6.0.1", - "@libp2p/bootstrap": "^1.0.3", + "@chainsafe/libp2p-noise": "^6.1.1", + "@libp2p/bootstrap": "^1.0.4", "@libp2p/daemon-client": "^1.0.2", "@libp2p/daemon-server": "^1.0.2", "@libp2p/delegated-content-routing": "^1.0.2", "@libp2p/delegated-peer-routing": "^1.0.2", "@libp2p/floodsub": "^1.0.6", - "@libp2p/interface-compliance-tests": "^1.1.31", + "@libp2p/interface-compliance-tests": "^1.1.32", "@libp2p/interop": "^1.0.3", - "@libp2p/kad-dht": "^1.0.8", - "@libp2p/mdns": "^1.0.4", - "@libp2p/mplex": "^1.0.3", + "@libp2p/kad-dht": "^1.0.9", + "@libp2p/mdns": "^1.0.5", + "@libp2p/mplex": "^1.0.4", "@libp2p/pubsub": "^1.2.18", - "@libp2p/tcp": "^1.0.8", + "@libp2p/tcp": "^1.0.9", "@libp2p/topology": "^1.1.7", - "@libp2p/webrtc-star": "^1.0.7", - "@libp2p/websockets": "^1.0.6", + "@libp2p/webrtc-star": "^1.0.8", + "@libp2p/websockets": "^1.0.7", "@nodeutils/defaults-deep": "^1.1.0", "@types/node": "^16.11.26", "@types/node-forge": "^1.0.0", diff --git a/src/address-manager/index.ts b/src/address-manager/index.ts index d5ccdd0267..00634a217d 100644 --- a/src/address-manager/index.ts +++ b/src/address-manager/index.ts @@ -1,4 +1,5 @@ -import { AddressManagerEvents, CustomEvent, EventEmitter } from '@libp2p/interfaces' +import type { AddressManagerEvents } from '@libp2p/interfaces/address-manager' +import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events' import { Multiaddr } from '@multiformats/multiaddr' import { peerIdFromString } from '@libp2p/peer-id' import type { Components } from '@libp2p/interfaces/components' diff --git a/src/circuit/index.ts b/src/circuit/index.ts index 8bd66808b1..0771a398e2 100644 --- a/src/circuit/index.ts +++ b/src/circuit/index.ts @@ -11,7 +11,7 @@ import { RELAY_RENDEZVOUS_NS } from './constants.js' import type { AddressSorter } from '@libp2p/interfaces/peer-store' -import type { Startable } from '@libp2p/interfaces' +import type { Startable } from '@libp2p/interfaces/startable' import type { Components } from '@libp2p/interfaces/components' const log = logger('libp2p:relay') diff --git a/src/circuit/listener.ts b/src/circuit/listener.ts index 495458771f..e70ef863d1 100644 --- a/src/circuit/listener.ts +++ b/src/circuit/listener.ts @@ -1,4 +1,4 @@ -import { CustomEvent, EventEmitter } from '@libp2p/interfaces' +import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events' import type { ConnectionManager } from '@libp2p/interfaces/connection-manager' import type { PeerStore } from '@libp2p/interfaces/peer-store' import type { Listener } from '@libp2p/interfaces/transport' diff --git a/src/connection-manager/auto-dialler.ts b/src/connection-manager/auto-dialler.ts index 523a710479..8a4e646e37 100644 --- a/src/connection-manager/auto-dialler.ts +++ b/src/connection-manager/auto-dialler.ts @@ -6,7 +6,7 @@ import all from 'it-all' import { pipe } from 'it-pipe' import filter from 'it-filter' import sort from 'it-sort' -import type { Startable } from '@libp2p/interfaces' +import type { Startable } from '@libp2p/interfaces/startable' import type { Components } from '@libp2p/interfaces/components' const log = logger('libp2p:connection-manager:auto-dialler') diff --git a/src/connection-manager/dialer/index.ts b/src/connection-manager/dialer/index.ts index 1e54f184de..e0688534bc 100644 --- a/src/connection-manager/dialer/index.ts +++ b/src/connection-manager/dialer/index.ts @@ -20,7 +20,8 @@ import { MAX_ADDRS_TO_DIAL } from '../../constants.js' import type { Connection } from '@libp2p/interfaces/connection' -import type { AbortOptions, Startable } from '@libp2p/interfaces' +import type { AbortOptions } from '@libp2p/interfaces' +import type { Startable } from '@libp2p/interfaces/startable' import type { PeerId } from '@libp2p/interfaces/peer-id' import { getPeer } from '../../get-peer.js' import sort from 'it-sort' diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index b6ca836490..3c207bc4e1 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -4,7 +4,9 @@ import mergeOptions from 'merge-options' import { LatencyMonitor, SummaryObject } from './latency-monitor.js' // @ts-expect-error retimer does not have types import retimer from 'retimer' -import { AbortOptions, CustomEvent, EventEmitter, Startable } from '@libp2p/interfaces' +import type { AbortOptions } from '@libp2p/interfaces' +import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events' +import type { Startable } from '@libp2p/interfaces/startable' import { trackedMap } from '@libp2p/tracked-map' import { codes } from '../errors.js' import { isPeerId, PeerId } from '@libp2p/interfaces/peer-id' diff --git a/src/connection-manager/latency-monitor.ts b/src/connection-manager/latency-monitor.ts index bb7ce62633..3d975cc8fd 100644 --- a/src/connection-manager/latency-monitor.ts +++ b/src/connection-manager/latency-monitor.ts @@ -2,7 +2,7 @@ * This code is based on `latency-monitor` (https://github.com/mlucool/latency-monitor) by `mlucool` (https://github.com/mlucool), available under Apache License 2.0 (https://github.com/mlucool/latency-monitor/blob/master/LICENSE) */ -import { CustomEvent, EventEmitter } from '@libp2p/interfaces' +import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events' import { VisibilityChangeEmitter } from './visibility-change-emitter.js' import { logger } from '@libp2p/logger' diff --git a/src/connection-manager/visibility-change-emitter.ts b/src/connection-manager/visibility-change-emitter.ts index 0eddbf39b2..1e0d2ab61e 100644 --- a/src/connection-manager/visibility-change-emitter.ts +++ b/src/connection-manager/visibility-change-emitter.ts @@ -2,7 +2,7 @@ * This code is based on `latency-monitor` (https://github.com/mlucool/latency-monitor) by `mlucool` (https://github.com/mlucool), available under Apache License 2.0 (https://github.com/mlucool/latency-monitor/blob/master/LICENSE) */ -import { CustomEvent, EventEmitter } from '@libp2p/interfaces' +import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events' import { logger } from '@libp2p/logger' const log = logger('libp2p:connection-manager:latency-monitor:visibility-change-emitter') diff --git a/src/content-routing/index.ts b/src/content-routing/index.ts index a52450c9f1..b84069847d 100644 --- a/src/content-routing/index.ts +++ b/src/content-routing/index.ts @@ -9,7 +9,8 @@ import drain from 'it-drain' import merge from 'it-merge' import { pipe } from 'it-pipe' import type { ContentRouting } from '@libp2p/interfaces/content-routing' -import type { AbortOptions, Startable } from '@libp2p/interfaces' +import type { AbortOptions } from '@libp2p/interfaces' +import type { Startable } from '@libp2p/interfaces/startable' import type { CID } from 'multiformats/cid' import type { Components } from '@libp2p/interfaces/components' diff --git a/src/dht/dummy-dht.ts b/src/dht/dummy-dht.ts index 77a9c4a3df..6fd899f559 100644 --- a/src/dht/dummy-dht.ts +++ b/src/dht/dummy-dht.ts @@ -2,7 +2,7 @@ import type { DualDHT, QueryEvent, SingleDHT } from '@libp2p/interfaces/dht' import type { PeerDiscoveryEvents } from '@libp2p/interfaces/peer-discovery' import errCode from 'err-code' import { messages, codes } from '../errors.js' -import { EventEmitter } from '@libp2p/interfaces' +import { EventEmitter } from '@libp2p/interfaces/events' export class DummyDHT extends EventEmitter implements DualDHT { get wan (): SingleDHT { diff --git a/src/fetch/index.ts b/src/fetch/index.ts index 4e4e54fdf9..5ae9b67c69 100644 --- a/src/fetch/index.ts +++ b/src/fetch/index.ts @@ -6,7 +6,7 @@ import { FetchRequest, FetchResponse } from './pb/proto.js' import { handshake } from 'it-handshake' import { PROTOCOL } from './constants.js' import type { PeerId } from '@libp2p/interfaces/peer-id' -import type { Startable } from '@libp2p/interfaces' +import type { Startable } from '@libp2p/interfaces/startable' import type { Stream } from '@libp2p/interfaces/connection' import type { IncomingStreamData } from '@libp2p/interfaces/registrar' import type { Components } from '@libp2p/interfaces/components' diff --git a/src/identify/index.ts b/src/identify/index.ts index 879b9ca69c..023c289c16 100644 --- a/src/identify/index.ts +++ b/src/identify/index.ts @@ -22,7 +22,7 @@ import { import { codes } from '../errors.js' import type { IncomingStreamData } from '@libp2p/interfaces/registrar' import type { Connection } from '@libp2p/interfaces/connection' -import type { Startable } from '@libp2p/interfaces' +import type { Startable } from '@libp2p/interfaces/startable' import { peerIdFromKeys } from '@libp2p/peer-id' import type { Components } from '@libp2p/interfaces/components' diff --git a/src/index.ts b/src/index.ts index 94e236f17f..7d411bed7d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,7 @@ import { createLibp2pNode } from './libp2p.js' -import type { AbortOptions, EventEmitter, RecursivePartial, Startable } from '@libp2p/interfaces' +import type { AbortOptions, RecursivePartial } from '@libp2p/interfaces' +import type { EventEmitter } from '@libp2p/interfaces/events' +import type { Startable } from '@libp2p/interfaces/startable' import type { Multiaddr } from '@multiformats/multiaddr' import type { FaultTolerance } from './transport-manager.js' import type { HostProperties } from './identify/index.js' diff --git a/src/libp2p.ts b/src/libp2p.ts index 1f4e23eb46..611dbe102e 100644 --- a/src/libp2p.ts +++ b/src/libp2p.ts @@ -1,5 +1,7 @@ import { logger } from '@libp2p/logger' -import { AbortOptions, EventEmitter, Startable, CustomEvent, isStartable } from '@libp2p/interfaces' +import type { AbortOptions } from '@libp2p/interfaces' +import { EventEmitter, CustomEvent } from '@libp2p/interfaces/events' +import { Startable, isStartable } from '@libp2p/interfaces/startable' import type { Multiaddr } from '@multiformats/multiaddr' import { MemoryDatastore } from 'datastore-core/memory' import { DefaultPeerRouting } from './peer-routing.js' diff --git a/src/metrics/index.ts b/src/metrics/index.ts index bf632fa2b2..47b560c217 100644 --- a/src/metrics/index.ts +++ b/src/metrics/index.ts @@ -5,7 +5,7 @@ import { METRICS as defaultOptions } from '../constants.js' import { DefaultStats, StatsInit } from './stats.js' import type { ComponentMetricsUpdate, Metrics, Stats, TrackStreamOptions } from '@libp2p/interfaces/metrics' import type { PeerId } from '@libp2p/interfaces/peer-id' -import type { Startable } from '@libp2p/interfaces' +import type { Startable } from '@libp2p/interfaces/startable' import type { Duplex } from 'it-stream-types' const initialCounters: ['dataReceived', 'dataSent'] = [ diff --git a/src/metrics/stats.ts b/src/metrics/stats.ts index 9806ab61ec..2e50bff884 100644 --- a/src/metrics/stats.ts +++ b/src/metrics/stats.ts @@ -1,4 +1,4 @@ -import { CustomEvent, EventEmitter } from '@libp2p/interfaces' +import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events' import { createMovingAverage } from './moving-average.js' // @ts-expect-error no types import retimer from 'retimer' diff --git a/src/nat-manager.ts b/src/nat-manager.ts index 978efa647b..0d53fdfe65 100644 --- a/src/nat-manager.ts +++ b/src/nat-manager.ts @@ -7,7 +7,7 @@ import * as pkg from './version.js' import errCode from 'err-code' import { codes } from './errors.js' import { isLoopback } from '@libp2p/utils/multiaddr/is-loopback' -import type { Startable } from '@libp2p/interfaces' +import type { Startable } from '@libp2p/interfaces/startable' import type { Components } from '@libp2p/interfaces/components' const log = logger('libp2p:nat') diff --git a/src/peer-record-updater.ts b/src/peer-record-updater.ts index 340508a675..9a38db8b76 100644 --- a/src/peer-record-updater.ts +++ b/src/peer-record-updater.ts @@ -1,6 +1,6 @@ import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record' import type { Components } from '@libp2p/interfaces/components' -import type { Startable } from '@libp2p/interfaces' +import type { Startable } from '@libp2p/interfaces/startable' import { logger } from '@libp2p/logger' import { protocols } from '@multiformats/multiaddr' diff --git a/src/peer-routing.ts b/src/peer-routing.ts index 4e95ff5cca..7f0b459136 100644 --- a/src/peer-routing.ts +++ b/src/peer-routing.ts @@ -21,7 +21,8 @@ import { import { setMaxListeners } from 'events' import type { PeerId } from '@libp2p/interfaces/peer-id' import type { PeerRouting } from '@libp2p/interfaces/peer-routing' -import type { AbortOptions, Startable } from '@libp2p/interfaces' +import type { AbortOptions } from '@libp2p/interfaces' +import type { Startable } from '@libp2p/interfaces/startable' import type { PeerInfo } from '@libp2p/interfaces/peer-info' import type { Components } from '@libp2p/interfaces/components' diff --git a/src/ping/index.ts b/src/ping/index.ts index 72c909b72e..03cceffe80 100644 --- a/src/ping/index.ts +++ b/src/ping/index.ts @@ -8,7 +8,7 @@ import { equals as uint8ArrayEquals } from 'uint8arrays/equals' import { PROTOCOL_NAME, PING_LENGTH, PROTOCOL_VERSION } from './constants.js' import type { IncomingStreamData } from '@libp2p/interfaces/registrar' import type { PeerId } from '@libp2p/interfaces/peer-id' -import type { Startable } from '@libp2p/interfaces' +import type { Startable } from '@libp2p/interfaces/startable' import type { Components } from '@libp2p/interfaces/components' const log = logger('libp2p:ping') diff --git a/src/pubsub/dummy-pubsub.ts b/src/pubsub/dummy-pubsub.ts index 15fe053679..b44efcb509 100644 --- a/src/pubsub/dummy-pubsub.ts +++ b/src/pubsub/dummy-pubsub.ts @@ -1,4 +1,4 @@ -import { EventEmitter } from '@libp2p/interfaces' +import { EventEmitter } from '@libp2p/interfaces/events' import type { PeerId } from '@libp2p/interfaces/peer-id' import type { PublishResult, PubSub, PubSubEvents, StrictNoSign, StrictSign } from '@libp2p/interfaces/pubsub' import errCode from 'err-code' diff --git a/src/transport-manager.ts b/src/transport-manager.ts index 2eab5cb733..ba76aa780b 100644 --- a/src/transport-manager.ts +++ b/src/transport-manager.ts @@ -5,7 +5,9 @@ import errCode from 'err-code' import type { Listener, Transport, TransportManager, TransportManagerEvents } from '@libp2p/interfaces/transport' import type { Multiaddr } from '@multiformats/multiaddr' import type { Connection } from '@libp2p/interfaces/connection' -import { AbortOptions, CustomEvent, EventEmitter, Startable } from '@libp2p/interfaces' +import type { AbortOptions } from '@libp2p/interfaces' +import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events' +import type { Startable } from '@libp2p/interfaces/startable' import type { Components } from '@libp2p/interfaces/components' import { trackedMap } from '@libp2p/tracked-map' diff --git a/src/upgrader.ts b/src/upgrader.ts index 12e6ee7712..109f2c2e80 100644 --- a/src/upgrader.ts +++ b/src/upgrader.ts @@ -6,7 +6,7 @@ import { pipe } from 'it-pipe' import mutableProxy from 'mutable-proxy' import { codes } from './errors.js' import { createConnection } from '@libp2p/connection' -import { CustomEvent, EventEmitter } from '@libp2p/interfaces' +import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events' import { peerIdFromString } from '@libp2p/peer-id' import type { Connection, ProtocolStream, Stream } from '@libp2p/interfaces/connection' import type { ConnectionEncrypter, SecuredConnection } from '@libp2p/interfaces/connection-encrypter' diff --git a/test/connection-manager/index.node.ts b/test/connection-manager/index.node.ts index 5aaa18c551..9c053b0ec5 100644 --- a/test/connection-manager/index.node.ts +++ b/test/connection-manager/index.node.ts @@ -8,7 +8,7 @@ import type { Libp2p } from '../../src/index.js' import type { PeerId } from '@libp2p/interfaces/peer-id' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' import { Components } from '@libp2p/interfaces/components' -import { CustomEvent } from '@libp2p/interfaces' +import { CustomEvent } from '@libp2p/interfaces/events' import * as STATUS from '@libp2p/interfaces/connection/status' import { stubInterface } from 'ts-sinon' import type { KeyBook, PeerStore } from '@libp2p/interfaces/peer-store' @@ -18,7 +18,7 @@ import type { Connection } from '@libp2p/interfaces/connection' import delay from 'delay' import type { Libp2pNode } from '../../src/libp2p.js' import { codes } from '../../src/errors.js' -import { start } from '@libp2p/interface-compliance-tests' +import { start } from '@libp2p/interfaces/startable' describe('Connection Manager', () => { let libp2p: Libp2p diff --git a/test/connection-manager/index.spec.ts b/test/connection-manager/index.spec.ts index 10cd669e6b..83a520f7b1 100644 --- a/test/connection-manager/index.spec.ts +++ b/test/connection-manager/index.spec.ts @@ -8,7 +8,7 @@ import type { Libp2pNode } from '../../src/libp2p.js' import type { DefaultConnectionManager } from '../../src/connection-manager/index.js' import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-compliance-tests/mocks' import { createEd25519PeerId } from '@libp2p/peer-id-factory' -import { CustomEvent } from '@libp2p/interfaces' +import { CustomEvent } from '@libp2p/interfaces/events' describe('Connection Manager', () => { let libp2p: Libp2pNode diff --git a/test/content-routing/dht/operation.node.ts b/test/content-routing/dht/operation.node.ts index e57a06f97a..65bee28fe1 100644 --- a/test/content-routing/dht/operation.node.ts +++ b/test/content-routing/dht/operation.node.ts @@ -8,7 +8,7 @@ import { subsystemMulticodecs, createSubsystemOptions } from './utils.js' import { createPeerId } from '../../utils/creators/peer.js' import type { PeerId } from '@libp2p/interfaces/peer-id' import { createLibp2pNode, Libp2pNode } from '../../../src/libp2p.js' -import { start } from '@libp2p/interface-compliance-tests' +import { start } from '@libp2p/interfaces/startable' const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/8000') const remoteListenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/8001') diff --git a/test/identify/index.spec.ts b/test/identify/index.spec.ts index 228d6a6faf..df47bc58af 100644 --- a/test/identify/index.spec.ts +++ b/test/identify/index.spec.ts @@ -27,14 +27,14 @@ import { } from '../../src/identify/consts.js' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' import { DefaultTransportManager } from '../../src/transport-manager.js' -import { CustomEvent } from '@libp2p/interfaces' +import { CustomEvent } from '@libp2p/interfaces/events' import delay from 'delay' import pWaitFor from 'p-wait-for' import { peerIdFromString } from '@libp2p/peer-id' import type { PeerId } from '@libp2p/interfaces/peer-id' import type { Libp2pNode } from '../../src/libp2p.js' import { pEvent } from 'p-event' -import { start, stop } from '@libp2p/interface-compliance-tests' +import { start, stop } from '@libp2p/interfaces/startable' const listenMaddrs = [new Multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] diff --git a/test/peer-discovery/index.node.ts b/test/peer-discovery/index.node.ts index 7701b47064..f33250757f 100644 --- a/test/peer-discovery/index.node.ts +++ b/test/peer-discovery/index.node.ts @@ -13,7 +13,7 @@ import { createBaseOptions } from '../utils/base-options.js' import { createPeerId } from '../utils/creators/peer.js' import type { PeerId } from '@libp2p/interfaces/peer-id' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' -import { CustomEvent } from '@libp2p/interfaces' +import { CustomEvent } from '@libp2p/interfaces/events' import type { PeerInfo } from '@libp2p/interfaces/peer-info' const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') diff --git a/test/peer-discovery/index.spec.ts b/test/peer-discovery/index.spec.ts index 83e81cfab6..6e453c5cd4 100644 --- a/test/peer-discovery/index.spec.ts +++ b/test/peer-discovery/index.spec.ts @@ -9,7 +9,7 @@ import { createPeerId } from '../utils/creators/peer.js' import { isPeerId, PeerId } from '@libp2p/interfaces/peer-id' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-compliance-tests/mocks' -import type { Startable } from '@libp2p/interfaces' +import type { Startable } from '@libp2p/interfaces/startable' describe('peer discovery', () => { describe('basic functions', () => { diff --git a/test/registrar/registrar.spec.ts b/test/registrar/registrar.spec.ts index a4837b7f6f..6b49bec87f 100644 --- a/test/registrar/registrar.spec.ts +++ b/test/registrar/registrar.spec.ts @@ -14,7 +14,7 @@ import type { PeerId } from '@libp2p/interfaces/peer-id' import { Components } from '@libp2p/interfaces/components' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' import { createEd25519PeerId } from '@libp2p/peer-id-factory' -import { CustomEvent } from '@libp2p/interfaces' +import { CustomEvent } from '@libp2p/interfaces/events' import type { Connection } from '@libp2p/interfaces/connection' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' import { Plaintext } from '../../src/insecure/index.js' From 4837430d8bcdbee0865eeba6fe694bc71fc6c9bb Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 10 May 2022 12:35:33 +0100 Subject: [PATCH 360/447] fix: encode enums correctly (#1210) Updates protons and regenerates protobuf code to encode enums correctly --- package.json | 13 ++------ src/circuit/pb/index.ts | 37 ++++++++++++++++++++--- src/circuit/transport.ts | 2 +- src/connection-manager/index.ts | 2 +- src/dht/dummy-dht.ts | 9 ++++++ src/fetch/pb/proto.ts | 13 ++++++-- src/identify/pb/message.ts | 3 +- src/insecure/pb/proto.ts | 15 ++++++--- src/registrar.ts | 20 ++---------- test/registrar/registrar.spec.ts | 20 ++++++++++++ test/transports/transport-manager.spec.ts | 2 +- 11 files changed, 93 insertions(+), 43 deletions(-) diff --git a/package.json b/package.json index b4be4f3863..1f0d018abb 100644 --- a/package.json +++ b/package.json @@ -111,12 +111,8 @@ "@multiformats/mafmt": "^11.0.2", "@multiformats/multiaddr": "^10.1.8", "abortable-iterator": "^4.0.2", - "aggregate-error": "^4.0.0", "any-signal": "^3.0.0", - "bignumber.js": "^9.0.1", - "class-is": "^1.1.0", "datastore-core": "^7.0.0", - "debug": "^4.3.3", "err-code": "^3.0.1", "events": "^3.3.0", "hashlru": "^2.3.0", @@ -135,9 +131,7 @@ "it-sort": "^1.0.1", "it-stream-types": "^1.0.4", "it-take": "^1.0.2", - "it-to-buffer": "^2.0.2", "merge-options": "^3.0.4", - "mortice": "^3.0.0", "multiformats": "^9.6.3", "mutable-proxy": "^1.0.0", "node-forge": "^1.2.1", @@ -145,14 +139,12 @@ "p-retry": "^5.0.0", "p-settle": "^5.0.0", "private-ip": "^2.3.3", - "protons-runtime": "^1.0.2", + "protons-runtime": "^1.0.4", "retimer": "^3.0.0", "sanitize-filename": "^1.6.3", "set-delayed-interval": "^1.0.0", - "streaming-iterables": "^6.0.0", "timeout-abort-controller": "^3.0.0", "uint8arrays": "^3.0.0", - "varint": "^6.0.0", "wherearewe": "^1.0.0", "xsalsa20": "^1.1.0" }, @@ -189,13 +181,14 @@ "into-stream": "^7.0.0", "ipfs-http-client": "^56.0.1", "it-pushable": "^2.0.1", + "it-to-buffer": "^2.0.2", "nock": "^13.0.3", "npm-run-all": "^4.1.5", "p-defer": "^4.0.0", "p-event": "^5.0.1", "p-times": "^4.0.0", "p-wait-for": "^4.1.0", - "protons": "^3.0.2", + "protons": "^3.0.4", "rimraf": "^3.0.2", "sinon": "^13.0.1", "ts-sinon": "^2.0.2" diff --git a/src/circuit/pb/index.ts b/src/circuit/pb/index.ts index 5337cc4ee4..71ee1b9d29 100644 --- a/src/circuit/pb/index.ts +++ b/src/circuit/pb/index.ts @@ -2,6 +2,7 @@ /* eslint-disable @typescript-eslint/no-namespace */ import { enumeration, encodeMessage, decodeMessage, message, bytes } from 'protons-runtime' +import type { Codec } from 'protons-runtime' export interface CircuitRelay { type?: CircuitRelay.Type @@ -30,11 +31,31 @@ export namespace CircuitRelay { MALFORMED_MESSAGE = 'MALFORMED_MESSAGE' } + enum __StatusValues { + SUCCESS = 100, + HOP_SRC_ADDR_TOO_LONG = 220, + HOP_DST_ADDR_TOO_LONG = 221, + HOP_SRC_MULTIADDR_INVALID = 250, + HOP_DST_MULTIADDR_INVALID = 251, + HOP_NO_CONN_TO_DST = 260, + HOP_CANT_DIAL_DST = 261, + HOP_CANT_OPEN_DST_STREAM = 262, + HOP_CANT_SPEAK_RELAY = 270, + HOP_CANT_RELAY_TO_SELF = 280, + STOP_SRC_ADDR_TOO_LONG = 320, + STOP_DST_ADDR_TOO_LONG = 321, + STOP_SRC_MULTIADDR_INVALID = 350, + STOP_DST_MULTIADDR_INVALID = 351, + STOP_RELAY_REFUSED = 390, + MALFORMED_MESSAGE = 400 + } + export namespace Status { export const codec = () => { - return enumeration(Status) + return enumeration(__StatusValues) } } + export enum Type { HOP = 'HOP', STOP = 'STOP', @@ -42,18 +63,26 @@ export namespace CircuitRelay { CAN_HOP = 'CAN_HOP' } + enum __TypeValues { + HOP = 1, + STOP = 2, + STATUS = 3, + CAN_HOP = 4 + } + export namespace Type { export const codec = () => { - return enumeration(Type) + return enumeration(__TypeValues) } } + export interface Peer { id: Uint8Array addrs: Uint8Array[] } export namespace Peer { - export const codec = () => { + export const codec = (): Codec => { return message({ 1: { name: 'id', codec: bytes }, 2: { name: 'addrs', codec: bytes, repeats: true } @@ -69,7 +98,7 @@ export namespace CircuitRelay { } } - export const codec = () => { + export const codec = (): Codec => { return message({ 1: { name: 'type', codec: CircuitRelay.Type.codec(), optional: true }, 2: { name: 'srcPeer', codec: CircuitRelay.Peer.codec(), optional: true }, diff --git a/src/circuit/transport.ts b/src/circuit/transport.ts index 4b4940d294..1ff71d4ed7 100644 --- a/src/circuit/transport.ts +++ b/src/circuit/transport.ts @@ -49,7 +49,7 @@ export class Circuit implements Transport, Initializable { } get [Symbol.toStringTag] () { - return this.constructor.name + return 'libp2p/circuit-relay-v1' } async _onProtocol (data: IncomingStreamData) { diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index 3c207bc4e1..65de6efd49 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -296,7 +296,7 @@ export class DefaultConnectionManager extends EventEmitter implements DualDHT { + get [symbol] (): true { + return true + } + + get [Symbol.toStringTag] () { + return '@libp2p/dummy-dht' + } + get wan (): SingleDHT { throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED) } diff --git a/src/fetch/pb/proto.ts b/src/fetch/pb/proto.ts index b6c112effe..919f94a73f 100644 --- a/src/fetch/pb/proto.ts +++ b/src/fetch/pb/proto.ts @@ -2,13 +2,14 @@ /* eslint-disable @typescript-eslint/no-namespace */ import { encodeMessage, decodeMessage, message, string, enumeration, bytes } from 'protons-runtime' +import type { Codec } from 'protons-runtime' export interface FetchRequest { identifier: string } export namespace FetchRequest { - export const codec = () => { + export const codec = (): Codec => { return message({ 1: { name: 'identifier', codec: string } }) @@ -35,13 +36,19 @@ export namespace FetchResponse { ERROR = 'ERROR' } + enum __StatusCodeValues { + OK = 0, + NOT_FOUND = 1, + ERROR = 2 + } + export namespace StatusCode { export const codec = () => { - return enumeration(StatusCode) + return enumeration(__StatusCodeValues) } } - export const codec = () => { + export const codec = (): Codec => { return message({ 1: { name: 'status', codec: FetchResponse.StatusCode.codec() }, 2: { name: 'data', codec: bytes } diff --git a/src/identify/pb/message.ts b/src/identify/pb/message.ts index 7270135656..04aad2a8b9 100644 --- a/src/identify/pb/message.ts +++ b/src/identify/pb/message.ts @@ -2,6 +2,7 @@ /* eslint-disable @typescript-eslint/no-namespace */ import { encodeMessage, decodeMessage, message, string, bytes } from 'protons-runtime' +import type { Codec } from 'protons-runtime' export interface Identify { protocolVersion?: string @@ -14,7 +15,7 @@ export interface Identify { } export namespace Identify { - export const codec = () => { + export const codec = (): Codec => { return message({ 5: { name: 'protocolVersion', codec: string, optional: true }, 6: { name: 'agentVersion', codec: string, optional: true }, diff --git a/src/insecure/pb/proto.ts b/src/insecure/pb/proto.ts index 03909f9ea3..24b0020a7a 100644 --- a/src/insecure/pb/proto.ts +++ b/src/insecure/pb/proto.ts @@ -2,6 +2,7 @@ /* eslint-disable @typescript-eslint/no-namespace */ import { encodeMessage, decodeMessage, message, bytes, enumeration } from 'protons-runtime' +import type { Codec } from 'protons-runtime' export interface Exchange { id?: Uint8Array @@ -9,7 +10,7 @@ export interface Exchange { } export namespace Exchange { - export const codec = () => { + export const codec = (): Codec => { return message({ 1: { name: 'id', codec: bytes, optional: true }, 2: { name: 'pubkey', codec: PublicKey.codec(), optional: true } @@ -32,19 +33,25 @@ export enum KeyType { ECDSA = 'ECDSA' } +enum __KeyTypeValues { + RSA = 0, + Ed25519 = 1, + Secp256k1 = 2, + ECDSA = 3 +} + export namespace KeyType { export const codec = () => { - return enumeration(KeyType) + return enumeration(__KeyTypeValues) } } - export interface PublicKey { Type: KeyType Data: Uint8Array } export namespace PublicKey { - export const codec = () => { + export const codec = (): Codec => { return message({ 1: { name: 'Type', codec: KeyType.codec() }, 2: { name: 'Data', codec: bytes } diff --git a/src/registrar.ts b/src/registrar.ts index 1e9e4d7c93..ef6fd5016b 100644 --- a/src/registrar.ts +++ b/src/registrar.ts @@ -33,11 +33,11 @@ export class DefaultRegistrar implements Registrar { this.components = components this._onDisconnect = this._onDisconnect.bind(this) - this._onConnect = this._onConnect.bind(this) this._onProtocolChange = this._onProtocolChange.bind(this) this.components.getConnectionManager().addEventListener('peer:disconnect', this._onDisconnect) - this.components.getConnectionManager().addEventListener('peer:connect', this._onConnect) + + // happens after identify this.components.getPeerStore().addEventListener('change:protocols', this._onProtocolChange) } @@ -159,22 +159,6 @@ export class DefaultRegistrar implements Registrar { }) } - _onConnect (evt: CustomEvent) { - const connection = evt.detail - - void this.components.getPeerStore().protoBook.get(connection.remotePeer) - .then(peerProtocols => { - for (const { topology, protocols } of this.topologies.values()) { - if (supportsProtocol(peerProtocols, protocols)) { - topology.onConnect(connection.remotePeer, connection) - } - } - }) - .catch(err => { - log.error(err) - }) - } - /** * Check if a new peer support the multicodecs for this topology */ diff --git a/test/registrar/registrar.spec.ts b/test/registrar/registrar.spec.ts index 6b49bec87f..259ae13e9d 100644 --- a/test/registrar/registrar.spec.ts +++ b/test/registrar/registrar.spec.ts @@ -20,6 +20,7 @@ import { DefaultConnectionManager } from '../../src/connection-manager/index.js' import { Plaintext } from '../../src/insecure/index.js' import { WebSockets } from '@libp2p/websockets' import { Mplex } from '@libp2p/mplex' +import type { PeerProtocolsChangeData } from '@libp2p/interfaces/peer-store' const protocol = '/test/1.0.0' @@ -145,6 +146,15 @@ describe('registrar', () => { detail: conn })) + // identify completes + await libp2p.components.getPeerStore().dispatchEvent(new CustomEvent('change:protocols', { + detail: { + peerId: conn.remotePeer, + protocols: [protocol], + oldProtocols: [] + } + })) + // remote peer disconnects await conn.close() await libp2p.components.getUpgrader().dispatchEvent(new CustomEvent('connectionEnd', { @@ -186,10 +196,20 @@ describe('registrar', () => { // Add protocol to peer and update it await libp2p.peerStore.protoBook.add(remotePeerId, [protocol]) + // remote peer connects await libp2p.components.getUpgrader().dispatchEvent(new CustomEvent('connection', { detail: conn })) + // identify completes + await libp2p.components.getPeerStore().dispatchEvent(new CustomEvent('change:protocols', { + detail: { + peerId: conn.remotePeer, + protocols: [protocol], + oldProtocols: [] + } + })) + await onConnectDefer.promise // Peer no longer supports the protocol our topology is registered for diff --git a/test/transports/transport-manager.spec.ts b/test/transports/transport-manager.spec.ts index 19d0902d2e..059b5c1007 100644 --- a/test/transports/transport-manager.spec.ts +++ b/test/transports/transport-manager.spec.ts @@ -47,7 +47,7 @@ describe('Transport Manager (WebSockets)', () => { tm.add(transport) expect(tm.getTransports()).to.have.lengthOf(1) - await tm.remove(transport.constructor.name) + await tm.remove(transport[Symbol.toStringTag]) expect(tm.getTransports()).to.have.lengthOf(0) }) From 31480603f3e17d838d2685573995218a1e678e7a Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Mon, 16 May 2022 10:07:32 +0100 Subject: [PATCH 361/447] fix: simplify pnet exports (#1213) Export the key generator from `libp2p/pnet` --- examples/pnet/index.js | 6 +++--- package.json | 3 --- src/keychain/index.ts | 1 - src/pnet/index.ts | 2 ++ src/pnet/key-generator.ts | 2 +- test/pnet/index.spec.ts | 7 +++---- 6 files changed, 9 insertions(+), 12 deletions(-) diff --git a/examples/pnet/index.js b/examples/pnet/index.js index 1da827cf14..59adbaa88f 100644 --- a/examples/pnet/index.js +++ b/examples/pnet/index.js @@ -1,6 +1,6 @@ /* eslint no-console: ["off"] */ -import { generate } from 'libp2p/pnet/generate' +import { generateKey } from 'libp2p/pnet' import { privateLibp2pNode } from './libp2p-node.js' import { pipe } from 'it-pipe' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' @@ -8,11 +8,11 @@ import { toString as uint8ArrayToString } from 'uint8arrays/to-string' // Create a Uint8Array and write the swarm key to it const swarmKey = new Uint8Array(95) -generate(swarmKey) +generateKey(swarmKey) // This key is for testing a different key not working const otherSwarmKey = new Uint8Array(95) -generate(otherSwarmKey) +generateKey(otherSwarmKey) ;(async () => { const node1 = await privateLibp2pNode(swarmKey) diff --git a/package.json b/package.json index 1f0d018abb..de32b1f662 100644 --- a/package.json +++ b/package.json @@ -57,9 +57,6 @@ "./pnet": { "import": "./dist/src/pnet/index.js" }, - "./pnet/generate": { - "import": "./dist/src/pnet/key-generator.js" - }, "./transport-manager": { "import": "./dist/src/transport-manager.js" } diff --git a/src/keychain/index.ts b/src/keychain/index.ts index b2b99126e6..bbb5bb61a1 100644 --- a/src/keychain/index.ts +++ b/src/keychain/index.ts @@ -9,7 +9,6 @@ import errCode from 'err-code' import { codes } from '../errors.js' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import 'node-forge/lib/sha512.js' import { generateKeyPair, importKey, unmarshalPrivateKey } from '@libp2p/crypto/keys' import type { PeerId } from '@libp2p/interfaces/peer-id' import type { Components } from '@libp2p/interfaces/components' diff --git a/src/pnet/index.ts b/src/pnet/index.ts index 868b43084d..2b3ea2edf3 100644 --- a/src/pnet/index.ts +++ b/src/pnet/index.ts @@ -17,6 +17,8 @@ import type { ConnectionProtector } from '@libp2p/interfaces/connection' const log = logger('libp2p:pnet') +export { generateKey } from './key-generator.js' + export interface ProtectorInit { enabled?: boolean psk: Uint8Array diff --git a/src/pnet/key-generator.ts b/src/pnet/key-generator.ts index af3b46c1a6..d7f4356cad 100644 --- a/src/pnet/key-generator.ts +++ b/src/pnet/key-generator.ts @@ -8,7 +8,7 @@ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' * @param {Uint8Array} bytes - An object to write the psk into * @returns {void} */ -export function generate (bytes: Uint8Array) { +export function generateKey (bytes: Uint8Array) { const psk = uint8ArrayToString(randomBytes(KEY_LENGTH), 'base16') const key = uint8ArrayFromString('/key/swarm/psk/1.0.0/\n/base16/\n' + psk) diff --git a/test/pnet/index.spec.ts b/test/pnet/index.spec.ts index d1105bb7cb..cb1fae3ac1 100644 --- a/test/pnet/index.spec.ts +++ b/test/pnet/index.spec.ts @@ -3,8 +3,7 @@ import { expect } from 'aegir/chai' import { pipe } from 'it-pipe' import all from 'it-all' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { PreSharedKeyConnectionProtector } from '../../src/pnet/index.js' -import { generate } from '../../src/pnet/key-generator.js' +import { PreSharedKeyConnectionProtector, generateKey } from '../../src/pnet/index.js' import { INVALID_PSK } from '../../src/pnet/errors.js' import { mockMultiaddrConnPair } from '@libp2p/interface-compliance-tests/mocks' import { Multiaddr } from '@multiformats/multiaddr' @@ -14,8 +13,8 @@ const swarmKeyBuffer = new Uint8Array(95) const wrongSwarmKeyBuffer = new Uint8Array(95) // Write new psk files to the buffers -generate(swarmKeyBuffer) -generate(wrongSwarmKeyBuffer) +generateKey(swarmKeyBuffer) +generateKey(wrongSwarmKeyBuffer) describe('private network', () => { it('should accept a valid psk buffer', () => { From f2fd4e30ffc69fd23e21bcd12e691d34e8c76e73 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Mon, 16 May 2022 10:58:56 +0100 Subject: [PATCH 362/447] docs: add migration guide (#1214) Adds migration guide for `libp2p@0.37.x` --- doc/migrations/v0.36-v.037.md | 256 ++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 doc/migrations/v0.36-v.037.md diff --git a/doc/migrations/v0.36-v.037.md b/doc/migrations/v0.36-v.037.md new file mode 100644 index 0000000000..dd9b74c71d --- /dev/null +++ b/doc/migrations/v0.36-v.037.md @@ -0,0 +1,256 @@ + +# Migrating to libp2p@37 + +A migration guide for refactoring your application code from libp2p v0.36.x to v0.37.0. + +## Table of Contents + +- [ESM](#esm) +- [TypeScript](#typescript) +- [Config](#config) +- [Bundled modules](#bundled-modules) +- [Events](#events) + +## ESM + +The biggest change to `libp2p@0.37.0` is that the module is now [ESM-only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c). + +ESM is the module system for JavaScript, it allows us to structure our code in separate files without polluting a global namespace. + +Other systems have tried to fill this gap, notably CommonJS, AMD, RequireJS and others, but ESM is [the official standard format](https://tc39.es/ecma262/#sec-modules) to package JavaScript code for reuse. + +## TypeScript + +The core `libp2p` module and all supporting modules have now been ported to TypeScript in a complete ground-up rewrite. This will not have a huge impact on most application code, but those that are type-aware, either by being written in TypeScript themselves or using JSDoc comments will notice full type completion and better error message when coding against the libp2p API. + +To reflect the updated nature of these modules, all ecosystem modules have been moved to the `@libp2p` org on npm, so `libp2p-tcp` has become `@libp2p/tcp`, `libp2p-mplex` has become `@libp2p/mplex` and so on. `@chainsafe/libp2p-noise` and `libp2p-gossipsub` are unaffected. + +## Config + +Because libp2p is now fully typed it was necessary to refactor the configuration object passed to the libp2p constructor. The reason being, it previously accepted config objects to pass to the constructors of the various modules - to type those we'd need to know the types of all possible modules in advance which isn't possible. + +The following changes have been made to the configuration object: + +1. It now takes instances of modules rather than their classes +2. Keys from the `config` and `modules` objects have been migrated to the root of the object +3. Use of the `enabled` flag has been removed - if you don't want a particular feature enabled, don't pass a module implementing that feature +4. Some keys have been renamed = `transport` -> `transports`, `streamMuxer` -> `streamMuxers`, `connEncryption` -> `connectionEncryption`, etc +5. Keys from `config.dialer` have been moved to `config.connectionManager` as the connection manager is now responsible for managing connections + +**Before** + +```js +import Libp2p from 'libp2p' +import TCP from 'libp2p-tcp' +import Mplex from 'libp2p-mplex' +import { NOISE } from '@chainsafe/libp2p-noise' +import Gossipsub from 'libp2p-gossipsub' +import KadDHT from 'libp2p-kad-dht' +import Bootstrap from 'libp2p-bootstrap' +import MulticastDNS from 'libp2p-mdns' + +const node = await Libp2p.create({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/8000'] + }, + modules: { + transport: [ + TCP + ], + streamMuxer: [ + Mplex + ], + connEncryption: [ + NOISE + ], + dht: KadDHT, + pubsub: Gossipsub, + peerDiscovery: [ + Bootstrap, + MulticastDNS + ] + }, + config: { + peerDiscovery: { + autoDial: true, + [MulticastDNS.tag]: { + interval: 1000, + enabled: true + }, + [Bootstrap.tag]: { + list: [ + // .. multiaddrs here + ], + interval: 2000, + enabled: true + } + }, + dialer: { + dialTimeout: 60000 + } + } +}) +``` + +**After** + +```js +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' +import Gossipsub from '@chainsafe/libp2p-gossipsub' +import { KadDHT } from '@libp2p/kad-dht' +import { Bootstrap } from '@libp2p/bootstrap' +import { MulticastDNS } from '@libp2p/mdns' + +const node = await createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/8000'] + }, + addressManager: { + autoDial: true + }, + connectionManager: { + dialTimeout: 60000 + }, + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + new Noise() + ], + dht: new KadDHT(), + pubsub: new Gossipsub(), + peerDiscovery: [ + new Bootstrap({ + list: [ + // .. multiaddrs here + ], + interval: 2000 + }), + new MulticastDNS({ + interval: 1000 + }) + ] +}) +``` + +## Bundled modules + +Previously you'd have to use deep import paths to get at bundled modules such as the private network module. + +Access to these modules is now controlled by the package.json export map so your import paths will need to be updated: + +**Before** + +```js +import plaintext from 'libp2p/src/insecure/plaintext.js' +import Protector from 'libp2p/src/pnet/index.js' +import generateKey from 'libp2p/src/pnet/key-generator.js' +import TransportManager from 'libp2p/src/transport-manager.js' +``` + +**After** + +```js +import { Plaintext } from 'libp2p/insecure' +import { PreSharedKeyConnectionProtector, generateKey } from 'libp2p/pnet' +import { TransportManager } from 'libp2p/transport-manager' +``` + +## Events + +To reduce our dependency on Node.js internals, use of [EventEmitter](https://nodejs.org/api/events.html#class-eventemitter) has been replaced with the standard [EventTarget](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget). + +The EventTarget API is very similar to [HTML DOM Events](https://developer.mozilla.org/en-US/docs/Web/API/Event) used by the browser. + +All events are instances of the [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) class. Event-specific information can be accessed via the `.detail` property of the passed event. + +They type of event emitted can be inferred from the types for each event emitter. + +**Before** + +```js +const handler = (peerInfo) => { + //... +} + +// listen for event +libp2p.on('peer:discovery', handler) + +// stop listening for event +libp2p.removeListener('peer:discovery', handler) +libp2p.off('peer:discovery', handler) +``` + +**After** + +```js +const handler = (event) => { + const peerInfo = event.detail + //... +} + +// listen for event +libp2p.addEventListener('peer:discovery', handler) + +// stop listening for event +libp2p.removeEventListener('peer:discovery', handler) +``` + +## Pubsub + +Similar to the events refactor above, pubsub is now driven by the standard [EventTarget](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) API. + +You can still subscribe to events without a listener with `.subscribe` but all other uses now use the standard API. + +Similar to the other events emitted by libp2p the event type is [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent). This is part of the js language but at the time of writing Node.js [does not support](https://github.com/nodejs/node/issues/40678) `CustomEvent`, so a polyfill is supplied as part of the `@libp2p/interfaces` + +**Before** + +```js +const handler = (message: Message) => { + const topic = message.topic + + //... +} + +// listen for event +libp2p.pubsub.subscribe('my-topic') +libp2p.pubsub.on('my-topic', handler) + +// send event +libp2p.pubsub.emit('my-topic', Uint8Array.from([0, 1, 2, 3])) + +// stop listening for event +libp2p.unsubscribe('my-topic', handler) +libp2p.pubsub.off('my-topic', handler) +``` + +**After** + +```js +import type { Message } from '@libp2p/interfaces/pubsub' + +const handler = (event: CustomEvent) => { + const message = event.detail + const topic = message.topic + + //... +} + +// listen for event +libp2p.pubsub.subscribe('my-topic') +libp2p.pubsub.addEventListener('message', handler) + +// send event +libp2p.pubsub.publish('my-topic', Uint8Array.from([0, 1, 2, 3])) + +// stop listening for event +libp2p.pubsub.unsubscribe('my-topic') +libp2p.pubsub.removeEventListener('message', handler) +``` From 7678156cf31c403efa8538a3d7614f57acf23af5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 May 2022 12:10:31 +0100 Subject: [PATCH 363/447] chore: release 0.37.0 (#1178) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b44b2e033c..7392893c04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,32 @@ +## [0.37.0](https://www.github.com/libp2p/js-libp2p/compare/v0.36.2...v0.37.0) (2022-05-16) + + +### ⚠ BREAKING CHANGES + +* types are no longer hand crafted, this module is now ESM only + +### Features + +* convert to typescript ([#1172](https://www.github.com/libp2p/js-libp2p/issues/1172)) ([199395d](https://www.github.com/libp2p/js-libp2p/commit/199395de4d8139cc77d0b408626f37c9b8520d28)) + + +### Bug Fixes + +* add transport manager to exports map and fix docs ([#1182](https://www.github.com/libp2p/js-libp2p/issues/1182)) ([cc60cfd](https://www.github.com/libp2p/js-libp2p/commit/cc60cfde1a0907ca68f658f6de5362a708189222)) +* emit peer:connect after all ([#1171](https://www.github.com/libp2p/js-libp2p/issues/1171)) ([d16817c](https://www.github.com/libp2p/js-libp2p/commit/d16817ca443443e88803ee8096d45debb14af91b)) +* encode enums correctly ([#1210](https://www.github.com/libp2p/js-libp2p/issues/1210)) ([4837430](https://www.github.com/libp2p/js-libp2p/commit/4837430d8bcdbee0865eeba6fe694bc71fc6c9bb)) +* expose getPublicKey ([#1188](https://www.github.com/libp2p/js-libp2p/issues/1188)) ([1473044](https://www.github.com/libp2p/js-libp2p/commit/147304449e5f8d3acb8b00bdd9588b56830667c6)) +* expose metrics and registrar, use dht for peer discovery ([#1183](https://www.github.com/libp2p/js-libp2p/issues/1183)) ([64bfcee](https://www.github.com/libp2p/js-libp2p/commit/64bfcee5093b368df0b381f78afc2ddff3d339a9)) +* simplify pnet exports ([#1213](https://www.github.com/libp2p/js-libp2p/issues/1213)) ([3148060](https://www.github.com/libp2p/js-libp2p/commit/31480603f3e17d838d2685573995218a1e678e7a)) +* update deps ([#1181](https://www.github.com/libp2p/js-libp2p/issues/1181)) ([8cca8e4](https://www.github.com/libp2p/js-libp2p/commit/8cca8e4bfc6a339e58b5a5efa8a84fd891aa08ee)) +* update interfaces ([#1207](https://www.github.com/libp2p/js-libp2p/issues/1207)) ([da3d19b](https://www.github.com/libp2p/js-libp2p/commit/da3d19b30977fd2c7e77d92aa8914b13e3179aaa)) +* update pubsub interfaces ([#1194](https://www.github.com/libp2p/js-libp2p/issues/1194)) ([fab4f13](https://www.github.com/libp2p/js-libp2p/commit/fab4f1385cf61b7b16719b9aacdfe03146a3f260)) +* update to new interfaces ([#1206](https://www.github.com/libp2p/js-libp2p/issues/1206)) ([a15254f](https://www.github.com/libp2p/js-libp2p/commit/a15254fdd478a336edf1e1196b721dc56888b2ea)) +* use placeholder dht/pubsub ([#1193](https://www.github.com/libp2p/js-libp2p/issues/1193)) ([5397137](https://www.github.com/libp2p/js-libp2p/commit/5397137c654dfdec431e0c9ba4b1ff9dee19abf1)) + ### [0.36.2](https://www.github.com/libp2p/js-libp2p/compare/v0.36.1...v0.36.2) (2022-01-26) diff --git a/package.json b/package.json index de32b1f662..2b3ffeaeb0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.36.2", + "version": "0.37.0", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p#readme", From 1f5d5c2de166d373dd2d71594832e73158d8041c Mon Sep 17 00:00:00 2001 From: Steve Loeppky Date: Mon, 16 May 2022 11:22:41 -0700 Subject: [PATCH 364/447] Added pubsub to the TOC (#1215) --- doc/migrations/v0.36-v.037.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/migrations/v0.36-v.037.md b/doc/migrations/v0.36-v.037.md index dd9b74c71d..2dd3322f0c 100644 --- a/doc/migrations/v0.36-v.037.md +++ b/doc/migrations/v0.36-v.037.md @@ -10,6 +10,7 @@ A migration guide for refactoring your application code from libp2p v0.36.x to v - [Config](#config) - [Bundled modules](#bundled-modules) - [Events](#events) +- [Pubsub](#pubsub) ## ESM @@ -202,7 +203,7 @@ libp2p.addEventListener('peer:discovery', handler) libp2p.removeEventListener('peer:discovery', handler) ``` -## Pubsub +## Pubsub Similar to the events refactor above, pubsub is now driven by the standard [EventTarget](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) API. From d5386df68478a71ac269acb2d00d36a7a5c9ebc5 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 18 May 2022 13:19:31 +0100 Subject: [PATCH 365/447] fix: do upnp hole punch after startup (#1217) The transport manager configures it's addresses during the `start` phase so access them during `afterStart` so they'll be ready for use. --- package.json | 2 +- src/nat-manager.ts | 10 +++++++--- test/nat-manager/nat-manager.node.ts | 17 +++++++++-------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 2b3ffeaeb0..028b34b313 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "test:interop": "aegir test -t node -f dist/test/interop.js" }, "dependencies": { - "@achingbrain/nat-port-mapper": "^1.0.0", + "@achingbrain/nat-port-mapper": "^1.0.3", "@libp2p/connection": "^1.1.5", "@libp2p/crypto": "^0.22.11", "@libp2p/interfaces": "^1.3.31", diff --git a/src/nat-manager.ts b/src/nat-manager.ts index 0d53fdfe65..8d229f07cf 100644 --- a/src/nat-manager.ts +++ b/src/nat-manager.ts @@ -94,10 +94,14 @@ export class NatManager implements Startable { return this.started } + start () {} + /** - * Starts the NAT manager + * Attempt to use uPnP to configure port mapping using the current gateway. + * + * Run after start to ensure the transport manager has all addresses configured. */ - start () { + afterStart () { if (isBrowser || !this.enabled || this.started) { return } @@ -105,7 +109,7 @@ export class NatManager implements Startable { this.started = true // done async to not slow down startup - this._start().catch((err) => { + void this._start().catch((err) => { // hole punching errors are non-fatal log.error(err) }) diff --git a/test/nat-manager/nat-manager.node.ts b/test/nat-manager/nat-manager.node.ts index 440cb29586..30e251ee52 100644 --- a/test/nat-manager/nat-manager.node.ts +++ b/test/nat-manager/nat-manager.node.ts @@ -13,6 +13,7 @@ import { createFromJSON } from '@libp2p/peer-id-factory' import { Components } from '@libp2p/interfaces/components' import type { NatAPI } from '@achingbrain/nat-port-mapper' import { StubbedInstance, stubInterface } from 'ts-sinon' +import { start, stop } from '@libp2p/interfaces/startable' const DEFAULT_ADDRESSES = [ '/ip4/127.0.0.1/tcp/0', @@ -49,7 +50,7 @@ describe('Nat Manager (TCP)', () => { await components.getTransportManager().listen(components.getAddressManager().getListenAddrs()) teardown.push(async () => { - await natManager.stop() + await stop(natManager) await components.getTransportManager().removeAll() }) @@ -78,7 +79,7 @@ describe('Nat Manager (TCP)', () => { let observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() - await natManager._start() + await start(natManager) observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) expect(observed).to.not.be.empty() @@ -127,7 +128,7 @@ describe('Nat Manager (TCP)', () => { enabled: false }) - natManager.start() + await start(natManager) await delay(100) @@ -146,7 +147,7 @@ describe('Nat Manager (TCP)', () => { let observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() - await natManager._start() + await start(natManager) observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() @@ -163,7 +164,7 @@ describe('Nat Manager (TCP)', () => { let observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() - await natManager._start() + await start(natManager) observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() @@ -180,7 +181,7 @@ describe('Nat Manager (TCP)', () => { let observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() - await natManager._start() + await start(natManager) observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() @@ -197,7 +198,7 @@ describe('Nat Manager (TCP)', () => { let observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() - await natManager._start() + await start(natManager) observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() @@ -214,7 +215,7 @@ describe('Nat Manager (TCP)', () => { let observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() - await natManager._start() + await start(natManager) observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() From 35f9c0c79387232465848b450a47cafe841405e7 Mon Sep 17 00:00:00 2001 From: Robert Kiel Date: Wed, 18 May 2022 15:53:58 +0200 Subject: [PATCH 366/447] fix: fix unintended aborts in dialer (#1185) Fix a bug where `DialRequest` can abort wrong dial attempts. Co-authored-by: Robert Kiel --- src/connection-manager/dialer/dial-request.ts | 34 ++++++++-- src/errors.ts | 3 +- test/dialing/dial-request.spec.ts | 67 +++++++++++++++---- 3 files changed, 86 insertions(+), 18 deletions(-) diff --git a/src/connection-manager/dialer/dial-request.ts b/src/connection-manager/dialer/dial-request.ts index 650e825a98..8718c1a1d5 100644 --- a/src/connection-manager/dialer/dial-request.ts +++ b/src/connection-manager/dialer/dial-request.ts @@ -62,7 +62,7 @@ export class DialRequest { }) } - const dialAbortControllers = this.addrs.map(() => { + const dialAbortControllers: Array<(AbortController | undefined)> = this.addrs.map(() => { const controller = new AbortController() try { // fails on node < 15.4 @@ -80,16 +80,27 @@ export class DialRequest { } let completedDials = 0 + let done = false try { return await Promise.any(this.addrs.map(async (addr, i) => { const token = await tokenHolder.shift() // get token + // End attempt once another attempt succeeded + if (done) { + this.dialer.releaseToken(tokens.splice(tokens.indexOf(token), 1)[0]) + throw errCode(new Error('dialAction already succeeded'), codes.ERR_ALREADY_SUCCEEDED) + } + + const controller = dialAbortControllers[i] + if (controller == null) { + throw errCode(new Error('dialAction did not come with an AbortController'), codes.ERR_INVALID_PARAMETERS) + } let conn try { - const signal = dialAbortControllers[i].signal + const signal = controller.signal conn = await this.dialAction(addr, { ...options, signal: (options.signal != null) ? anySignal([signal, options.signal]) : signal }) // Remove the successful AbortController so it is not aborted - dialAbortControllers.splice(i, 1) + dialAbortControllers[i] = undefined } finally { completedDials++ // If we have more or equal dials remaining than tokens, recycle the token, otherwise release it @@ -102,10 +113,25 @@ export class DialRequest { } } + if (conn == null) { + // Notify Promise.any that attempt was not successful + // to prevent from returning undefined despite there + // were successful dial attempts + throw errCode(new Error('dialAction led to empty object'), codes.ERR_TRANSPORT_DIAL_FAILED) + } else { + // This dial succeeded, don't attempt anything else + done = true + } + return conn })) } finally { - dialAbortControllers.map(c => c.abort()) // success/failure happened, abort everything else + // success/failure happened, abort everything else + dialAbortControllers.forEach(c => { + if (c !== undefined) { + c.abort() + } + }) tokens.forEach(token => this.dialer.releaseToken(token)) // release tokens back to the dialer } } diff --git a/src/errors.ts b/src/errors.ts index b5048e65d3..ec02ed6002 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -69,5 +69,6 @@ export enum codes { ERR_INVALID_PASS_LENGTH = 'ERR_INVALID_PASS_LENGTH', ERR_NOT_IMPLEMENTED = 'ERR_NOT_IMPLEMENTED', ERR_WRONG_PING_ACK = 'ERR_WRONG_PING_ACK', - ERR_INVALID_RECORD = 'ERR_INVALID_RECORD' + ERR_INVALID_RECORD = 'ERR_INVALID_RECORD', + ERR_ALREADY_SUCCEEDED = 'ERR_ALREADY_SUCCEEDED' } diff --git a/test/dialing/dial-request.spec.ts b/test/dialing/dial-request.spec.ts index ea8c6f6e69..3c945922f6 100644 --- a/test/dialing/dial-request.spec.ts +++ b/test/dialing/dial-request.spec.ts @@ -15,10 +15,11 @@ const error = new Error('dial failure') describe('Dial Request', () => { it('should end when a single multiaddr dials succeeds', async () => { const connection = mockConnection(mockMultiaddrConnection(mockDuplex(), await createEd25519PeerId())) + const deferredConn = pDefer() const actions: Record Promise> = { '/ip4/127.0.0.1/tcp/1231': async () => await Promise.reject(error), '/ip4/127.0.0.1/tcp/1232': async () => await Promise.resolve(connection), - '/ip4/127.0.0.1/tcp/1233': async () => await Promise.reject(error) + '/ip4/127.0.0.1/tcp/1233': async () => await deferredConn.promise } const dialAction: DialAction = async (num) => await actions[num.toString()]() const controller = new AbortController() @@ -32,15 +33,12 @@ describe('Dial Request', () => { dialAction }) - sinon.spy(actions, '/ip4/127.0.0.1/tcp/1231') - sinon.spy(actions, '/ip4/127.0.0.1/tcp/1232') - sinon.spy(actions, '/ip4/127.0.0.1/tcp/1233') + // Make sure that dial attempt comes back before terminating last dial action + expect(await dialRequest.run({ signal: controller.signal })).to.equal(connection) + + // End third dial attempt + deferredConn.resolve() - const result = await dialRequest.run({ signal: controller.signal }) - expect(result).to.equal(connection) - expect(actions['/ip4/127.0.0.1/tcp/1231']).to.have.property('callCount', 1) - expect(actions['/ip4/127.0.0.1/tcp/1232']).to.have.property('callCount', 1) - expect(actions['/ip4/127.0.0.1/tcp/1233']).to.have.property('callCount', 0) expect(dialerReleaseTokenSpy.callCount).to.equal(2) }) @@ -73,14 +71,16 @@ describe('Dial Request', () => { // Let the first dials run await delay(0) + // Only 1 dial should remain, so 1 token should have been released + expect(actions['/ip4/127.0.0.1/tcp/1231']).to.have.property('callCount', 1) + expect(actions['/ip4/127.0.0.1/tcp/1232']).to.have.property('callCount', 1) + expect(actions['/ip4/127.0.0.1/tcp/1233']).to.have.property('callCount', 0) + // Finish the first 2 dials firstDials.reject(error) + await delay(0) - // Only 1 dial should remain, so 1 token should have been released - expect(actions['/ip4/127.0.0.1/tcp/1231']).to.have.property('callCount', 1) - expect(actions['/ip4/127.0.0.1/tcp/1232']).to.have.property('callCount', 1) - expect(actions['/ip4/127.0.0.1/tcp/1233']).to.have.property('callCount', 1) expect(dialerReleaseTokenSpy.callCount).to.equal(1) // Finish the dial and release the 2nd token @@ -214,4 +214,45 @@ describe('Dial Request', () => { expect(dialerGetTokensSpy.calledWith(addrs.length)).to.equal(true) expect(dialerReleaseTokenSpy.callCount).to.equal(2) }) + + it('should abort other dials when one succeeds', async () => { + const connection = mockConnection(mockMultiaddrConnection(mockDuplex(), await createEd25519PeerId())) + const actions: Record Promise> = { + '/ip4/127.0.0.1/tcp/1231': async () => { + await delay(100) + }, + '/ip4/127.0.0.1/tcp/1232': async () => { + // Successful dial takes longer to establish + await delay(1000) + + return connection + }, + + '/ip4/127.0.0.1/tcp/1233': async () => { + await delay(100) + } + } + + const signals: Record = {} + + const dialRequest = new DialRequest({ + addrs: Object.keys(actions).map(str => new Multiaddr(str)), + dialer: new Dialer({ + maxParallelDials: 3 + }), + dialAction: async (ma, opts) => { + signals[ma.toString()] = opts.signal + return await actions[ma.toString()]() + } + }) + + await expect(dialRequest.run()).to.eventually.equal(connection) + + // Dial attempt finished without connection + expect(signals['/ip4/127.0.0.1/tcp/1231']).to.have.property('aborted', false) + // Dial attempt led to connection + expect(signals['/ip4/127.0.0.1/tcp/1232']).to.have.property('aborted', false) + // Dial attempt finished without connection + expect(signals['/ip4/127.0.0.1/tcp/1233']).to.have.property('aborted', false) + }) }) From b09eb8fc53ec1d8f6280d681c9ca6a467ec259b5 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Mon, 23 May 2022 13:45:20 +0100 Subject: [PATCH 367/447] fix: explicitly close streams when connnections close (#1221) Make sure we don't leave streams open. Updates all deps to close multiplexed streams when closing connections. --- examples/package.json | 1 + package.json | 13 +++++-------- src/connection-manager/dialer/dial-request.ts | 1 - src/connection-manager/dialer/index.ts | 1 - src/connection-manager/index.ts | 11 ++++++++--- src/peer-routing.ts | 1 - src/upgrader.ts | 6 ++++-- 7 files changed, 18 insertions(+), 16 deletions(-) diff --git a/examples/package.json b/examples/package.json index ddeef8ea95..848c9d47fc 100644 --- a/examples/package.json +++ b/examples/package.json @@ -11,6 +11,7 @@ "dependencies": { "@libp2p/pubsub-peer-discovery": "^5.0.2", "@libp2p/floodsub": "^1.0.6", + "@nodeutils/defaults-deep": "^1.1.0", "execa": "^2.1.0", "fs-extra": "^8.1.0", "libp2p": "../", diff --git a/package.json b/package.json index 028b34b313..22c39dd6ad 100644 --- a/package.json +++ b/package.json @@ -93,9 +93,9 @@ }, "dependencies": { "@achingbrain/nat-port-mapper": "^1.0.3", - "@libp2p/connection": "^1.1.5", + "@libp2p/connection": "^2.0.2", "@libp2p/crypto": "^0.22.11", - "@libp2p/interfaces": "^1.3.31", + "@libp2p/interfaces": "^2.0.1", "@libp2p/logger": "^1.1.4", "@libp2p/multistream-select": "^1.0.4", "@libp2p/peer-collections": "^1.0.2", @@ -153,24 +153,21 @@ "@libp2p/delegated-content-routing": "^1.0.2", "@libp2p/delegated-peer-routing": "^1.0.2", "@libp2p/floodsub": "^1.0.6", - "@libp2p/interface-compliance-tests": "^1.1.32", + "@libp2p/interface-compliance-tests": "^2.0.1", "@libp2p/interop": "^1.0.3", "@libp2p/kad-dht": "^1.0.9", "@libp2p/mdns": "^1.0.5", - "@libp2p/mplex": "^1.0.4", + "@libp2p/mplex": "^1.1.0", "@libp2p/pubsub": "^1.2.18", "@libp2p/tcp": "^1.0.9", "@libp2p/topology": "^1.1.7", "@libp2p/webrtc-star": "^1.0.8", "@libp2p/websockets": "^1.0.7", - "@nodeutils/defaults-deep": "^1.1.0", - "@types/node": "^16.11.26", "@types/node-forge": "^1.0.0", "@types/p-fifo": "^1.0.0", "@types/varint": "^6.0.0", "@types/xsalsa20": "^1.1.0", "aegir": "^37.0.9", - "buffer": "^6.0.3", "cborg": "^1.8.1", "delay": "^5.0.0", "execa": "^6.1.0", @@ -187,7 +184,7 @@ "p-wait-for": "^4.1.0", "protons": "^3.0.4", "rimraf": "^3.0.2", - "sinon": "^13.0.1", + "sinon": "^14.0.0", "ts-sinon": "^2.0.2" }, "browser": { diff --git a/src/connection-manager/dialer/dial-request.ts b/src/connection-manager/dialer/dial-request.ts index 8718c1a1d5..bb26f474d5 100644 --- a/src/connection-manager/dialer/dial-request.ts +++ b/src/connection-manager/dialer/dial-request.ts @@ -1,7 +1,6 @@ import errCode from 'err-code' import { anySignal } from 'any-signal' import FIFO from 'p-fifo' -// @ts-expect-error setMaxListeners is missing from the node 16 types import { setMaxListeners } from 'events' import { codes } from '../../errors.js' import { logger } from '@libp2p/logger' diff --git a/src/connection-manager/dialer/index.ts b/src/connection-manager/dialer/index.ts index e0688534bc..a7abe5165a 100644 --- a/src/connection-manager/dialer/index.ts +++ b/src/connection-manager/dialer/index.ts @@ -7,7 +7,6 @@ import { Multiaddr, Resolver } from '@multiformats/multiaddr' import { TimeoutController } from 'timeout-abort-controller' import { AbortError } from '@libp2p/interfaces/errors' import { anySignal } from 'any-signal' -// @ts-expect-error setMaxListeners is missing from the node 16 types import { setMaxListeners } from 'events' import { DialAction, DialRequest } from './dial-request.js' import { publicAddressesFirst } from '@libp2p/utils/address-sort' diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index 65de6efd49..58a9b5eda5 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -10,7 +10,6 @@ import type { Startable } from '@libp2p/interfaces/startable' import { trackedMap } from '@libp2p/tracked-map' import { codes } from '../errors.js' import { isPeerId, PeerId } from '@libp2p/interfaces/peer-id' -// @ts-expect-error setMaxListeners is missing from the node 16 types import { setMaxListeners } from 'events' import type { Connection } from '@libp2p/interfaces/connection' import type { ConnectionManager } from '@libp2p/interfaces/connection-manager' @@ -254,10 +253,16 @@ export class DefaultConnectionManager extends EventEmitter> = [] for (const connectionList of this.connections.values()) { for (const connection of connectionList) { - tasks.push(connection.close()) + tasks.push((async () => { + try { + await connection.close() + } catch (err) { + log.error(err) + } + })()) } } diff --git a/src/peer-routing.ts b/src/peer-routing.ts index 7f0b459136..a47f1a8275 100644 --- a/src/peer-routing.ts +++ b/src/peer-routing.ts @@ -17,7 +17,6 @@ import { clearDelayedInterval // @ts-expect-error module with no types } from 'set-delayed-interval' -// @ts-expect-error setMaxListeners is missing from the node 16 types import { setMaxListeners } from 'events' import type { PeerId } from '@libp2p/interfaces/peer-id' import type { PeerRouting } from '@libp2p/interfaces/peer-routing' diff --git a/src/upgrader.ts b/src/upgrader.ts index 109f2c2e80..27cd729ec9 100644 --- a/src/upgrader.ts +++ b/src/upgrader.ts @@ -382,9 +382,11 @@ export class DefaultUpgrader extends EventEmitter implements Upg getStreams: () => muxer != null ? muxer.streams : errConnectionNotMultiplexed(), close: async () => { await maConn.close() - // Ensure remaining streams are aborted + // Ensure remaining streams are closed if (muxer != null) { - muxer.streams.map(stream => stream.abort()) + await Promise.all(muxer.streams.map(async stream => { + await stream.close() + })) } } }) From 5934b13ccecfdc4e2aa92ae29e3301da5a7e726a Mon Sep 17 00:00:00 2001 From: Rakesh Shrestha Date: Tue, 24 May 2022 21:19:22 +0545 Subject: [PATCH 368/447] Protocol muxing docs explanation (#1165) * update examples * prettier to single quotes * protocol-muxing bidirectional code explanation Co-authored-by: aomini daiki --- examples/protocol-and-stream-muxing/README.md | 97 ++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/examples/protocol-and-stream-muxing/README.md b/examples/protocol-and-stream-muxing/README.md index dae3104801..cb34a65dba 100644 --- a/examples/protocol-and-stream-muxing/README.md +++ b/examples/protocol-and-stream-muxing/README.md @@ -167,10 +167,105 @@ There is one last trick on _protocol and stream multiplexing_ that libp2p uses t With the aid of both mechanisms, we can reuse an incomming connection to dial streams out too, this is specially useful when you are behind tricky NAT, firewalls or if you are running in a browser, where you can't have listening addrs, but you can dial out. By dialing out, you enable other peers to talk with you in Protocols that they want, simply by opening a new multiplexed stream. -You can see this working on example [3.js](./3.js). The result should look like the following: +You can see this working on example [3.js](./3.js). + +As we've seen earlier, we can create our node with this createNode function. +```js +const createNode = async () => { + const node = await Libp2p.create({ + addresses: { + listen: ['/ip4/0.0.0.0/tcp/0'] + }, + modules: { + transport: [TCP], + streamMuxer: [MPLEX], + connEncryption: [NOISE] + } + }) + + await node.start() + + return node +} +``` + +We can now create our two nodes for this example. +```js +const [node1, node2] = await Promise.all([ + createNode(), + createNode() +]) +``` + +Since, we want to connect these nodes `node1` & `node2`, we add our `node2` multiaddr in key-value pair in `node1` peer store. +```js +await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) +``` + +You may notice that we are only adding `node2` to `node1` peer store. This is because we want to dial up a bidirectional connection between these two nodes. + +Finally, let's create protocols for `node1` & `node2` and dial those protocols. +```js +node1.handle('/node-1', ({ stream }) => { + pipe( + stream, + async function (source) { + for await (const msg of source) { + console.log(msg.toString()) + } + } + ) +}) + +node2.handle('/node-2', ({ stream }) => { + pipe( + stream, + async function (source) { + for await (const msg of source) { + console.log(msg.toString()) + } + } + ) +}) + +// Dialing node2 from node1 +const { stream: stream1 } = await node1.dialProtocol(node2.peerId, ['/node-2']) +await pipe( + ['from 1 to 2'], + stream1 +) + +// Dialing node1 from node2 +const { stream: stream2 } = await node2.dialProtocol(node1.peerId, ['/node-1']) +await pipe( + ['from 2 to 1'], + stream2 +) +``` + +If we run this code, the result should look like the following: ```Bash > node 3.js from 1 to 2 from 2 to 1 ``` + +So, we have successfully set up a bidirectional connection with protocol muxing. But you should be aware that we were able to dial from `node2` to `node1` even we haven't added the `node1` peerId to node2 address book is because we dialed node2 from node1 first. Then, we just dialed back our stream out from `node2` to `node1`. So, if we dial from `node2` to `node1` before dialing from `node1` to `node2` we will get an error. + +The code below will result into an error as `the dial address is not valid`. +```js +// Dialing from node2 to node1 +const { stream: stream2 } = await node2.dialProtocol(node1.peerId, ['/node-1']) +await pipe( + ['from 2 to 1'], + stream2 +) + +// Dialing from node1 to node2 +const { stream: stream1 } = await node1.dialProtocol(node2.peerId, ['/node-2']) +await pipe( + ['from 1 to 2'], + stream1 +) +``` \ No newline at end of file From a1220d22f5affb64e64dec0cd6a92cd8241b26df Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 25 May 2022 18:15:21 +0100 Subject: [PATCH 369/447] fix: time out slow reads (#1227) There are a few places in the codebase where we send/receive data from the network without timeouts/abort controllers which means the user has to wait for the underlying socket to timeout which can take a long time depending on the platform, if at all. This change ensures we can time out while running identify (both flavours), ping and fetch and adds tests to ensure there are no regressions. --- doc/API.md | 4 +- doc/CONFIGURATION.md | 7 +- doc/migrations/v0.36-v.037.md | 7 +- examples/delegated-routing/package.json | 2 +- examples/libp2p-in-the-browser/package.json | 2 +- examples/webrtc-direct/package.json | 2 +- package.json | 7 +- src/config.ts | 17 +- src/fetch/constants.ts | 3 +- src/fetch/index.ts | 28 +- src/identify/index.ts | 104 ++++-- src/index.ts | 13 +- src/libp2p.ts | 21 +- src/ping/index.ts | 40 +- src/upgrader.ts | 12 +- test/configuration/protocol-prefix.node.ts | 15 +- test/fetch/index.spec.ts | 133 +++++++ test/identify/index.spec.ts | 393 ++------------------ test/identify/push.spec.ts | 296 +++++++++++++++ test/identify/service.spec.ts | 216 +++++++++++ test/ping/index.spec.ts | 122 ++++++ test/{core => ping}/ping.node.ts | 0 test/upgrading/upgrader.spec.ts | 43 ++- 23 files changed, 1042 insertions(+), 445 deletions(-) create mode 100644 test/fetch/index.spec.ts create mode 100644 test/identify/push.spec.ts create mode 100644 test/identify/service.spec.ts create mode 100644 test/ping/index.spec.ts rename test/{core => ping}/ping.node.ts (100%) diff --git a/doc/API.md b/doc/API.md index 908fff2314..46c8e7a7ee 100644 --- a/doc/API.md +++ b/doc/API.md @@ -97,7 +97,9 @@ Creates an instance of Libp2p. | options.modules | [`Array`](./CONFIGURATION.md#modules) | libp2p [modules](./CONFIGURATION.md#modules) to use | | [options.addresses] | `{ listen: Array, announce: Array, announceFilter: (ma: Array) => Array }` | Addresses for transport listening and to advertise to the network | | [options.config] | `object` | libp2p modules configuration and core configuration | -| [options.host] | `{ agentVersion: string }` | libp2p host options | +| [options.identify] | `{ protocolPrefix: string, host: { agentVersion: string }, timeout: number }` | libp2p identify protocol options | +| [options.ping] | `{ protocolPrefix: string }` | libp2p ping protocol options | +| [options.fetch] | `{ protocolPrefix: string }` | libp2p fetch protocol options | | [options.connectionManager] | [`object`](./CONFIGURATION.md#configuring-connection-manager) | libp2p Connection Manager [configuration](./CONFIGURATION.md#configuring-connection-manager) | | [options.transportManager] | [`object`](./CONFIGURATION.md#configuring-transport-manager) | libp2p transport manager [configuration](./CONFIGURATION.md#configuring-transport-manager) | | [options.datastore] | `object` | must implement [ipfs/interface-datastore](https://github.com/ipfs/interface-datastore) (in memory datastore will be used if not provided) | diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index df5e689aa6..89d4acc83e 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -885,7 +885,12 @@ Changing the protocol name prefix can isolate default public network (IPFS) for ```js const node = await createLibp2p({ - protocolPrefix: 'ipfs' // default + identify: { + protocolPrefix: 'ipfs' // default + }, + ping: { + protocolPrefix: 'ipfs' // default + } }) /* protocols: [ diff --git a/doc/migrations/v0.36-v.037.md b/doc/migrations/v0.36-v.037.md index 2dd3322f0c..d4482feee3 100644 --- a/doc/migrations/v0.36-v.037.md +++ b/doc/migrations/v0.36-v.037.md @@ -37,6 +37,7 @@ The following changes have been made to the configuration object: 3. Use of the `enabled` flag has been removed - if you don't want a particular feature enabled, don't pass a module implementing that feature 4. Some keys have been renamed = `transport` -> `transports`, `streamMuxer` -> `streamMuxers`, `connEncryption` -> `connectionEncryption`, etc 5. Keys from `config.dialer` have been moved to `config.connectionManager` as the connection manager is now responsible for managing connections +6. The `protocolPrefix` configuration option is now passed on a per-protocol basis for `identify`, `fetch` and `ping` **Before** @@ -71,6 +72,7 @@ const node = await Libp2p.create({ MulticastDNS ] }, + protocolPrefix: 'ipfs', config: { peerDiscovery: { autoDial: true, @@ -136,7 +138,10 @@ const node = await createLibp2p({ new MulticastDNS({ interval: 1000 }) - ] + ], + identify: { + protocolPrefix: 'ipfs' + } }) ``` diff --git a/examples/delegated-routing/package.json b/examples/delegated-routing/package.json index 22cd66fb05..5a3bedb64e 100644 --- a/examples/delegated-routing/package.json +++ b/examples/delegated-routing/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { - "@chainsafe/libp2p-noise": "^6.1.1", + "@chainsafe/libp2p-noise": "^6.2.0", "ipfs-core": "^0.14.1", "libp2p": "../../", "@libp2p/delegated-content-routing": "^1.0.1", diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json index c182fea02a..4a6c6df561 100644 --- a/examples/libp2p-in-the-browser/package.json +++ b/examples/libp2p-in-the-browser/package.json @@ -9,7 +9,7 @@ }, "license": "ISC", "dependencies": { - "@chainsafe/libp2p-noise": "^6.1.1", + "@chainsafe/libp2p-noise": "^6.2.0", "@libp2p/bootstrap": "^1.0.4", "@libp2p/mplex": "^1.0.4", "@libp2p/webrtc-star": "^1.0.8", diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index a888ea7fde..6d64d0813d 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -10,7 +10,7 @@ "license": "ISC", "dependencies": { "@libp2p/webrtc-direct": "^1.0.1", - "@chainsafe/libp2p-noise": "^6.1.1", + "@chainsafe/libp2p-noise": "^6.2.0", "@libp2p/bootstrap": "^1.0.4", "@libp2p/mplex": "^1.0.4", "libp2p": "../../", diff --git a/package.json b/package.json index 22c39dd6ad..219967771b 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "@achingbrain/nat-port-mapper": "^1.0.3", "@libp2p/connection": "^2.0.2", "@libp2p/crypto": "^0.22.11", - "@libp2p/interfaces": "^2.0.1", + "@libp2p/interfaces": "^2.0.2", "@libp2p/logger": "^1.1.4", "@libp2p/multistream-select": "^1.0.4", "@libp2p/peer-collections": "^1.0.2", @@ -127,7 +127,6 @@ "it-pipe": "^2.0.3", "it-sort": "^1.0.1", "it-stream-types": "^1.0.4", - "it-take": "^1.0.2", "merge-options": "^3.0.4", "multiformats": "^9.6.3", "mutable-proxy": "^1.0.0", @@ -146,14 +145,14 @@ "xsalsa20": "^1.1.0" }, "devDependencies": { - "@chainsafe/libp2p-noise": "^6.1.1", + "@chainsafe/libp2p-noise": "^6.2.0", "@libp2p/bootstrap": "^1.0.4", "@libp2p/daemon-client": "^1.0.2", "@libp2p/daemon-server": "^1.0.2", "@libp2p/delegated-content-routing": "^1.0.2", "@libp2p/delegated-peer-routing": "^1.0.2", "@libp2p/floodsub": "^1.0.6", - "@libp2p/interface-compliance-tests": "^2.0.1", + "@libp2p/interface-compliance-tests": "^2.0.3", "@libp2p/interop": "^1.0.3", "@libp2p/kad-dht": "^1.0.9", "@libp2p/mdns": "^1.0.5", diff --git a/src/config.ts b/src/config.ts index 8707d43349..432d7e9a2b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -35,9 +35,6 @@ const DefaultConfig: Partial = { transportManager: { faultTolerance: FaultTolerance.FATAL_ALL }, - host: { - agentVersion: AGENT_VERSION - }, metrics: { enabled: false, computeThrottleMaxQueueSize: 1000, @@ -56,7 +53,6 @@ const DefaultConfig: Partial = { bootDelay: 10e3 } }, - protocolPrefix: 'ipfs', nat: { enabled: true, ttl: 7200, @@ -77,6 +73,19 @@ const DefaultConfig: Partial = { enabled: false, maxListeners: 2 } + }, + identify: { + protocolPrefix: 'ipfs', + host: { + agentVersion: AGENT_VERSION + }, + timeout: 30000 + }, + ping: { + protocolPrefix: 'ipfs' + }, + fetch: { + protocolPrefix: 'libp2p' } } diff --git a/src/fetch/constants.ts b/src/fetch/constants.ts index c9c425d60d..1c2551bb05 100644 --- a/src/fetch/constants.ts +++ b/src/fetch/constants.ts @@ -1,3 +1,4 @@ // https://github.com/libp2p/specs/tree/master/fetch#wire-protocol -export const PROTOCOL = '/libp2p/fetch/0.0.1' +export const PROTOCOL_VERSION = '0.0.1' +export const PROTOCOL_NAME = 'fetch' diff --git a/src/fetch/index.ts b/src/fetch/index.ts index 5ae9b67c69..59874cb0d5 100644 --- a/src/fetch/index.ts +++ b/src/fetch/index.ts @@ -4,16 +4,19 @@ import { codes } from '../errors.js' import * as lp from 'it-length-prefixed' import { FetchRequest, FetchResponse } from './pb/proto.js' import { handshake } from 'it-handshake' -import { PROTOCOL } from './constants.js' +import { PROTOCOL_NAME, PROTOCOL_VERSION } from './constants.js' import type { PeerId } from '@libp2p/interfaces/peer-id' import type { Startable } from '@libp2p/interfaces/startable' import type { Stream } from '@libp2p/interfaces/connection' import type { IncomingStreamData } from '@libp2p/interfaces/registrar' import type { Components } from '@libp2p/interfaces/components' +import type { AbortOptions } from '@libp2p/interfaces' +import type { Duplex } from 'it-stream-types' +import { abortableDuplex } from 'abortable-iterator' const log = logger('libp2p:fetch') -export interface FetchInit { +export interface FetchServiceInit { protocolPrefix: string } @@ -33,15 +36,15 @@ export interface LookupFunction { * by a fixed prefix that all keys that should be routed to that lookup function will start with. */ export class FetchService implements Startable { + public readonly protocol: string private readonly components: Components private readonly lookupFunctions: Map - private readonly protocol: string private started: boolean - constructor (components: Components, init: FetchInit) { + constructor (components: Components, init: FetchServiceInit) { this.started = false this.components = components - this.protocol = PROTOCOL + this.protocol = `/${init.protocolPrefix ?? 'libp2p'}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}` this.lookupFunctions = new Map() // Maps key prefix to value lookup function this.handleMessage = this.handleMessage.bind(this) } @@ -67,12 +70,19 @@ export class FetchService implements Startable { /** * Sends a request to fetch the value associated with the given key from the given peer */ - async fetch (peer: PeerId, key: string): Promise { + async fetch (peer: PeerId, key: string, options: AbortOptions = {}): Promise { log('dialing %s to %p', this.protocol, peer) - const connection = await this.components.getConnectionManager().openConnection(peer) - const { stream } = await connection.newStream([this.protocol]) - const shake = handshake(stream) + const connection = await this.components.getConnectionManager().openConnection(peer, options) + const { stream } = await connection.newStream([this.protocol], options) + let source: Duplex = stream + + // make stream abortable if AbortSignal passed + if (options.signal != null) { + source = abortableDuplex(stream, options.signal) + } + + const shake = handshake(source) // send message shake.write(lp.encode.single(FetchRequest.encode({ identifier: key })).slice()) diff --git a/src/identify/index.ts b/src/identify/index.ts index 023c289c16..123895aad6 100644 --- a/src/identify/index.ts +++ b/src/identify/index.ts @@ -2,8 +2,6 @@ import { logger } from '@libp2p/logger' import errCode from 'err-code' import * as lp from 'it-length-prefixed' import { pipe } from 'it-pipe' -import all from 'it-all' -import take from 'it-take' import drain from 'it-drain' import first from 'it-first' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' @@ -21,13 +19,19 @@ import { } from './consts.js' import { codes } from '../errors.js' import type { IncomingStreamData } from '@libp2p/interfaces/registrar' -import type { Connection } from '@libp2p/interfaces/connection' +import type { Connection, Stream } from '@libp2p/interfaces/connection' import type { Startable } from '@libp2p/interfaces/startable' import { peerIdFromKeys } from '@libp2p/peer-id' import type { Components } from '@libp2p/interfaces/components' +import { TimeoutController } from 'timeout-abort-controller' +import type { AbortOptions } from '@libp2p/interfaces' +import { abortableDuplex } from 'abortable-iterator' +import type { Duplex } from 'it-stream-types' const log = logger('libp2p:identify') +const IDENTIFY_TIMEOUT = 30000 + export interface HostProperties { agentVersion: string } @@ -35,6 +39,7 @@ export interface HostProperties { export interface IdentifyServiceInit { protocolPrefix: string host: HostProperties + timeout?: number } export class IdentifyService implements Startable { @@ -46,11 +51,13 @@ export class IdentifyService implements Startable { agentVersion: string } + private readonly init: IdentifyServiceInit private started: boolean constructor (components: Components, init: IdentifyServiceInit) { this.components = components this.started = false + this.init = init this.handleMessage = this.handleMessage.bind(this) @@ -128,8 +135,17 @@ export class IdentifyService implements Startable { const protocols = await this.components.getPeerStore().protoBook.get(this.components.getPeerId()) const pushes = connections.map(async connection => { + const timeoutController = new TimeoutController(this.init.timeout ?? IDENTIFY_TIMEOUT) + let stream: Stream | undefined + try { - const { stream } = await connection.newStream([this.identifyPushProtocolStr]) + const data = await connection.newStream([this.identifyPushProtocolStr], { + signal: timeoutController.signal + }) + stream = data.stream + + // make stream abortable + const source: Duplex = abortableDuplex(stream, timeoutController.signal) await pipe( [Identify.encode({ @@ -138,12 +154,18 @@ export class IdentifyService implements Startable { protocols })], lp.encode(), - stream, + source, drain ) } catch (err: any) { // Just log errors log.error('could not push identify update to peer', err) + } finally { + if (stream != null) { + stream.close() + } + + timeoutController.clear() } }) @@ -175,31 +197,44 @@ export class IdentifyService implements Startable { await this.push(connections) } + async _identify (connection: Connection, options: AbortOptions = {}): Promise { + const { stream } = await connection.newStream([this.identifyProtocolStr], options) + let source: Duplex = stream + + // make stream abortable if AbortSignal passed + if (options.signal != null) { + source = abortableDuplex(stream, options.signal) + } + + try { + const data = await pipe( + [], + source, + lp.decode(), + async (source) => await first(source) + ) + + if (data == null) { + throw errCode(new Error('No data could be retrieved'), codes.ERR_CONNECTION_ENDED) + } + + try { + return Identify.decode(data) + } catch (err: any) { + throw errCode(err, codes.ERR_INVALID_MESSAGE) + } + } finally { + stream.close() + } + } + /** * Requests the `Identify` message from peer associated with the given `connection`. * If the identified peer does not match the `PeerId` associated with the connection, * an error will be thrown. */ - async identify (connection: Connection): Promise { - const { stream } = await connection.newStream([this.identifyProtocolStr]) - const [data] = await pipe( - [], - stream, - lp.decode(), - (source) => take(source, 1), - async (source) => await all(source) - ) - - if (data == null) { - throw errCode(new Error('No data could be retrieved'), codes.ERR_CONNECTION_ENDED) - } - - let message: Identify - try { - message = Identify.decode(data) - } catch (err: any) { - throw errCode(err, codes.ERR_INVALID_MESSAGE) - } + async identify (connection: Connection, options: AbortOptions = {}): Promise { + const message = await this._identify(connection, options) const { publicKey, @@ -308,6 +343,8 @@ export class IdentifyService implements Startable { */ async _handleIdentify (data: IncomingStreamData) { const { connection, stream } = data + const timeoutController = new TimeoutController(this.init.timeout ?? IDENTIFY_TIMEOUT) + try { const publicKey = this.components.getPeerId().publicKey ?? new Uint8Array(0) const peerData = await this.components.getPeerStore().get(this.components.getPeerId()) @@ -335,14 +372,20 @@ export class IdentifyService implements Startable { protocols: peerData.protocols }) + // make stream abortable + const source: Duplex = abortableDuplex(stream, timeoutController.signal) + await pipe( [message], lp.encode(), - stream, + source, drain ) } catch (err: any) { log.error('could not respond to identify request', err) + } finally { + stream.close() + timeoutController.clear() } } @@ -351,12 +394,16 @@ export class IdentifyService implements Startable { */ async _handlePush (data: IncomingStreamData) { const { connection, stream } = data + const timeoutController = new TimeoutController(this.init.timeout ?? IDENTIFY_TIMEOUT) let message: Identify | undefined try { + // make stream abortable + const source: Duplex = abortableDuplex(stream, timeoutController.signal) + const data = await pipe( [], - stream, + source, lp.decode(), async (source) => await first(source) ) @@ -366,6 +413,9 @@ export class IdentifyService implements Startable { } } catch (err: any) { return log.error('received invalid message', err) + } finally { + stream.close() + timeoutController.clear() } if (message == null) { diff --git a/src/index.ts b/src/index.ts index 7d411bed7d..9a530f71ed 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ import type { EventEmitter } from '@libp2p/interfaces/events' import type { Startable } from '@libp2p/interfaces/startable' import type { Multiaddr } from '@multiformats/multiaddr' import type { FaultTolerance } from './transport-manager.js' -import type { HostProperties } from './identify/index.js' +import type { IdentifyServiceInit } from './identify/index.js' import type { DualDHT } from '@libp2p/interfaces/dht' import type { Datastore } from 'interface-datastore' import type { PeerStore, PeerStoreInit } from '@libp2p/interfaces/peer-store' @@ -24,6 +24,8 @@ import type { Metrics, MetricsInit } from '@libp2p/interfaces/metrics' import type { PeerInfo } from '@libp2p/interfaces/peer-info' import type { KeyChain } from './keychain/index.js' import type { ConnectionManagerInit } from './connection-manager/index.js' +import type { PingServiceInit } from './ping/index.js' +import type { FetchServiceInit } from './fetch/index.js' export interface PersistentPeerStoreOptions { threshold?: number @@ -95,7 +97,6 @@ export interface RefreshManagerConfig { export interface Libp2pInit { peerId: PeerId - host: HostProperties addresses: AddressesConfig connectionManager: ConnectionManagerInit connectionGater: Partial @@ -105,9 +106,11 @@ export interface Libp2pInit { peerStore: PeerStoreInit peerRouting: PeerRoutingConfig keychain: KeychainConfig - protocolPrefix: string nat: NatManagerConfig relay: RelayConfig + identify: IdentifyServiceInit + ping: PingServiceInit + fetch: FetchServiceInit transports: Transport[] streamMuxers?: StreamMuxerFactory[] @@ -195,12 +198,12 @@ export interface Libp2p extends Startable, EventEmitter { /** * Pings the given peer in order to obtain the operation latency */ - ping: (peer: Multiaddr |PeerId) => Promise + ping: (peer: Multiaddr | PeerId, options?: AbortOptions) => Promise /** * Sends a request to fetch the value associated with the given key from the given peer. */ - fetch: (peer: PeerId | Multiaddr | string, key: string) => Promise + fetch: (peer: PeerId | Multiaddr | string, key: string, options?: AbortOptions) => Promise /** * Returns the public key for the passed PeerId. If the PeerId is of the 'RSA' type diff --git a/src/libp2p.ts b/src/libp2p.ts index 611dbe102e..9d2dafed35 100644 --- a/src/libp2p.ts +++ b/src/libp2p.ts @@ -166,10 +166,7 @@ export class Libp2pNode extends EventEmitter implements Libp2p { if (init.streamMuxers != null && init.streamMuxers.length > 0) { // Add the identify service since we can multiplex this.identifyService = new IdentifyService(this.components, { - protocolPrefix: init.protocolPrefix, - host: { - agentVersion: init.host.agentVersion - } + ...init.identify }) this.configureComponent(this.identifyService) } @@ -229,11 +226,11 @@ export class Libp2pNode extends EventEmitter implements Libp2p { } this.fetchService = this.configureComponent(new FetchService(this.components, { - protocolPrefix: init.protocolPrefix + ...init.fetch })) this.pingService = this.configureComponent(new PingService(this.components, { - protocolPrefix: init.protocolPrefix + ...init.ping })) const autoDialer = this.configureComponent(new AutoDialer(this.components, { @@ -419,9 +416,9 @@ export class Libp2pNode extends EventEmitter implements Libp2p { throw errCode(new Error('no protocols were provided to open a stream'), codes.ERR_INVALID_PROTOCOLS_FOR_STREAM) } - const connection = await this.dial(peer) + const connection = await this.dial(peer, options) - return await connection.newStream(protocols) + return await connection.newStream(protocols, options) } getMultiaddrs (): Multiaddr[] { @@ -473,24 +470,24 @@ export class Libp2pNode extends EventEmitter implements Libp2p { throw errCode(new Error(`Node not responding with its public key: ${peer.toString()}`), codes.ERR_INVALID_RECORD) } - async fetch (peer: PeerId | Multiaddr | string, key: string): Promise { + async fetch (peer: PeerId | Multiaddr | string, key: string, options: AbortOptions = {}): Promise { const { id, multiaddrs } = getPeer(peer) if (multiaddrs != null) { await this.components.getPeerStore().addressBook.add(id, multiaddrs) } - return await this.fetchService.fetch(id, key) + return await this.fetchService.fetch(id, key, options) } - async ping (peer: PeerId | Multiaddr | string): Promise { + async ping (peer: PeerId | Multiaddr | string, options: AbortOptions = {}): Promise { const { id, multiaddrs } = getPeer(peer) if (multiaddrs.length > 0) { await this.components.getPeerStore().addressBook.add(id, multiaddrs) } - return await this.pingService.ping(id) + return await this.pingService.ping(id, options) } async handle (protocols: string | string[], handler: StreamHandler): Promise { diff --git a/src/ping/index.ts b/src/ping/index.ts index 03cceffe80..685e1cd5c2 100644 --- a/src/ping/index.ts +++ b/src/ping/index.ts @@ -10,6 +10,9 @@ import type { IncomingStreamData } from '@libp2p/interfaces/registrar' import type { PeerId } from '@libp2p/interfaces/peer-id' import type { Startable } from '@libp2p/interfaces/startable' import type { Components } from '@libp2p/interfaces/components' +import type { AbortOptions } from '@libp2p/interfaces' +import type { Duplex } from 'it-stream-types' +import { abortableDuplex } from 'abortable-iterator' const log = logger('libp2p:ping') @@ -18,8 +21,8 @@ export interface PingServiceInit { } export class PingService implements Startable { + public readonly protocol: string private readonly components: Components - private readonly protocol: string private started: boolean constructor (components: Components, init: PingServiceInit) { @@ -60,25 +63,36 @@ export class PingService implements Startable { * @param {PeerId|Multiaddr} peer * @returns {Promise} */ - async ping (peer: PeerId): Promise { + async ping (peer: PeerId, options: AbortOptions = {}): Promise { log('dialing %s to %p', this.protocol, peer) - const connection = await this.components.getConnectionManager().openConnection(peer) - const { stream } = await connection.newStream([this.protocol]) + const connection = await this.components.getConnectionManager().openConnection(peer, options) + const { stream } = await connection.newStream([this.protocol], options) const start = Date.now() const data = randomBytes(PING_LENGTH) - const result = await pipe( - [data], - stream, - async (source) => await first(source) - ) - const end = Date.now() + let source: Duplex = stream - if (result == null || !uint8ArrayEquals(data, result)) { - throw errCode(new Error('Received wrong ping ack'), codes.ERR_WRONG_PING_ACK) + // make stream abortable if AbortSignal passed + if (options.signal != null) { + source = abortableDuplex(stream, options.signal) } - return end - start + try { + const result = await pipe( + [data], + source, + async (source) => await first(source) + ) + const end = Date.now() + + if (result == null || !uint8ArrayEquals(data, result)) { + throw errCode(new Error('Received wrong ping ack'), codes.ERR_WRONG_PING_ACK) + } + + return end - start + } finally { + stream.close() + } } } diff --git a/src/upgrader.ts b/src/upgrader.ts index 27cd729ec9..f83365ac5d 100644 --- a/src/upgrader.ts +++ b/src/upgrader.ts @@ -15,6 +15,7 @@ import type { PeerId } from '@libp2p/interfaces/peer-id' import type { MultiaddrConnection, Upgrader, UpgraderEvents } from '@libp2p/interfaces/transport' import type { Duplex } from 'it-stream-types' import type { Components } from '@libp2p/interfaces/components' +import type { AbortOptions } from '@libp2p/interfaces' const log = logger('libp2p:upgrader') @@ -266,7 +267,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg } = opts let muxer: StreamMuxer | undefined - let newStream: ((multicodecs: string[]) => Promise) | undefined + let newStream: ((multicodecs: string[], options?: AbortOptions) => Promise) | undefined let connection: Connection // eslint-disable-line prefer-const if (muxerFactory != null) { @@ -308,7 +309,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg } }) - newStream = async (protocols: string[]): Promise => { + newStream = async (protocols: string[], options: AbortOptions = {}): Promise => { if (muxer == null) { throw errCode(new Error('Stream is not multiplexed'), codes.ERR_MUXER_UNAVAILABLE) } @@ -319,7 +320,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg const metrics = this.components.getMetrics() try { - let { stream, protocol } = await mss.select(protocols) + let { stream, protocol } = await mss.select(protocols, options) if (metrics != null) { stream = metrics.trackStream({ stream, remotePeer, protocol }) @@ -328,6 +329,11 @@ export class DefaultUpgrader extends EventEmitter implements Upg return { stream: { ...muxedStream, ...stream }, protocol } } catch (err: any) { log.error('could not create new stream', err) + + if (err.code != null) { + throw err + } + throw errCode(err, codes.ERR_UNSUPPORTED_PROTOCOL) } } diff --git a/test/configuration/protocol-prefix.node.ts b/test/configuration/protocol-prefix.node.ts index a48bbee1ce..c21cdd5658 100644 --- a/test/configuration/protocol-prefix.node.ts +++ b/test/configuration/protocol-prefix.node.ts @@ -18,13 +18,21 @@ describe('Protocol prefix is configurable', () => { it('protocolPrefix is provided', async () => { const testProtocol = 'test-protocol' libp2p = await createLibp2pNode(mergeOptions(baseOptions, { - protocolPrefix: testProtocol + identify: { + protocolPrefix: testProtocol + }, + ping: { + protocolPrefix: testProtocol + }, + fetch: { + protocolPrefix: testProtocol + } })) await libp2p.start() const protocols = await libp2p.peerStore.protoBook.get(libp2p.peerId) expect(protocols).to.include.members([ - '/libp2p/fetch/0.0.1', + `/${testProtocol}/fetch/0.0.1`, '/libp2p/circuit/relay/0.1.0', `/${testProtocol}/id/1.0.0`, `/${testProtocol}/id/push/1.0.0`, @@ -41,7 +49,8 @@ describe('Protocol prefix is configurable', () => { '/libp2p/circuit/relay/0.1.0', '/ipfs/id/1.0.0', '/ipfs/id/push/1.0.0', - '/ipfs/ping/1.0.0' + '/ipfs/ping/1.0.0', + '/libp2p/fetch/0.0.1' ]) }) }) diff --git a/test/fetch/index.spec.ts b/test/fetch/index.spec.ts new file mode 100644 index 0000000000..0e9d173b1b --- /dev/null +++ b/test/fetch/index.spec.ts @@ -0,0 +1,133 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/chai' +import sinon from 'sinon' +import { FetchService } from '../../src/fetch/index.js' +import Peers from '../fixtures/peers.js' +import { mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-compliance-tests/mocks' +import { createFromJSON } from '@libp2p/peer-id-factory' +import { Components } from '@libp2p/interfaces/components' +import { DefaultConnectionManager } from '../../src/connection-manager/index.js' +import { start, stop } from '@libp2p/interfaces/startable' +import { CustomEvent } from '@libp2p/interfaces/events' +import { TimeoutController } from 'timeout-abort-controller' +import delay from 'delay' +import { pipe } from 'it-pipe' + +const defaultInit = { + protocolPrefix: 'ipfs' +} + +async function createComponents (index: number) { + const peerId = await createFromJSON(Peers[index]) + + const components = new Components({ + peerId, + registrar: mockRegistrar(), + upgrader: mockUpgrader(), + connectionManager: new DefaultConnectionManager({ + minConnections: 50, + maxConnections: 1000, + autoDialInterval: 1000 + }) + }) + + return components +} + +describe('fetch', () => { + let localComponents: Components + let remoteComponents: Components + + beforeEach(async () => { + localComponents = await createComponents(0) + remoteComponents = await createComponents(1) + + await Promise.all([ + start(localComponents), + start(remoteComponents) + ]) + }) + + afterEach(async () => { + sinon.restore() + + await Promise.all([ + stop(localComponents), + stop(remoteComponents) + ]) + }) + + it('should be able to fetch from another peer', async () => { + const key = 'key' + const value = Uint8Array.from([0, 1, 2, 3, 4]) + const localFetch = new FetchService(localComponents, defaultInit) + const remoteFetch = new FetchService(remoteComponents, defaultInit) + + remoteFetch.registerLookupFunction(key, async (identifier) => { + expect(identifier).to.equal(key) + + return value + }) + + await start(localFetch) + await start(remoteFetch) + + // simulate connection between nodes + const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) + localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { detail: localToRemote })) + remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { detail: remoteToLocal })) + + // Run fetch + const result = await localFetch.fetch(remoteComponents.getPeerId(), key) + + expect(result).to.equalBytes(value) + }) + + it('should time out fetching from another peer when waiting for the record', async () => { + const key = 'key' + const localFetch = new FetchService(localComponents, defaultInit) + const remoteFetch = new FetchService(remoteComponents, defaultInit) + + await start(localFetch) + await start(remoteFetch) + + // simulate connection between nodes + const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) + localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { detail: localToRemote })) + remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { detail: remoteToLocal })) + + // replace existing handler with a really slow one + await remoteComponents.getRegistrar().unhandle(remoteFetch.protocol) + await remoteComponents.getRegistrar().handle(remoteFetch.protocol, ({ stream }) => { + void pipe( + stream, + async function * (source) { + for await (const chunk of source) { + // longer than the timeout + await delay(1000) + + yield chunk + } + }, + stream + ) + }) + + const newStreamSpy = sinon.spy(localToRemote, 'newStream') + + // 10 ms timeout + const timeoutController = new TimeoutController(10) + + // Run fetch, should time out + await expect(localFetch.fetch(remoteComponents.getPeerId(), key, { + signal: timeoutController.signal + })) + .to.eventually.be.rejected.with.property('code', 'ABORT_ERR') + + // should have closed stream + expect(newStreamSpy).to.have.property('callCount', 1) + const { stream } = await newStreamSpy.getCall(0).returnValue + expect(stream).to.have.nested.property('timeline.close') + }) +}) diff --git a/test/identify/index.spec.ts b/test/identify/index.spec.ts index df47bc58af..7ce5d75ee5 100644 --- a/test/identify/index.spec.ts +++ b/test/identify/index.spec.ts @@ -3,17 +3,13 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' import { Multiaddr } from '@multiformats/multiaddr' -import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { codes } from '../../src/errors.js' import { IdentifyService, Message } from '../../src/identify/index.js' import Peers from '../fixtures/peers.js' -import { createLibp2pNode } from '../../src/libp2p.js' import { PersistentPeerStore } from '@libp2p/peer-store' -import { createBaseOptions } from '../utils/base-options.browser.js' import { DefaultAddressManager } from '../../src/address-manager/index.js' import { MemoryDatastore } from 'datastore-core/memory' -import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' import * as lp from 'it-length-prefixed' import drain from 'it-drain' import { pipe } from 'it-pipe' @@ -27,14 +23,9 @@ import { } from '../../src/identify/consts.js' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' import { DefaultTransportManager } from '../../src/transport-manager.js' -import { CustomEvent } from '@libp2p/interfaces/events' import delay from 'delay' -import pWaitFor from 'p-wait-for' -import { peerIdFromString } from '@libp2p/peer-id' -import type { PeerId } from '@libp2p/interfaces/peer-id' -import type { Libp2pNode } from '../../src/libp2p.js' -import { pEvent } from 'p-event' import { start, stop } from '@libp2p/interfaces/startable' +import { TimeoutController } from 'timeout-abort-controller' const listenMaddrs = [new Multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] @@ -75,18 +66,16 @@ async function createComponents (index: number) { return components } -describe('Identify', () => { +describe('identify', () => { let localComponents: Components let remoteComponents: Components - let localPeerRecordUpdater: PeerRecordUpdater let remotePeerRecordUpdater: PeerRecordUpdater beforeEach(async () => { localComponents = await createComponents(0) remoteComponents = await createComponents(1) - localPeerRecordUpdater = new PeerRecordUpdater(localComponents) remotePeerRecordUpdater = new PeerRecordUpdater(remoteComponents) await Promise.all([ @@ -238,355 +227,47 @@ describe('Identify', () => { await stop(localIdentify) }) - describe('push', () => { - it('should be able to push identify updates to another peer', async () => { - const localIdentify = new IdentifyService(localComponents, defaultInit) - const remoteIdentify = new IdentifyService(remoteComponents, defaultInit) - - await start(localIdentify) - await start(remoteIdentify) - - const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) - - // ensure connections are registered by connection manager - localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { - detail: localToRemote - })) - remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { - detail: remoteToLocal - })) - - // identify both ways - await localIdentify.identify(localToRemote) - await remoteIdentify.identify(remoteToLocal) - - const updatedProtocol = '/special-new-protocol/1.0.0' - const updatedAddress = new Multiaddr('/ip4/127.0.0.1/tcp/48322') - - // should have protocols but not our new one - const identifiedProtocols = await remoteComponents.getPeerStore().protoBook.get(localComponents.getPeerId()) - expect(identifiedProtocols).to.not.be.empty() - expect(identifiedProtocols).to.not.include(updatedProtocol) - - // should have addresses but not our new one - const identifiedAddresses = await remoteComponents.getPeerStore().addressBook.get(localComponents.getPeerId()) - expect(identifiedAddresses).to.not.be.empty() - expect(identifiedAddresses.map(a => a.multiaddr.toString())).to.not.include(updatedAddress.toString()) - - // update local data - change event will trigger push - await localComponents.getPeerStore().protoBook.add(localComponents.getPeerId(), [updatedProtocol]) - await localComponents.getPeerStore().addressBook.add(localComponents.getPeerId(), [updatedAddress]) - - // needed to update the peer record and send our supported addresses - const addressManager = localComponents.getAddressManager() - addressManager.getAddresses = () => { - return [updatedAddress] - } - - // ensure sequence number of peer record we are about to create is different - await delay(1000) - - // make sure we have a peer record to send - await localPeerRecordUpdater.update() - - // wait for the remote peer store to notice the changes - const eventPromise = pEvent(remoteComponents.getPeerStore(), 'change:multiaddrs') - - // push updated peer record to connections - await localIdentify.pushToPeerStore() - - await eventPromise - - // should have new protocol - const updatedProtocols = await remoteComponents.getPeerStore().protoBook.get(localComponents.getPeerId()) - expect(updatedProtocols).to.not.be.empty() - expect(updatedProtocols).to.include(updatedProtocol) - - // should have new address - const updatedAddresses = await remoteComponents.getPeerStore().addressBook.get(localComponents.getPeerId()) - expect(updatedAddresses.map(a => { - return { - multiaddr: a.multiaddr.toString(), - isCertified: a.isCertified - } - })).to.deep.equal([{ - multiaddr: updatedAddress.toString(), - isCertified: true - }]) - - await stop(localIdentify) - await stop(remoteIdentify) - }) - - // LEGACY - it('should be able to push identify updates to another peer with no certified peer records support', async () => { - const localIdentify = new IdentifyService(localComponents, defaultInit) - const remoteIdentify = new IdentifyService(remoteComponents, defaultInit) - - await start(localIdentify) - await start(remoteIdentify) - - const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) - - // ensure connections are registered by connection manager - localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { - detail: localToRemote - })) - remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { - detail: remoteToLocal - })) - - // identify both ways - await localIdentify.identify(localToRemote) - await remoteIdentify.identify(remoteToLocal) - - const updatedProtocol = '/special-new-protocol/1.0.0' - const updatedAddress = new Multiaddr('/ip4/127.0.0.1/tcp/48322') - - // should have protocols but not our new one - const identifiedProtocols = await remoteComponents.getPeerStore().protoBook.get(localComponents.getPeerId()) - expect(identifiedProtocols).to.not.be.empty() - expect(identifiedProtocols).to.not.include(updatedProtocol) - - // should have addresses but not our new one - const identifiedAddresses = await remoteComponents.getPeerStore().addressBook.get(localComponents.getPeerId()) - expect(identifiedAddresses).to.not.be.empty() - expect(identifiedAddresses.map(a => a.multiaddr.toString())).to.not.include(updatedAddress.toString()) - - // update local data - change event will trigger push - await localComponents.getPeerStore().protoBook.add(localComponents.getPeerId(), [updatedProtocol]) - await localComponents.getPeerStore().addressBook.add(localComponents.getPeerId(), [updatedAddress]) - - // needed to send our supported addresses - const addressManager = localComponents.getAddressManager() - addressManager.getAddresses = () => { - return [updatedAddress] - } - - // wait until remote peer store notices protocol list update - const waitForUpdate = pEvent(remoteComponents.getPeerStore(), 'change:protocols') - - await localIdentify.pushToPeerStore() - - await waitForUpdate - - // should have new protocol - const updatedProtocols = await remoteComponents.getPeerStore().protoBook.get(localComponents.getPeerId()) - expect(updatedProtocols).to.not.be.empty() - expect(updatedProtocols).to.include(updatedProtocol) - - // should have new address - const updatedAddresses = await remoteComponents.getPeerStore().addressBook.get(localComponents.getPeerId()) - expect(updatedAddresses.map(a => { - return { - multiaddr: a.multiaddr.toString(), - isCertified: a.isCertified - } - })).to.deep.equal([{ - multiaddr: updatedAddress.toString(), - isCertified: false - }]) - - await stop(localIdentify) - await stop(remoteIdentify) - }) - }) - - describe('libp2p.dialer.identifyService', () => { - let peerId: PeerId - let libp2p: Libp2pNode - let remoteLibp2p: Libp2pNode - const remoteAddr = MULTIADDRS_WEBSOCKETS[0] - - before(async () => { - peerId = await createFromJSON(Peers[0]) - }) - - afterEach(async () => { - sinon.restore() - - if (libp2p != null) { - await libp2p.stop() - } - }) - - after(async () => { - if (remoteLibp2p != null) { - await remoteLibp2p.stop() - } - }) - - it('should run identify automatically after connecting', async () => { - libp2p = await createLibp2pNode(createBaseOptions({ - peerId - })) - - await libp2p.start() - - if (libp2p.identifyService == null) { - throw new Error('Identity service was not configured') - } - - const identityServiceIdentifySpy = sinon.spy(libp2p.identifyService, 'identify') - const peerStoreSpyConsumeRecord = sinon.spy(libp2p.peerStore.addressBook, 'consumePeerRecord') - const peerStoreSpyAdd = sinon.spy(libp2p.peerStore.addressBook, 'add') - - const connection = await libp2p.dial(remoteAddr) - expect(connection).to.exist() - - // Wait for peer store to be updated - // Dialer._createDialTarget (add), Identify (consume) - await pWaitFor(() => peerStoreSpyConsumeRecord.callCount === 1 && peerStoreSpyAdd.callCount === 1) - expect(identityServiceIdentifySpy.callCount).to.equal(1) - - // The connection should have no open streams - await pWaitFor(() => connection.streams.length === 0) - await connection.close() - }) - - it('should store remote agent and protocol versions in metadataBook after connecting', async () => { - libp2p = await createLibp2pNode(createBaseOptions({ - peerId - })) - - await libp2p.start() - - if (libp2p.identifyService == null) { - throw new Error('Identity service was not configured') - } - - const identityServiceIdentifySpy = sinon.spy(libp2p.identifyService, 'identify') - const peerStoreSpyConsumeRecord = sinon.spy(libp2p.peerStore.addressBook, 'consumePeerRecord') - const peerStoreSpyAdd = sinon.spy(libp2p.peerStore.addressBook, 'add') - - const connection = await libp2p.dial(remoteAddr) - expect(connection).to.exist() - - // Wait for peer store to be updated - // Dialer._createDialTarget (add), Identify (consume) - await pWaitFor(() => peerStoreSpyConsumeRecord.callCount === 1 && peerStoreSpyAdd.callCount === 1) - expect(identityServiceIdentifySpy.callCount).to.equal(1) - - // The connection should have no open streams - await pWaitFor(() => connection.streams.length === 0) - await connection.close() - - const remotePeer = peerIdFromString(remoteAddr.getPeerId() ?? '') - - const storedAgentVersion = await libp2p.peerStore.metadataBook.getValue(remotePeer, 'AgentVersion') - const storedProtocolVersion = await libp2p.peerStore.metadataBook.getValue(remotePeer, 'ProtocolVersion') - - expect(storedAgentVersion).to.exist() - expect(storedProtocolVersion).to.exist() - }) - - it('should push protocol updates to an already connected peer', async () => { - libp2p = await createLibp2pNode(createBaseOptions({ - peerId - })) - - await libp2p.start() - - if (libp2p.identifyService == null) { - throw new Error('Identity service was not configured') - } - - const identityServiceIdentifySpy = sinon.spy(libp2p.identifyService, 'identify') - const identityServicePushSpy = sinon.spy(libp2p.identifyService, 'push') - const connectionPromise = pEvent(libp2p.connectionManager, 'peer:connect') - const connection = await libp2p.dial(remoteAddr) - - expect(connection).to.exist() - // Wait for connection event to be emitted - await connectionPromise - - // Wait for identify to finish - await identityServiceIdentifySpy.firstCall.returnValue - sinon.stub(libp2p, 'isStarted').returns(true) - - await libp2p.handle('/echo/2.0.0', () => {}) - await libp2p.unhandle('/echo/2.0.0') - - // the protocol change event listener in the identity service is async - await pWaitFor(() => identityServicePushSpy.callCount === 2) - - // Verify the remote peer is notified of both changes - expect(identityServicePushSpy.callCount).to.equal(2) - - for (const call of identityServicePushSpy.getCalls()) { - const [connections] = call.args - expect(connections.length).to.equal(1) - expect(connections[0].remotePeer.toString()).to.equal(remoteAddr.getPeerId()) - await call.returnValue - } - - // Verify the streams close - await pWaitFor(() => connection.streams.length === 0) - }) - - it('should store host data and protocol version into metadataBook', async () => { - const agentVersion = 'js-project/1.0.0' - - libp2p = await createLibp2pNode(createBaseOptions({ - peerId, - host: { - agentVersion - } - })) - - await libp2p.start() + it('should time out during identify', async () => { + const localIdentify = new IdentifyService(localComponents, defaultInit) + const remoteIdentify = new IdentifyService(remoteComponents, defaultInit) - if (libp2p.identifyService == null) { - throw new Error('Identity service was not configured') - } + await start(localIdentify) + await start(remoteIdentify) - const storedAgentVersion = await libp2p.peerStore.metadataBook.getValue(peerId, 'AgentVersion') - const storedProtocolVersion = await libp2p.peerStore.metadataBook.getValue(peerId, 'ProtocolVersion') + const [localToRemote] = connectionPair(localComponents, remoteComponents) - expect(agentVersion).to.equal(uint8ArrayToString(storedAgentVersion ?? new Uint8Array())) - expect(storedProtocolVersion).to.exist() + // replace existing handler with a really slow one + await remoteComponents.getRegistrar().unhandle(MULTICODEC_IDENTIFY) + await remoteComponents.getRegistrar().handle(MULTICODEC_IDENTIFY, ({ stream }) => { + void pipe( + stream, + async function * (source) { + // we receive no data in the identify protocol, we just send our data + await drain(source) + + // longer than the timeout + await delay(1000) + + yield new Uint8Array() + }, + stream + ) }) - it('should push multiaddr updates to an already connected peer', async () => { - libp2p = await createLibp2pNode(createBaseOptions({ - peerId - })) + const newStreamSpy = sinon.spy(localToRemote, 'newStream') - await libp2p.start() + // 10 ms timeout + const timeoutController = new TimeoutController(10) - if (libp2p.identifyService == null) { - throw new Error('Identity service was not configured') - } - - const identityServiceIdentifySpy = sinon.spy(libp2p.identifyService, 'identify') - const identityServicePushSpy = sinon.spy(libp2p.identifyService, 'push') - const connectionPromise = pEvent(libp2p.connectionManager, 'peer:connect') - const connection = await libp2p.dial(remoteAddr) - - expect(connection).to.exist() - // Wait for connection event to be emitted - await connectionPromise - - // Wait for identify to finish - await identityServiceIdentifySpy.firstCall.returnValue - sinon.stub(libp2p, 'isStarted').returns(true) - - await libp2p.peerStore.addressBook.add(libp2p.peerId, [new Multiaddr('/ip4/180.0.0.1/tcp/15001/ws')]) - - // the protocol change event listener in the identity service is async - await pWaitFor(() => identityServicePushSpy.callCount === 1) - - // Verify the remote peer is notified of change - expect(identityServicePushSpy.callCount).to.equal(1) - for (const call of identityServicePushSpy.getCalls()) { - const [connections] = call.args - expect(connections.length).to.equal(1) - expect(connections[0].remotePeer.toString()).to.equal(remoteAddr.getPeerId()) - await call.returnValue - } - - // Verify the streams close - await pWaitFor(() => connection.streams.length === 0) - }) + // Run identify + await expect(localIdentify.identify(localToRemote, { + signal: timeoutController.signal + })) + .to.eventually.be.rejected.with.property('code', 'ABORT_ERR') + + // should have closed stream + expect(newStreamSpy).to.have.property('callCount', 1) + const { stream } = await newStreamSpy.getCall(0).returnValue + expect(stream).to.have.nested.property('timeline.close') }) }) diff --git a/test/identify/push.spec.ts b/test/identify/push.spec.ts new file mode 100644 index 0000000000..8f0b62e667 --- /dev/null +++ b/test/identify/push.spec.ts @@ -0,0 +1,296 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/chai' +import sinon from 'sinon' +import { Multiaddr } from '@multiformats/multiaddr' +import { IdentifyService } from '../../src/identify/index.js' +import Peers from '../fixtures/peers.js' +import { PersistentPeerStore } from '@libp2p/peer-store' +import { DefaultAddressManager } from '../../src/address-manager/index.js' +import { MemoryDatastore } from 'datastore-core/memory' +import drain from 'it-drain' +import { pipe } from 'it-pipe' +import { mockConnectionGater, mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-compliance-tests/mocks' +import { createFromJSON } from '@libp2p/peer-id-factory' +import { Components } from '@libp2p/interfaces/components' +import { PeerRecordUpdater } from '../../src/peer-record-updater.js' +import { + MULTICODEC_IDENTIFY, + MULTICODEC_IDENTIFY_PUSH +} from '../../src/identify/consts.js' +import { DefaultConnectionManager } from '../../src/connection-manager/index.js' +import { DefaultTransportManager } from '../../src/transport-manager.js' +import { CustomEvent } from '@libp2p/interfaces/events' +import delay from 'delay' +import { pEvent } from 'p-event' +import { start, stop } from '@libp2p/interfaces/startable' + +const listenMaddrs = [new Multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] + +const defaultInit = { + protocolPrefix: 'ipfs', + host: { + agentVersion: 'v1.0.0' + } +} + +const protocols = [MULTICODEC_IDENTIFY, MULTICODEC_IDENTIFY_PUSH] + +async function createComponents (index: number) { + const peerId = await createFromJSON(Peers[index]) + + const components = new Components({ + peerId, + datastore: new MemoryDatastore(), + registrar: mockRegistrar(), + upgrader: mockUpgrader(), + connectionGater: mockConnectionGater(), + peerStore: new PersistentPeerStore(), + connectionManager: new DefaultConnectionManager({ + minConnections: 50, + maxConnections: 1000, + autoDialInterval: 1000 + }) + }) + components.setAddressManager(new DefaultAddressManager(components, { + announce: listenMaddrs.map(ma => ma.toString()) + })) + + const transportManager = new DefaultTransportManager(components) + components.setTransportManager(transportManager) + + await components.getPeerStore().protoBook.set(peerId, protocols) + + return components +} + +describe('identify (push)', () => { + let localComponents: Components + let remoteComponents: Components + + let localPeerRecordUpdater: PeerRecordUpdater + + beforeEach(async () => { + localComponents = await createComponents(0) + remoteComponents = await createComponents(1) + + localPeerRecordUpdater = new PeerRecordUpdater(localComponents) + + await Promise.all([ + start(localComponents), + start(remoteComponents) + ]) + }) + + afterEach(async () => { + sinon.restore() + + await Promise.all([ + stop(localComponents), + stop(remoteComponents) + ]) + }) + + it('should be able to push identify updates to another peer', async () => { + const localIdentify = new IdentifyService(localComponents, defaultInit) + const remoteIdentify = new IdentifyService(remoteComponents, defaultInit) + + await start(localIdentify) + await start(remoteIdentify) + + const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) + + // ensure connections are registered by connection manager + localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { + detail: localToRemote + })) + remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { + detail: remoteToLocal + })) + + // identify both ways + await localIdentify.identify(localToRemote) + await remoteIdentify.identify(remoteToLocal) + + const updatedProtocol = '/special-new-protocol/1.0.0' + const updatedAddress = new Multiaddr('/ip4/127.0.0.1/tcp/48322') + + // should have protocols but not our new one + const identifiedProtocols = await remoteComponents.getPeerStore().protoBook.get(localComponents.getPeerId()) + expect(identifiedProtocols).to.not.be.empty() + expect(identifiedProtocols).to.not.include(updatedProtocol) + + // should have addresses but not our new one + const identifiedAddresses = await remoteComponents.getPeerStore().addressBook.get(localComponents.getPeerId()) + expect(identifiedAddresses).to.not.be.empty() + expect(identifiedAddresses.map(a => a.multiaddr.toString())).to.not.include(updatedAddress.toString()) + + // update local data - change event will trigger push + await localComponents.getPeerStore().protoBook.add(localComponents.getPeerId(), [updatedProtocol]) + await localComponents.getPeerStore().addressBook.add(localComponents.getPeerId(), [updatedAddress]) + + // needed to update the peer record and send our supported addresses + const addressManager = localComponents.getAddressManager() + addressManager.getAddresses = () => { + return [updatedAddress] + } + + // ensure sequence number of peer record we are about to create is different + await delay(1000) + + // make sure we have a peer record to send + await localPeerRecordUpdater.update() + + // wait for the remote peer store to notice the changes + const eventPromise = pEvent(remoteComponents.getPeerStore(), 'change:multiaddrs') + + // push updated peer record to connections + await localIdentify.pushToPeerStore() + + await eventPromise + + // should have new protocol + const updatedProtocols = await remoteComponents.getPeerStore().protoBook.get(localComponents.getPeerId()) + expect(updatedProtocols).to.not.be.empty() + expect(updatedProtocols).to.include(updatedProtocol) + + // should have new address + const updatedAddresses = await remoteComponents.getPeerStore().addressBook.get(localComponents.getPeerId()) + expect(updatedAddresses.map(a => { + return { + multiaddr: a.multiaddr.toString(), + isCertified: a.isCertified + } + })).to.deep.equal([{ + multiaddr: updatedAddress.toString(), + isCertified: true + }]) + + await stop(localIdentify) + await stop(remoteIdentify) + }) + + it('should time out during push identify', async () => { + let streamEnded = false + const localIdentify = new IdentifyService(localComponents, { + ...defaultInit, + timeout: 10 + }) + const remoteIdentify = new IdentifyService(remoteComponents, defaultInit) + + await start(localIdentify) + await start(remoteIdentify) + + // simulate connection between nodes + const [localToRemote] = connectionPair(localComponents, remoteComponents) + + // replace existing handler with a really slow one + await remoteComponents.getRegistrar().unhandle(MULTICODEC_IDENTIFY_PUSH) + await remoteComponents.getRegistrar().handle(MULTICODEC_IDENTIFY_PUSH, ({ stream }) => { + void pipe( + stream, + async function * (source) { + // ignore the sent data + await drain(source) + + // longer than the timeout + await delay(1000) + + // the delay should have caused the local push to time out so this should + // occur after the local push method invocation has completed + streamEnded = true + + yield new Uint8Array() + }, + stream + ) + }) + + const newStreamSpy = sinon.spy(localToRemote, 'newStream') + + // push updated peer record to remote + await localIdentify.push([localToRemote]) + + // should have closed stream + expect(newStreamSpy).to.have.property('callCount', 1) + const { stream } = await newStreamSpy.getCall(0).returnValue + expect(stream).to.have.nested.property('timeline.close') + + // method should have returned before the remote handler completes as we timed + // out so we ignore the return value + expect(streamEnded).to.be.false() + }) + + // LEGACY + it('should be able to push identify updates to another peer with no certified peer records support', async () => { + const localIdentify = new IdentifyService(localComponents, defaultInit) + const remoteIdentify = new IdentifyService(remoteComponents, defaultInit) + + await start(localIdentify) + await start(remoteIdentify) + + const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) + + // ensure connections are registered by connection manager + localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { + detail: localToRemote + })) + remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { + detail: remoteToLocal + })) + + // identify both ways + await localIdentify.identify(localToRemote) + await remoteIdentify.identify(remoteToLocal) + + const updatedProtocol = '/special-new-protocol/1.0.0' + const updatedAddress = new Multiaddr('/ip4/127.0.0.1/tcp/48322') + + // should have protocols but not our new one + const identifiedProtocols = await remoteComponents.getPeerStore().protoBook.get(localComponents.getPeerId()) + expect(identifiedProtocols).to.not.be.empty() + expect(identifiedProtocols).to.not.include(updatedProtocol) + + // should have addresses but not our new one + const identifiedAddresses = await remoteComponents.getPeerStore().addressBook.get(localComponents.getPeerId()) + expect(identifiedAddresses).to.not.be.empty() + expect(identifiedAddresses.map(a => a.multiaddr.toString())).to.not.include(updatedAddress.toString()) + + // update local data - change event will trigger push + await localComponents.getPeerStore().protoBook.add(localComponents.getPeerId(), [updatedProtocol]) + await localComponents.getPeerStore().addressBook.add(localComponents.getPeerId(), [updatedAddress]) + + // needed to send our supported addresses + const addressManager = localComponents.getAddressManager() + addressManager.getAddresses = () => { + return [updatedAddress] + } + + // wait until remote peer store notices protocol list update + const waitForUpdate = pEvent(remoteComponents.getPeerStore(), 'change:protocols') + + await localIdentify.pushToPeerStore() + + await waitForUpdate + + // should have new protocol + const updatedProtocols = await remoteComponents.getPeerStore().protoBook.get(localComponents.getPeerId()) + expect(updatedProtocols).to.not.be.empty() + expect(updatedProtocols).to.include(updatedProtocol) + + // should have new address + const updatedAddresses = await remoteComponents.getPeerStore().addressBook.get(localComponents.getPeerId()) + expect(updatedAddresses.map(a => { + return { + multiaddr: a.multiaddr.toString(), + isCertified: a.isCertified + } + })).to.deep.equal([{ + multiaddr: updatedAddress.toString(), + isCertified: false + }]) + + await stop(localIdentify) + await stop(remoteIdentify) + }) +}) diff --git a/test/identify/service.spec.ts b/test/identify/service.spec.ts new file mode 100644 index 0000000000..3f198eb2ab --- /dev/null +++ b/test/identify/service.spec.ts @@ -0,0 +1,216 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/chai' +import sinon from 'sinon' +import { Multiaddr } from '@multiformats/multiaddr' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import Peers from '../fixtures/peers.js' +import { createLibp2pNode } from '../../src/libp2p.js' +import { createBaseOptions } from '../utils/base-options.browser.js' +import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' +import { createFromJSON } from '@libp2p/peer-id-factory' +import pWaitFor from 'p-wait-for' +import { peerIdFromString } from '@libp2p/peer-id' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { Libp2pNode } from '../../src/libp2p.js' +import { pEvent } from 'p-event' + +describe('libp2p.dialer.identifyService', () => { + let peerId: PeerId + let libp2p: Libp2pNode + let remoteLibp2p: Libp2pNode + const remoteAddr = MULTIADDRS_WEBSOCKETS[0] + + before(async () => { + peerId = await createFromJSON(Peers[0]) + }) + + afterEach(async () => { + sinon.restore() + + if (libp2p != null) { + await libp2p.stop() + } + }) + + after(async () => { + if (remoteLibp2p != null) { + await remoteLibp2p.stop() + } + }) + + it('should run identify automatically after connecting', async () => { + libp2p = await createLibp2pNode(createBaseOptions({ + peerId + })) + + await libp2p.start() + + if (libp2p.identifyService == null) { + throw new Error('Identity service was not configured') + } + + const identityServiceIdentifySpy = sinon.spy(libp2p.identifyService, 'identify') + const peerStoreSpyConsumeRecord = sinon.spy(libp2p.peerStore.addressBook, 'consumePeerRecord') + const peerStoreSpyAdd = sinon.spy(libp2p.peerStore.addressBook, 'add') + + const connection = await libp2p.dial(remoteAddr) + expect(connection).to.exist() + + // Wait for peer store to be updated + // Dialer._createDialTarget (add), Identify (consume) + await pWaitFor(() => peerStoreSpyConsumeRecord.callCount === 1 && peerStoreSpyAdd.callCount === 1) + expect(identityServiceIdentifySpy.callCount).to.equal(1) + + // The connection should have no open streams + await pWaitFor(() => connection.streams.length === 0) + await connection.close() + }) + + it('should store remote agent and protocol versions in metadataBook after connecting', async () => { + libp2p = await createLibp2pNode(createBaseOptions({ + peerId + })) + + await libp2p.start() + + if (libp2p.identifyService == null) { + throw new Error('Identity service was not configured') + } + + const identityServiceIdentifySpy = sinon.spy(libp2p.identifyService, 'identify') + const peerStoreSpyConsumeRecord = sinon.spy(libp2p.peerStore.addressBook, 'consumePeerRecord') + const peerStoreSpyAdd = sinon.spy(libp2p.peerStore.addressBook, 'add') + + const connection = await libp2p.dial(remoteAddr) + expect(connection).to.exist() + + // Wait for peer store to be updated + // Dialer._createDialTarget (add), Identify (consume) + await pWaitFor(() => peerStoreSpyConsumeRecord.callCount === 1 && peerStoreSpyAdd.callCount === 1) + expect(identityServiceIdentifySpy.callCount).to.equal(1) + + // The connection should have no open streams + await pWaitFor(() => connection.streams.length === 0) + await connection.close() + + const remotePeer = peerIdFromString(remoteAddr.getPeerId() ?? '') + + const storedAgentVersion = await libp2p.peerStore.metadataBook.getValue(remotePeer, 'AgentVersion') + const storedProtocolVersion = await libp2p.peerStore.metadataBook.getValue(remotePeer, 'ProtocolVersion') + + expect(storedAgentVersion).to.exist() + expect(storedProtocolVersion).to.exist() + }) + + it('should push protocol updates to an already connected peer', async () => { + libp2p = await createLibp2pNode(createBaseOptions({ + peerId + })) + + await libp2p.start() + + if (libp2p.identifyService == null) { + throw new Error('Identity service was not configured') + } + + const identityServiceIdentifySpy = sinon.spy(libp2p.identifyService, 'identify') + const identityServicePushSpy = sinon.spy(libp2p.identifyService, 'push') + const connectionPromise = pEvent(libp2p.connectionManager, 'peer:connect') + const connection = await libp2p.dial(remoteAddr) + + expect(connection).to.exist() + // Wait for connection event to be emitted + await connectionPromise + + // Wait for identify to finish + await identityServiceIdentifySpy.firstCall.returnValue + sinon.stub(libp2p, 'isStarted').returns(true) + + await libp2p.handle('/echo/2.0.0', () => {}) + await libp2p.unhandle('/echo/2.0.0') + + // the protocol change event listener in the identity service is async + await pWaitFor(() => identityServicePushSpy.callCount === 2) + + // Verify the remote peer is notified of both changes + expect(identityServicePushSpy.callCount).to.equal(2) + + for (const call of identityServicePushSpy.getCalls()) { + const [connections] = call.args + expect(connections.length).to.equal(1) + expect(connections[0].remotePeer.toString()).to.equal(remoteAddr.getPeerId()) + await call.returnValue + } + + // Verify the streams close + await pWaitFor(() => connection.streams.length === 0) + }) + + it('should store host data and protocol version into metadataBook', async () => { + const agentVersion = 'js-project/1.0.0' + + libp2p = await createLibp2pNode(createBaseOptions({ + peerId, + identify: { + host: { + agentVersion + } + } + })) + + await libp2p.start() + + if (libp2p.identifyService == null) { + throw new Error('Identity service was not configured') + } + + const storedAgentVersion = await libp2p.peerStore.metadataBook.getValue(peerId, 'AgentVersion') + const storedProtocolVersion = await libp2p.peerStore.metadataBook.getValue(peerId, 'ProtocolVersion') + + expect(agentVersion).to.equal(uint8ArrayToString(storedAgentVersion ?? new Uint8Array())) + expect(storedProtocolVersion).to.exist() + }) + + it('should push multiaddr updates to an already connected peer', async () => { + libp2p = await createLibp2pNode(createBaseOptions({ + peerId + })) + + await libp2p.start() + + if (libp2p.identifyService == null) { + throw new Error('Identity service was not configured') + } + + const identityServiceIdentifySpy = sinon.spy(libp2p.identifyService, 'identify') + const identityServicePushSpy = sinon.spy(libp2p.identifyService, 'push') + const connectionPromise = pEvent(libp2p.connectionManager, 'peer:connect') + const connection = await libp2p.dial(remoteAddr) + + expect(connection).to.exist() + // Wait for connection event to be emitted + await connectionPromise + + // Wait for identify to finish + await identityServiceIdentifySpy.firstCall.returnValue + sinon.stub(libp2p, 'isStarted').returns(true) + + await libp2p.peerStore.addressBook.add(libp2p.peerId, [new Multiaddr('/ip4/180.0.0.1/tcp/15001/ws')]) + + // the protocol change event listener in the identity service is async + await pWaitFor(() => identityServicePushSpy.callCount === 1) + + // Verify the remote peer is notified of change + expect(identityServicePushSpy.callCount).to.equal(1) + for (const call of identityServicePushSpy.getCalls()) { + const [connections] = call.args + expect(connections.length).to.equal(1) + expect(connections[0].remotePeer.toString()).to.equal(remoteAddr.getPeerId()) + await call.returnValue + } + + // Verify the streams close + await pWaitFor(() => connection.streams.length === 0) + }) +}) diff --git a/test/ping/index.spec.ts b/test/ping/index.spec.ts new file mode 100644 index 0000000000..d97837bb13 --- /dev/null +++ b/test/ping/index.spec.ts @@ -0,0 +1,122 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/chai' +import sinon from 'sinon' +import { PingService } from '../../src/ping/index.js' +import Peers from '../fixtures/peers.js' +import { mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-compliance-tests/mocks' +import { createFromJSON } from '@libp2p/peer-id-factory' +import { Components } from '@libp2p/interfaces/components' +import { DefaultConnectionManager } from '../../src/connection-manager/index.js' +import { start, stop } from '@libp2p/interfaces/startable' +import { CustomEvent } from '@libp2p/interfaces/events' +import { TimeoutController } from 'timeout-abort-controller' +import delay from 'delay' +import { pipe } from 'it-pipe' + +const defaultInit = { + protocolPrefix: 'ipfs' +} + +async function createComponents (index: number) { + const peerId = await createFromJSON(Peers[index]) + + const components = new Components({ + peerId, + registrar: mockRegistrar(), + upgrader: mockUpgrader(), + connectionManager: new DefaultConnectionManager({ + minConnections: 50, + maxConnections: 1000, + autoDialInterval: 1000 + }) + }) + + return components +} + +describe('ping', () => { + let localComponents: Components + let remoteComponents: Components + + beforeEach(async () => { + localComponents = await createComponents(0) + remoteComponents = await createComponents(1) + + await Promise.all([ + start(localComponents), + start(remoteComponents) + ]) + }) + + afterEach(async () => { + sinon.restore() + + await Promise.all([ + stop(localComponents), + stop(remoteComponents) + ]) + }) + + it('should be able to ping another peer', async () => { + const localPing = new PingService(localComponents, defaultInit) + const remotePing = new PingService(remoteComponents, defaultInit) + + await start(localPing) + await start(remotePing) + + // simulate connection between nodes + const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) + localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { detail: localToRemote })) + remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { detail: remoteToLocal })) + + // Run ping + await expect(localPing.ping(remoteComponents.getPeerId())).to.eventually.be.gte(0) + }) + + it('should time out pinging another peer when waiting for a pong', async () => { + const localPing = new PingService(localComponents, defaultInit) + const remotePing = new PingService(remoteComponents, defaultInit) + + await start(localPing) + await start(remotePing) + + // simulate connection between nodes + const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) + localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { detail: localToRemote })) + remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { detail: remoteToLocal })) + + // replace existing handler with a really slow one + await remoteComponents.getRegistrar().unhandle(remotePing.protocol) + await remoteComponents.getRegistrar().handle(remotePing.protocol, ({ stream }) => { + void pipe( + stream, + async function * (source) { + for await (const chunk of source) { + // longer than the timeout + await delay(1000) + + yield chunk + } + }, + stream + ) + }) + + const newStreamSpy = sinon.spy(localToRemote, 'newStream') + + // 10 ms timeout + const timeoutController = new TimeoutController(10) + + // Run ping, should time out + await expect(localPing.ping(remoteComponents.getPeerId(), { + signal: timeoutController.signal + })) + .to.eventually.be.rejected.with.property('code', 'ABORT_ERR') + + // should have closed stream + expect(newStreamSpy).to.have.property('callCount', 1) + const { stream } = await newStreamSpy.getCall(0).returnValue + expect(stream).to.have.nested.property('timeline.close') + }) +}) diff --git a/test/core/ping.node.ts b/test/ping/ping.node.ts similarity index 100% rename from test/core/ping.node.ts rename to test/ping/ping.node.ts diff --git a/test/upgrading/upgrader.spec.ts b/test/upgrading/upgrader.spec.ts index 56e5eb968d..6a6772988c 100644 --- a/test/upgrading/upgrader.spec.ts +++ b/test/upgrading/upgrader.spec.ts @@ -14,7 +14,7 @@ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import swarmKey from '../fixtures/swarm.key.js' import { DefaultUpgrader } from '../../src/upgrader.js' import { codes } from '../../src/errors.js' -import { mockConnectionGater, mockMultiaddrConnPair, mockRegistrar } from '@libp2p/interface-compliance-tests/mocks' +import { mockConnectionGater, mockMultiaddrConnPair, mockRegistrar, mockStream } from '@libp2p/interface-compliance-tests/mocks' import Peers from '../fixtures/peers.js' import type { Upgrader } from '@libp2p/interfaces/transport' import type { PeerId } from '@libp2p/interfaces/peer-id' @@ -27,6 +27,9 @@ import type { Stream } from '@libp2p/interfaces/connection' import pDefer from 'p-defer' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' import { pEvent } from 'p-event' +import { TimeoutController } from 'timeout-abort-controller' +import delay from 'delay' +import drain from 'it-drain' const addrs = [ new Multiaddr('/ip4/127.0.0.1/tcp/0'), @@ -35,6 +38,7 @@ const addrs = [ describe('Upgrader', () => { let localUpgrader: Upgrader + let localMuxerFactory: StreamMuxerFactory let remoteUpgrader: Upgrader let localPeer: PeerId let remotePeer: PeerId @@ -55,12 +59,13 @@ describe('Upgrader', () => { connectionGater: mockConnectionGater(), registrar: mockRegistrar() }) + localMuxerFactory = new Mplex() localUpgrader = new DefaultUpgrader(localComponents, { connectionEncryption: [ new Plaintext() ], muxers: [ - new Mplex() + localMuxerFactory ] }) @@ -366,6 +371,40 @@ describe('Upgrader', () => { expect(result).to.have.nested.property('reason.code', codes.ERR_UNSUPPORTED_PROTOCOL) }) }) + + it('should abort protocol selection for slow streams', async () => { + const createStreamMuxerSpy = sinon.spy(localMuxerFactory, 'createStreamMuxer') + const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) + + const connections = await Promise.all([ + localUpgrader.upgradeOutbound(outbound), + remoteUpgrader.upgradeInbound(inbound) + ]) + + // 10 ms timeout + const timeoutController = new TimeoutController(10) + + // should have created muxer for connection + expect(createStreamMuxerSpy).to.have.property('callCount', 1) + + // create mock muxed stream that never sends data + const muxer = createStreamMuxerSpy.getCall(0).returnValue + muxer.newStream = () => { + return mockStream({ + source: (async function * () { + // longer than the timeout + await delay(1000) + yield new Uint8Array() + }()), + sink: drain + }) + } + + await expect(connections[0].newStream('/echo/1.0.0', { + signal: timeoutController.signal + })) + .to.eventually.be.rejected.with.property('code', 'ABORT_ERR') + }) }) describe('libp2p.upgrader', () => { From 4c0c2c6d3edac0d16e48f13ce082cc4ee473241e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 26 May 2022 08:30:50 +0100 Subject: [PATCH 370/447] chore: release 0.37.1 (#1220) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 10 ++++++++++ package.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7392893c04..ccb3582be2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,16 @@ +### [0.37.1](https://www.github.com/libp2p/js-libp2p/compare/v0.37.0...v0.37.1) (2022-05-25) + + +### Bug Fixes + +* do upnp hole punch after startup ([#1217](https://www.github.com/libp2p/js-libp2p/issues/1217)) ([d5386df](https://www.github.com/libp2p/js-libp2p/commit/d5386df68478a71ac269acb2d00d36a7a5c9ebc5)) +* explicitly close streams when connnections close ([#1221](https://www.github.com/libp2p/js-libp2p/issues/1221)) ([b09eb8f](https://www.github.com/libp2p/js-libp2p/commit/b09eb8fc53ec1d8f6280d681c9ca6a467ec259b5)) +* fix unintended aborts in dialer ([#1185](https://www.github.com/libp2p/js-libp2p/issues/1185)) ([35f9c0c](https://www.github.com/libp2p/js-libp2p/commit/35f9c0c79387232465848b450a47cafe841405e7)) +* time out slow reads ([#1227](https://www.github.com/libp2p/js-libp2p/issues/1227)) ([a1220d2](https://www.github.com/libp2p/js-libp2p/commit/a1220d22f5affb64e64dec0cd6a92cd8241b26df)) + ## [0.37.0](https://www.github.com/libp2p/js-libp2p/compare/v0.36.2...v0.37.0) (2022-05-16) diff --git a/package.json b/package.json index 219967771b..bc170c437b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.37.0", + "version": "0.37.1", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p#readme", From 824720fb8f21f868ed88e881fbc3ce6b9459600d Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 31 May 2022 17:10:40 +0100 Subject: [PATCH 371/447] fix: reduce identify message size limit (#1230) Adds a config option to specify a maximum message size we'll accept for an Identify message. The default is 8KB, the same as go-libp2p - previously we fell back to the default `maxMessageLength` option of `it-length-prefixed` which is 4MB. Also adds a default timeout for reading responses to identify requests which is used if an AbortSignal is not passed in. The default timeout also aligns with go-libp2p. --- doc/API.md | 2 +- src/identify/index.ts | 46 +++++++++++++--- test/identify/index.spec.ts | 104 ++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 7 deletions(-) diff --git a/doc/API.md b/doc/API.md index 46c8e7a7ee..d8ba8fd83e 100644 --- a/doc/API.md +++ b/doc/API.md @@ -97,7 +97,7 @@ Creates an instance of Libp2p. | options.modules | [`Array`](./CONFIGURATION.md#modules) | libp2p [modules](./CONFIGURATION.md#modules) to use | | [options.addresses] | `{ listen: Array, announce: Array, announceFilter: (ma: Array) => Array }` | Addresses for transport listening and to advertise to the network | | [options.config] | `object` | libp2p modules configuration and core configuration | -| [options.identify] | `{ protocolPrefix: string, host: { agentVersion: string }, timeout: number }` | libp2p identify protocol options | +| [options.identify] | `{ protocolPrefix: string, host: { agentVersion: string }, timeout: number, maxIdentifyMessageSize: number }` | libp2p identify protocol options | | [options.ping] | `{ protocolPrefix: string }` | libp2p ping protocol options | | [options.fetch] | `{ protocolPrefix: string }` | libp2p fetch protocol options | | [options.connectionManager] | [`object`](./CONFIGURATION.md#configuring-connection-manager) | libp2p Connection Manager [configuration](./CONFIGURATION.md#configuring-connection-manager) | diff --git a/src/identify/index.ts b/src/identify/index.ts index 123895aad6..777c35c1aa 100644 --- a/src/identify/index.ts +++ b/src/identify/index.ts @@ -30,16 +30,36 @@ import type { Duplex } from 'it-stream-types' const log = logger('libp2p:identify') -const IDENTIFY_TIMEOUT = 30000 +// https://github.com/libp2p/go-libp2p/blob/8d2e54e1637041d5cf4fac1e531287560bd1f4ac/p2p/protocol/identify/id.go#L48 +const IDENTIFY_TIMEOUT = 60000 + +// https://github.com/libp2p/go-libp2p/blob/8d2e54e1637041d5cf4fac1e531287560bd1f4ac/p2p/protocol/identify/id.go#L52 +const MAX_IDENTIFY_MESSAGE_SIZE = 1024 * 8 export interface HostProperties { agentVersion: string } export interface IdentifyServiceInit { + /** + * The prefix to use for the protocol (default: 'ipfs') + */ protocolPrefix: string + + /** + * What details we should send as part of an identify message + */ host: HostProperties + + /** + * How long we should wait for a remote peer to send their identify response + */ timeout?: number + + /** + * Identify responses larger than this in bytes will be rejected (default: 8192) + */ + maxIdentifyMessageSize?: number } export class IdentifyService implements Startable { @@ -200,17 +220,25 @@ export class IdentifyService implements Startable { async _identify (connection: Connection, options: AbortOptions = {}): Promise { const { stream } = await connection.newStream([this.identifyProtocolStr], options) let source: Duplex = stream + let timeoutController + let signal = options.signal - // make stream abortable if AbortSignal passed - if (options.signal != null) { - source = abortableDuplex(stream, options.signal) + // create a timeout if no abort signal passed + if (signal == null) { + timeoutController = new TimeoutController(this.init.timeout ?? IDENTIFY_TIMEOUT) + signal = timeoutController.signal } + // make stream abortable if AbortSignal passed + source = abortableDuplex(stream, signal) + try { const data = await pipe( [], source, - lp.decode(), + lp.decode({ + maxDataLength: this.init.maxIdentifyMessageSize ?? MAX_IDENTIFY_MESSAGE_SIZE + }), async (source) => await first(source) ) @@ -224,6 +252,10 @@ export class IdentifyService implements Startable { throw errCode(err, codes.ERR_INVALID_MESSAGE) } } finally { + if (timeoutController != null) { + timeoutController.clear() + } + stream.close() } } @@ -404,7 +436,9 @@ export class IdentifyService implements Startable { const data = await pipe( [], source, - lp.decode(), + lp.decode({ + maxDataLength: this.init.maxIdentifyMessageSize ?? MAX_IDENTIFY_MESSAGE_SIZE + }), async (source) => await first(source) ) diff --git a/test/identify/index.spec.ts b/test/identify/index.spec.ts index 7ce5d75ee5..b47f9f0121 100644 --- a/test/identify/index.spec.ts +++ b/test/identify/index.spec.ts @@ -1,4 +1,5 @@ /* eslint-env mocha */ +/* eslint max-nested-callbacks: ["error", 6] */ import { expect } from 'aegir/chai' import sinon from 'sinon' @@ -26,6 +27,8 @@ import { DefaultTransportManager } from '../../src/transport-manager.js' import delay from 'delay' import { start, stop } from '@libp2p/interfaces/startable' import { TimeoutController } from 'timeout-abort-controller' +import { CustomEvent } from '@libp2p/interfaces/events' +import pDefer from 'p-defer' const listenMaddrs = [new Multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] @@ -270,4 +273,105 @@ describe('identify', () => { const { stream } = await newStreamSpy.getCall(0).returnValue expect(stream).to.have.nested.property('timeline.close') }) + + it('should limit incoming identify message sizes', async () => { + const deferred = pDefer() + + const remoteIdentify = new IdentifyService(remoteComponents, { + ...defaultInit, + maxIdentifyMessageSize: 100 + }) + await start(remoteIdentify) + + const identifySpy = sinon.spy(remoteIdentify, 'identify') + + const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) + + // handle incoming identify requests and send too much data + await localComponents.getRegistrar().handle('/ipfs/id/1.0.0', ({ stream }) => { + const data = new Uint8Array(1024) + + void Promise.resolve().then(async () => { + await pipe( + [data], + lp.encode(), + stream, + async (source) => await drain(source) + ) + + deferred.resolve() + }) + }) + + // ensure connections are registered by connection manager + localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { + detail: localToRemote + })) + remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { + detail: remoteToLocal + })) + + await deferred.promise + await stop(remoteIdentify) + + expect(identifySpy.called).to.be.true() + + await expect(identifySpy.getCall(0).returnValue) + .to.eventually.be.rejected.with.property('code', 'ERR_MSG_DATA_TOO_LONG') + }) + + it('should time out incoming identify messages', async () => { + const deferred = pDefer() + + const remoteIdentify = new IdentifyService(remoteComponents, { + ...defaultInit, + timeout: 100 + }) + await start(remoteIdentify) + + const identifySpy = sinon.spy(remoteIdentify, 'identify') + + const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) + + // handle incoming identify requests and don't send anything + await localComponents.getRegistrar().handle('/ipfs/id/1.0.0', ({ stream }) => { + const data = new Uint8Array(1024) + + void Promise.resolve().then(async () => { + await pipe( + [data], + lp.encode(), + async (source) => { + await stream.sink(async function * () { + for await (const buf of source) { + // don't send all of the data, remote will expect another message + yield buf.slice(0, buf.length - 100) + + // wait for longer than the timeout without sending any more data or closing the stream + await delay(500) + } + }()) + } + ) + + deferred.resolve() + }) + }) + + // ensure connections are registered by connection manager + localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { + detail: localToRemote + })) + remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { + detail: remoteToLocal + })) + + await deferred.promise + await stop(remoteIdentify) + + expect(identifySpy.called).to.be.true() + + await expect(identifySpy.getCall(0).returnValue) + .to.eventually.be.rejected.with.property('code', 'ABORT_ERR') + }) }) From 893f8c280f08a545ffa70f18584df8bdb012b22f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 31 May 2022 17:45:54 +0100 Subject: [PATCH 372/447] chore: release 0.37.2 (#1232) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccb3582be2..61a63aaf8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,13 @@ +### [0.37.2](https://www.github.com/libp2p/js-libp2p/compare/v0.37.1...v0.37.2) (2022-05-31) + + +### Bug Fixes + +* reduce identify message size limit ([#1230](https://www.github.com/libp2p/js-libp2p/issues/1230)) ([824720f](https://www.github.com/libp2p/js-libp2p/commit/824720fb8f21f868ed88e881fbc3ce6b9459600d)) + ### [0.37.1](https://www.github.com/libp2p/js-libp2p/compare/v0.37.0...v0.37.1) (2022-05-25) diff --git a/package.json b/package.json index bc170c437b..61b240f1ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.37.1", + "version": "0.37.2", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p#readme", From 3babbbd75a6d14554e52cfd6e74723f0f977f5a4 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Mon, 6 Jun 2022 15:04:00 +0100 Subject: [PATCH 373/447] chore: update ipfs-http-client (#1234) Updates to ESM version of ipfs-http-client --- package.json | 6 +++--- test/peer-routing/peer-routing.node.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 61b240f1ad..effd67f97e 100644 --- a/package.json +++ b/package.json @@ -149,8 +149,8 @@ "@libp2p/bootstrap": "^1.0.4", "@libp2p/daemon-client": "^1.0.2", "@libp2p/daemon-server": "^1.0.2", - "@libp2p/delegated-content-routing": "^1.0.2", - "@libp2p/delegated-peer-routing": "^1.0.2", + "@libp2p/delegated-content-routing": "^1.0.5", + "@libp2p/delegated-peer-routing": "^1.0.5", "@libp2p/floodsub": "^1.0.6", "@libp2p/interface-compliance-tests": "^2.0.3", "@libp2p/interop": "^1.0.3", @@ -172,7 +172,7 @@ "execa": "^6.1.0", "go-libp2p": "^0.0.6", "into-stream": "^7.0.0", - "ipfs-http-client": "^56.0.1", + "ipfs-http-client": "^57.0.1", "it-pushable": "^2.0.1", "it-to-buffer": "^2.0.2", "nock": "^13.0.3", diff --git a/test/peer-routing/peer-routing.node.ts b/test/peer-routing/peer-routing.node.ts index 4472c87893..f98abfa0d9 100644 --- a/test/peer-routing/peer-routing.node.ts +++ b/test/peer-routing/peer-routing.node.ts @@ -360,8 +360,8 @@ describe('peer-routing', () => { .query(true) .reply(200, () => intoStream([ - `{"Extra":"","id":"${closest1}","Responses":[{"ID":"${closest1}","Addrs":["/ip4/127.0.0.1/tcp/63930","/ip4/127.0.0.1/tcp/63930"]}],"Type":1}\n`, - `{"Extra":"","id":"${closest2}","Responses":[{"ID":"${closest2}","Addrs":["/ip4/127.0.0.1/tcp/63506","/ip4/127.0.0.1/tcp/63506"]}],"Type":1}\n`, + `{"Extra":"","ID":"${closest1}","Responses":[{"ID":"${closest1}","Addrs":["/ip4/127.0.0.1/tcp/63930","/ip4/127.0.0.1/tcp/63930"]}],"Type":1}\n`, + `{"Extra":"","ID":"${closest2}","Responses":[{"ID":"${closest2}","Addrs":["/ip4/127.0.0.1/tcp/63506","/ip4/127.0.0.1/tcp/63506"]}],"Type":1}\n`, `{"Extra":"","ID":"${closest2}","Responses":[],"Type":2}\n`, `{"Extra":"","ID":"${closest1}","Responses":[],"Type":2}\n` ]), From eee256db8ab65cea7228b1683403417edfdb1367 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 8 Jun 2022 08:29:32 +0100 Subject: [PATCH 374/447] fix: ensure streams are closed when protocol negotiation fails (#1236) If an error is thrown during the initial stages of setting up a multiplexed stream, ensure we close the stream to free up any resources associated with it. --- src/upgrader.ts | 8 ++++++++ test/upgrading/upgrader.spec.ts | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/upgrader.ts b/src/upgrader.ts index f83365ac5d..23f29619d4 100644 --- a/src/upgrader.ts +++ b/src/upgrader.ts @@ -301,6 +301,10 @@ export class DefaultUpgrader extends EventEmitter implements Upg }) .catch(err => { log.error(err) + + if (muxedStream.timeline.close == null) { + muxedStream.close() + } }) }, // Run anytime a stream closes @@ -330,6 +334,10 @@ export class DefaultUpgrader extends EventEmitter implements Upg } catch (err: any) { log.error('could not create new stream', err) + if (muxedStream.timeline.close == null) { + muxedStream.close() + } + if (err.code != null) { throw err } diff --git a/test/upgrading/upgrader.spec.ts b/test/upgrading/upgrader.spec.ts index 6a6772988c..26e3169959 100644 --- a/test/upgrading/upgrader.spec.ts +++ b/test/upgrading/upgrader.spec.ts @@ -405,6 +405,29 @@ describe('Upgrader', () => { })) .to.eventually.be.rejected.with.property('code', 'ABORT_ERR') }) + + it('should close streams when protocol negotiation fails', async () => { + await remoteComponents.getRegistrar().unhandle('/echo/1.0.0') + + const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) + + const connections = await Promise.all([ + localUpgrader.upgradeOutbound(outbound), + remoteUpgrader.upgradeInbound(inbound) + ]) + + expect(connections[0].streams).to.have.lengthOf(0) + expect(connections[1].streams).to.have.lengthOf(0) + + await expect(connections[0].newStream('/echo/1.0.0')) + .to.eventually.be.rejected.with.property('code', 'ERR_UNSUPPORTED_PROTOCOL') + + // wait for remote to close + await delay(100) + + expect(connections[0].streams).to.have.lengthOf(0) + expect(connections[1].streams).to.have.lengthOf(0) + }) }) describe('libp2p.upgrader', () => { From f9073ecd215e119b7a864e2ad31fe7067322c754 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 8 Jun 2022 16:20:56 +0100 Subject: [PATCH 375/447] fix: connection pruning (#1235) Actually prune connections when we reach the connection limit --- src/connection-manager/index.ts | 82 ++++++++++++++++----------- test/connection-manager/index.spec.ts | 4 +- test/metrics/index.node.ts | 5 +- 3 files changed, 54 insertions(+), 37 deletions(-) diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index 58a9b5eda5..4a78606dc2 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -31,7 +31,7 @@ const defaultOptions: Partial = { pollInterval: 2000, autoDialInterval: 10000, movingAverageInterval: 60000, - defaultPeerValue: 1 + defaultPeerValue: 0.5 } const METRICS_COMPONENT = 'connection-manager' @@ -344,7 +344,10 @@ export class DefaultConnectionManager extends EventEmitter('peer:connect', { detail: connection })) } @@ -459,7 +462,7 @@ export class DefaultConnectionManager extends EventEmitter) { const { detail: summary } = evt - this._checkMaxLimit('maxEventLoopDelay', summary.avgMs) + this._checkMaxLimit('maxEventLoopDelay', summary.avgMs, 1) .catch(err => { log.error(err) }) @@ -468,46 +471,59 @@ export class DefaultConnectionManager extends EventEmitter limit) { - log('%s: limit exceeded: %p, %d', this.components.getPeerId(), name, value) - await this._maybeDisconnectOne() + log('%s: limit exceeded: %p, %d, pruning %d connection(s)', this.components.getPeerId(), name, value, toPrune) + await this._maybePruneConnections(toPrune) } } /** - * If we have more connections than our maximum, close a connection - * to the lowest valued peer. + * If we have more connections than our maximum, select some excess connections + * to prune based on peer value */ - async _maybeDisconnectOne () { - if (this.opts.minConnections < this.connections.size) { - const peerValues = Array.from(new Map([...this.peerValues.entries()].sort((a, b) => a[1] - b[1]))) - - log('%p: sorted peer values: %j', this.components.getPeerId(), peerValues) - const disconnectPeer = peerValues[0] - - if (disconnectPeer != null) { - const peerId = disconnectPeer[0] - log('%p: lowest value peer is %s', this.components.getPeerId(), peerId) - log('%p: closing a connection to %j', this.components.getPeerId(), peerId) - - for (const connections of this.connections.values()) { - if (connections[0].remotePeer.toString() === peerId) { - void connections[0].close() - .catch(err => { - log.error(err) - }) - - // TODO: should not need to invoke this manually - this.onDisconnect(new CustomEvent('connectionEnd', { - detail: connections[0] - })) - break - } + async _maybePruneConnections (toPrune: number) { + const connections = this.getConnections() + + if (connections.length <= this.opts.minConnections || toPrune < 1) { + return + } + + const peerValues = Array.from(new Map([...this.peerValues.entries()].sort((a, b) => a[1] - b[1]))) + log.trace('sorted peer values: %j', peerValues) + + const toClose = [] + + for (const [peerId] of peerValues) { + log('too many connections open - closing a connection to %p', peerId) + + for (const connection of connections) { + if (connection.remotePeer.toString() === peerId) { + toClose.push(connection) + } + + if (toClose.length === toPrune) { + break } } } + + // close connections + await Promise.all( + toClose.map(async connection => { + try { + await connection.close() + } catch (err) { + log.error(err) + } + + // TODO: should not need to invoke this manually + this.onDisconnect(new CustomEvent('connectionEnd', { + detail: connection + })) + }) + ) } } diff --git a/test/connection-manager/index.spec.ts b/test/connection-manager/index.spec.ts index 83a520f7b1..7253bcd197 100644 --- a/test/connection-manager/index.spec.ts +++ b/test/connection-manager/index.spec.ts @@ -66,7 +66,7 @@ describe('Connection Manager', () => { await libp2p.start() const connectionManager = libp2p.components.getConnectionManager() as DefaultConnectionManager - const connectionManagerMaybeDisconnectOneSpy = sinon.spy(connectionManager, '_maybeDisconnectOne') + const connectionManagerMaybeDisconnectOneSpy = sinon.spy(connectionManager, '_maybePruneConnections') // Add 1 too many connections const spies = new Map>>() @@ -115,7 +115,7 @@ describe('Connection Manager', () => { await libp2p.start() const connectionManager = libp2p.components.getConnectionManager() as DefaultConnectionManager - const connectionManagerMaybeDisconnectOneSpy = sinon.spy(connectionManager, '_maybeDisconnectOne') + const connectionManagerMaybeDisconnectOneSpy = sinon.spy(connectionManager, '_maybePruneConnections') // Add 1 too many connections const spy = sinon.spy() diff --git a/test/metrics/index.node.ts b/test/metrics/index.node.ts index 83f5783c8f..8a6e7a58f2 100644 --- a/test/metrics/index.node.ts +++ b/test/metrics/index.node.ts @@ -11,6 +11,7 @@ import { createBaseOptions } from '../utils/base-options.js' import type { Libp2pNode } from '../../src/libp2p.js' import type { Libp2pOptions } from '../../src/index.js' import type { DefaultMetrics } from '../../src/metrics/index.js' +import drain from 'it-drain' describe('libp2p.metrics', () => { let libp2p: Libp2pNode @@ -149,7 +150,7 @@ describe('libp2p.metrics', () => { ]) await populateAddressBooks([libp2p, remoteLibp2p]) - void remoteLibp2p.handle('/echo/1.0.0', ({ stream }) => { + await remoteLibp2p.handle('/echo/1.0.0', ({ stream }) => { void pipe(stream, stream) }) @@ -160,7 +161,7 @@ describe('libp2p.metrics', () => { await pipe( [bytes], stream, - async (source) => await toBuffer(source) + drain ) const metrics = libp2p.components.getMetrics() From b0472686d29a4f295360d3f15a50c86c981892f7 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 8 Jun 2022 16:47:41 +0100 Subject: [PATCH 376/447] fix: wait for peer stats to be updated during test (#1238) The peer stats update interval doesn't always align with the timing in the test so make sure it's elapsed before asserting on the results. Fixes #1219 --- test/metrics/index.node.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/metrics/index.node.ts b/test/metrics/index.node.ts index 8a6e7a58f2..1be7558d2f 100644 --- a/test/metrics/index.node.ts +++ b/test/metrics/index.node.ts @@ -11,6 +11,7 @@ import { createBaseOptions } from '../utils/base-options.js' import type { Libp2pNode } from '../../src/libp2p.js' import type { Libp2pOptions } from '../../src/index.js' import type { DefaultMetrics } from '../../src/metrics/index.js' +import pWaitFor from 'p-wait-for' import drain from 'it-drain' describe('libp2p.metrics', () => { @@ -170,6 +171,19 @@ describe('libp2p.metrics', () => { throw new Error('Metrics not configured') } + await pWaitFor(() => { + const peerStats = metrics.forPeer(connection.remotePeer)?.getSnapshot() + const transferred = parseInt(peerStats?.dataReceived.toString() ?? '0') + + if (transferred < bytes.length) { + return false + } + + return true + }, { + interval: 100 + }) + const peerStats = metrics.forPeer(connection.remotePeer)?.getSnapshot() expect(parseInt(peerStats?.dataReceived.toString() ?? '0')).to.be.at.least(bytes.length) From 13d95b413cc85092752a6dd3dbe998c6f935e2e6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 10 Jun 2022 07:49:49 +0100 Subject: [PATCH 377/447] chore: release 0.37.3 (#1237) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61a63aaf8f..deb10ccb77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,15 @@ +### [0.37.3](https://www.github.com/libp2p/js-libp2p/compare/v0.37.2...v0.37.3) (2022-06-08) + + +### Bug Fixes + +* connection pruning ([#1235](https://www.github.com/libp2p/js-libp2p/issues/1235)) ([f9073ec](https://www.github.com/libp2p/js-libp2p/commit/f9073ecd215e119b7a864e2ad31fe7067322c754)) +* ensure streams are closed when protocol negotiation fails ([#1236](https://www.github.com/libp2p/js-libp2p/issues/1236)) ([eee256d](https://www.github.com/libp2p/js-libp2p/commit/eee256db8ab65cea7228b1683403417edfdb1367)) +* wait for peer stats to be updated during test ([#1238](https://www.github.com/libp2p/js-libp2p/issues/1238)) ([b047268](https://www.github.com/libp2p/js-libp2p/commit/b0472686d29a4f295360d3f15a50c86c981892f7)), closes [#1219](https://www.github.com/libp2p/js-libp2p/issues/1219) + ### [0.37.2](https://www.github.com/libp2p/js-libp2p/compare/v0.37.1...v0.37.2) (2022-05-31) diff --git a/package.json b/package.json index effd67f97e..01c5d08d77 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.37.2", + "version": "0.37.3", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p#readme", From d4dd664071476e3d22f53e02e7d66099f3265f6c Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 15 Jun 2022 18:30:39 +0100 Subject: [PATCH 378/447] feat!: update libp2p interfaces (#1252) BREAKING CHANGE: uses new single-issue libp2p interface modules --- doc/CONFIGURATION.md | 2 +- doc/migrations/v0.36-v.037.md | 2 +- examples/delegated-routing/package.json | 12 +- examples/libp2p-in-the-browser/package.json | 8 +- examples/package.json | 4 +- examples/webrtc-direct/package.json | 6 +- package.json | 74 ++++++---- src/address-manager/index.ts | 4 +- src/circuit/auto-relay.ts | 8 +- src/circuit/circuit/hop.ts | 4 +- src/circuit/circuit/stop.ts | 2 +- src/circuit/circuit/stream-handler.ts | 2 +- src/circuit/index.ts | 4 +- src/circuit/listener.ts | 6 +- src/circuit/transport.ts | 10 +- src/connection-manager/auto-dialler.ts | 2 +- src/connection-manager/dialer/auto-dialer.ts | 4 +- src/connection-manager/dialer/dial-request.ts | 2 +- src/connection-manager/dialer/index.ts | 10 +- src/connection-manager/index.ts | 12 +- src/content-routing/index.ts | 4 +- src/content-routing/utils.ts | 4 +- src/dht/dht-content-routing.ts | 4 +- src/dht/dht-peer-routing.ts | 6 +- src/dht/dummy-dht.ts | 6 +- src/fetch/index.ts | 8 +- src/get-peer.ts | 6 +- src/identify/index.ts | 36 ++--- src/index.ts | 30 ++--- src/insecure/index.ts | 6 +- src/keychain/index.ts | 4 +- src/libp2p.ts | 44 ++++-- src/metrics/index.ts | 4 +- src/metrics/moving-average.ts | 2 +- src/metrics/stats.ts | 2 +- src/nat-manager.ts | 2 +- src/peer-record-updater.ts | 2 +- src/peer-routing.ts | 8 +- src/ping/index.ts | 6 +- src/pnet/index.ts | 3 +- src/pubsub/dummy-pubsub.ts | 4 +- src/registrar.ts | 127 ++++++++++-------- src/transport-manager.ts | 6 +- src/upgrader.ts | 20 +-- test/addresses/address-manager.spec.ts | 6 +- test/configuration/pubsub.spec.ts | 2 +- test/configuration/utils.ts | 4 +- test/connection-manager/auto-dialler.spec.ts | 6 +- test/connection-manager/index.node.ts | 12 +- test/connection-manager/index.spec.ts | 2 +- test/content-routing/content-routing.node.ts | 2 +- test/content-routing/dht/operation.node.ts | 2 +- test/core/encryption.spec.ts | 2 +- test/core/listening.node.ts | 2 +- test/dialing/dial-request.spec.ts | 2 +- test/dialing/direct.node.ts | 10 +- test/dialing/direct.spec.ts | 10 +- test/dialing/resolver.spec.ts | 4 +- test/fetch/fetch.node.ts | 2 +- test/fetch/index.spec.ts | 4 +- test/identify/index.spec.ts | 4 +- test/identify/push.spec.ts | 4 +- test/identify/service.spec.ts | 2 +- test/insecure/compliance.spec.ts | 2 +- test/insecure/plaintext.spec.ts | 8 +- test/interop.ts | 2 +- test/keychain/cms-interop.spec.ts | 2 +- test/keychain/keychain.spec.ts | 4 +- test/keychain/peerid.spec.ts | 2 +- test/metrics/index.spec.ts | 4 +- test/nat-manager/nat-manager.node.ts | 4 +- test/peer-discovery/index.node.ts | 4 +- test/peer-discovery/index.spec.ts | 4 +- test/peer-routing/peer-routing.node.ts | 6 +- test/ping/index.spec.ts | 4 +- test/pnet/index.spec.ts | 2 +- test/registrar/registrar.spec.ts | 18 +-- test/transports/transport-manager.node.ts | 6 +- test/transports/transport-manager.spec.ts | 6 +- test/upgrading/upgrader.spec.ts | 16 +-- test/utils/creators/peer.ts | 2 +- 81 files changed, 370 insertions(+), 330 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 89d4acc83e..fd38e2711a 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -323,7 +323,7 @@ import { TCP } from '@libp2p/tcp' import { Mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' import { GossipSub } from 'libp2p-gossipsub' -import { SignaturePolicy } from '@libp2p/interfaces/pubsub' +import { SignaturePolicy } from '@libp2p/interface-pubsub' const node = await createLibp2p({ transports: [ diff --git a/doc/migrations/v0.36-v.037.md b/doc/migrations/v0.36-v.037.md index d4482feee3..eeacd49644 100644 --- a/doc/migrations/v0.36-v.037.md +++ b/doc/migrations/v0.36-v.037.md @@ -240,7 +240,7 @@ libp2p.pubsub.off('my-topic', handler) **After** ```js -import type { Message } from '@libp2p/interfaces/pubsub' +import type { Message } from '@libp2p/interface-pubsub' const handler = (event: CustomEvent) => { const message = event.detail diff --git a/examples/delegated-routing/package.json b/examples/delegated-routing/package.json index 5a3bedb64e..399cc0afbb 100644 --- a/examples/delegated-routing/package.json +++ b/examples/delegated-routing/package.json @@ -6,12 +6,12 @@ "@chainsafe/libp2p-noise": "^6.2.0", "ipfs-core": "^0.14.1", "libp2p": "../../", - "@libp2p/delegated-content-routing": "^1.0.1", - "@libp2p/delegated-peer-routing": "^1.0.1", - "@libp2p/kad-dht": "^1.0.9", - "@libp2p/mplex": "^1.0.4", - "@libp2p/webrtc-star": "^1.0.8", - "@libp2p/websockets": "^1.0.7", + "@libp2p/delegated-content-routing": "^2.0.1", + "@libp2p/delegated-peer-routing": "^2.0.1", + "@libp2p/kad-dht": "^2.0.0", + "@libp2p/mplex": "^2.0.0", + "@libp2p/webrtc-star": "^2.0.0", + "@libp2p/websockets": "^2.0.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "5.0.0" diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json index 4a6c6df561..6dbc839662 100644 --- a/examples/libp2p-in-the-browser/package.json +++ b/examples/libp2p-in-the-browser/package.json @@ -10,10 +10,10 @@ "license": "ISC", "dependencies": { "@chainsafe/libp2p-noise": "^6.2.0", - "@libp2p/bootstrap": "^1.0.4", - "@libp2p/mplex": "^1.0.4", - "@libp2p/webrtc-star": "^1.0.8", - "@libp2p/websockets": "^1.0.7", + "@libp2p/bootstrap": "^2.0.0", + "@libp2p/mplex": "^2.0.0", + "@libp2p/webrtc-star": "^2.0.0", + "@libp2p/websockets": "^2.0.0", "libp2p": "../../" }, "devDependencies": { diff --git a/examples/package.json b/examples/package.json index 848c9d47fc..70984696c5 100644 --- a/examples/package.json +++ b/examples/package.json @@ -9,8 +9,8 @@ }, "license": "MIT", "dependencies": { - "@libp2p/pubsub-peer-discovery": "^5.0.2", - "@libp2p/floodsub": "^1.0.6", + "@libp2p/pubsub-peer-discovery": "^6.0.0", + "@libp2p/floodsub": "^2.0.0", "@nodeutils/defaults-deep": "^1.1.0", "execa": "^2.1.0", "fs-extra": "^8.1.0", diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index 6d64d0813d..c1d34ffee2 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -9,10 +9,10 @@ }, "license": "ISC", "dependencies": { - "@libp2p/webrtc-direct": "^1.0.1", + "@libp2p/webrtc-direct": "^2.0.0", "@chainsafe/libp2p-noise": "^6.2.0", - "@libp2p/bootstrap": "^1.0.4", - "@libp2p/mplex": "^1.0.4", + "@libp2p/bootstrap": "^2.0.0", + "@libp2p/mplex": "^2.0.0", "libp2p": "../../", "wrtc": "^0.4.7" }, diff --git a/package.json b/package.json index 01c5d08d77..997908acdc 100644 --- a/package.json +++ b/package.json @@ -49,15 +49,19 @@ ], "exports": { ".": { + "types": "./src/index.d.ts", "import": "./dist/src/index.js" }, "./insecure": { + "types": "./dist/src/insecure/index.d.ts", "import": "./dist/src/insecure/index.js" }, "./pnet": { + "types": "./dist/src/pnet/index.d.ts", "import": "./dist/src/pnet/index.js" }, "./transport-manager": { + "types": "./dist/src/transport-manager.d.ts", "import": "./dist/src/transport-manager.js" } }, @@ -93,18 +97,34 @@ }, "dependencies": { "@achingbrain/nat-port-mapper": "^1.0.3", - "@libp2p/connection": "^2.0.2", - "@libp2p/crypto": "^0.22.11", - "@libp2p/interfaces": "^2.0.2", - "@libp2p/logger": "^1.1.4", - "@libp2p/multistream-select": "^1.0.4", + "@libp2p/components": "^1.0.0", + "@libp2p/connection": "^3.0.0", + "@libp2p/crypto": "^1.0.0", + "@libp2p/interface-address-manager": "^1.0.1", + "@libp2p/interface-connection": "^1.0.1", + "@libp2p/interface-connection-encrypter": "^1.0.2", + "@libp2p/interface-content-routing": "^1.0.1", + "@libp2p/interface-dht": "^1.0.0", + "@libp2p/interface-metrics": "^1.0.2", + "@libp2p/interface-peer-discovery": "^1.0.0", + "@libp2p/interface-peer-id": "^1.0.2", + "@libp2p/interface-peer-info": "^1.0.1", + "@libp2p/interface-peer-routing": "^1.0.0", + "@libp2p/interface-peer-store": "^1.0.0", + "@libp2p/interface-pubsub": "^1.0.1", + "@libp2p/interface-registrar": "^1.0.0", + "@libp2p/interface-stream-muxer": "^1.0.1", + "@libp2p/interface-transport": "^1.0.0", + "@libp2p/interfaces": "^3.0.2", + "@libp2p/logger": "^2.0.0", + "@libp2p/multistream-select": "^2.0.0", "@libp2p/peer-collections": "^1.0.2", "@libp2p/peer-id": "^1.1.10", "@libp2p/peer-id-factory": "^1.0.9", - "@libp2p/peer-record": "^1.0.8", - "@libp2p/peer-store": "^1.0.10", + "@libp2p/peer-record": "^2.0.0", + "@libp2p/peer-store": "^2.0.0", "@libp2p/tracked-map": "^1.0.5", - "@libp2p/utils": "^1.0.10", + "@libp2p/utils": "^2.0.0", "@multiformats/mafmt": "^11.0.2", "@multiformats/multiaddr": "^10.1.8", "abortable-iterator": "^4.0.2", @@ -119,7 +139,7 @@ "it-filter": "^1.0.3", "it-first": "^1.0.6", "it-foreach": "^0.1.1", - "it-handshake": "^3.0.1", + "it-handshake": "^4.0.0", "it-length-prefixed": "^7.0.1", "it-map": "^1.0.6", "it-merge": "^1.0.3", @@ -146,22 +166,24 @@ }, "devDependencies": { "@chainsafe/libp2p-noise": "^6.2.0", - "@libp2p/bootstrap": "^1.0.4", - "@libp2p/daemon-client": "^1.0.2", - "@libp2p/daemon-server": "^1.0.2", - "@libp2p/delegated-content-routing": "^1.0.5", - "@libp2p/delegated-peer-routing": "^1.0.5", - "@libp2p/floodsub": "^1.0.6", - "@libp2p/interface-compliance-tests": "^2.0.3", - "@libp2p/interop": "^1.0.3", - "@libp2p/kad-dht": "^1.0.9", - "@libp2p/mdns": "^1.0.5", - "@libp2p/mplex": "^1.1.0", - "@libp2p/pubsub": "^1.2.18", - "@libp2p/tcp": "^1.0.9", - "@libp2p/topology": "^1.1.7", - "@libp2p/webrtc-star": "^1.0.8", - "@libp2p/websockets": "^1.0.7", + "@libp2p/bootstrap": "^2.0.0", + "@libp2p/daemon-client": "^2.0.0", + "@libp2p/daemon-server": "^2.0.0", + "@libp2p/delegated-content-routing": "^2.0.0", + "@libp2p/delegated-peer-routing": "^2.0.0", + "@libp2p/floodsub": "^2.0.0", + "@libp2p/interface-compliance-tests": "^3.0.1", + "@libp2p/interface-connection-encrypter-compliance-tests": "^1.0.0", + "@libp2p/interface-mocks": "^1.0.1", + "@libp2p/interop": "^2.0.0", + "@libp2p/kad-dht": "^2.0.0", + "@libp2p/mdns": "^2.0.0", + "@libp2p/mplex": "^2.0.0", + "@libp2p/pubsub": "^2.0.0", + "@libp2p/tcp": "^2.0.0", + "@libp2p/topology": "^2.0.0", + "@libp2p/webrtc-star": "^2.0.0", + "@libp2p/websockets": "^2.0.0", "@types/node-forge": "^1.0.0", "@types/p-fifo": "^1.0.0", "@types/varint": "^6.0.0", @@ -173,7 +195,7 @@ "go-libp2p": "^0.0.6", "into-stream": "^7.0.0", "ipfs-http-client": "^57.0.1", - "it-pushable": "^2.0.1", + "it-pushable": "^3.0.0", "it-to-buffer": "^2.0.2", "nock": "^13.0.3", "npm-run-all": "^4.1.5", diff --git a/src/address-manager/index.ts b/src/address-manager/index.ts index 00634a217d..e011bde4f7 100644 --- a/src/address-manager/index.ts +++ b/src/address-manager/index.ts @@ -1,8 +1,8 @@ -import type { AddressManagerEvents } from '@libp2p/interfaces/address-manager' +import type { AddressManagerEvents } from '@libp2p/interface-address-manager' import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events' import { Multiaddr } from '@multiformats/multiaddr' import { peerIdFromString } from '@libp2p/peer-id' -import type { Components } from '@libp2p/interfaces/components' +import type { Components } from '@libp2p/components' export interface AddressManagerInit { announceFilter?: AddressFilter diff --git a/src/circuit/auto-relay.ts b/src/circuit/auto-relay.ts index 04b6fa3c64..a6218a0334 100644 --- a/src/circuit/auto-relay.ts +++ b/src/circuit/auto-relay.ts @@ -10,10 +10,10 @@ import { HOP_METADATA_VALUE, RELAY_RENDEZVOUS_NS } from './constants.js' -import type { PeerId } from '@libp2p/interfaces/peer-id' -import type { AddressSorter, PeerProtocolsChangeData } from '@libp2p/interfaces/peer-store' -import type { Connection } from '@libp2p/interfaces/connection' -import type { Components } from '@libp2p/interfaces/components' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { AddressSorter, PeerProtocolsChangeData } from '@libp2p/interface-peer-store' +import type { Connection } from '@libp2p/interface-connection' +import type { Components } from '@libp2p/components' import sort from 'it-sort' import all from 'it-all' import { pipe } from 'it-pipe' diff --git a/src/circuit/circuit/hop.ts b/src/circuit/circuit/hop.ts index 02ed844b26..4f5474d150 100644 --- a/src/circuit/circuit/hop.ts +++ b/src/circuit/circuit/hop.ts @@ -7,11 +7,11 @@ import { pipe } from 'it-pipe' import { codes as Errors } from '../../errors.js' import { stop } from './stop.js' import { RELAY_CODEC } from '../multicodec.js' -import type { Connection } from '@libp2p/interfaces/connection' +import type { Connection } from '@libp2p/interface-connection' import { peerIdFromBytes } from '@libp2p/peer-id' import type { Duplex } from 'it-stream-types' import type { Circuit } from '../transport.js' -import type { ConnectionManager } from '@libp2p/interfaces/connection-manager' +import type { ConnectionManager } from '@libp2p/interface-connection-manager' const log = logger('libp2p:circuit:hop') diff --git a/src/circuit/circuit/stop.ts b/src/circuit/circuit/stop.ts index dab6aab893..c953ce56bb 100644 --- a/src/circuit/circuit/stop.ts +++ b/src/circuit/circuit/stop.ts @@ -3,7 +3,7 @@ import { CircuitRelay as CircuitPB } from '../pb/index.js' import { RELAY_CODEC } from '../multicodec.js' import { StreamHandler } from './stream-handler.js' import { validateAddrs } from './utils.js' -import type { Connection } from '@libp2p/interfaces/connection' +import type { Connection } from '@libp2p/interface-connection' import type { Duplex } from 'it-stream-types' const log = logger('libp2p:circuit:stop') diff --git a/src/circuit/circuit/stream-handler.ts b/src/circuit/circuit/stream-handler.ts index 3d53bb4d86..5dcb36ebf3 100644 --- a/src/circuit/circuit/stream-handler.ts +++ b/src/circuit/circuit/stream-handler.ts @@ -2,7 +2,7 @@ import { logger } from '@libp2p/logger' import * as lp from 'it-length-prefixed' import { Handshake, handshake } from 'it-handshake' import { CircuitRelay } from '../pb/index.js' -import type { Stream } from '@libp2p/interfaces/connection' +import type { Stream } from '@libp2p/interface-connection' import type { Source } from 'it-stream-types' const log = logger('libp2p:circuit:stream-handler') diff --git a/src/circuit/index.ts b/src/circuit/index.ts index 0771a398e2..b8219b57e8 100644 --- a/src/circuit/index.ts +++ b/src/circuit/index.ts @@ -10,9 +10,9 @@ import { namespaceToCid } from './utils.js' import { RELAY_RENDEZVOUS_NS } from './constants.js' -import type { AddressSorter } from '@libp2p/interfaces/peer-store' +import type { AddressSorter } from '@libp2p/interface-peer-store' import type { Startable } from '@libp2p/interfaces/startable' -import type { Components } from '@libp2p/interfaces/components' +import type { Components } from '@libp2p/components' const log = logger('libp2p:relay') diff --git a/src/circuit/listener.ts b/src/circuit/listener.ts index e70ef863d1..0e96cc1f98 100644 --- a/src/circuit/listener.ts +++ b/src/circuit/listener.ts @@ -1,7 +1,7 @@ import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events' -import type { ConnectionManager } from '@libp2p/interfaces/connection-manager' -import type { PeerStore } from '@libp2p/interfaces/peer-store' -import type { Listener } from '@libp2p/interfaces/transport' +import type { ConnectionManager } from '@libp2p/interface-connection-manager' +import type { PeerStore } from '@libp2p/interface-peer-store' +import type { Listener } from '@libp2p/interface-transport' import { peerIdFromString } from '@libp2p/peer-id' import { Multiaddr } from '@multiformats/multiaddr' diff --git a/src/circuit/transport.ts b/src/circuit/transport.ts index 1ff71d4ed7..a4d4894cc3 100644 --- a/src/circuit/transport.ts +++ b/src/circuit/transport.ts @@ -10,13 +10,13 @@ import { createListener } from './listener.js' import { handleCanHop, handleHop, hop } from './circuit/hop.js' import { handleStop } from './circuit/stop.js' import { StreamHandler } from './circuit/stream-handler.js' -import { symbol } from '@libp2p/interfaces/transport' +import { symbol } from '@libp2p/interface-transport' import { peerIdFromString } from '@libp2p/peer-id' -import { Components, Initializable } from '@libp2p/interfaces/components' +import { Components, Initializable } from '@libp2p/components' import type { AbortOptions } from '@libp2p/interfaces' -import type { IncomingStreamData } from '@libp2p/interfaces/registrar' -import type { Listener, Transport, CreateListenerOptions, ConnectionHandler } from '@libp2p/interfaces/transport' -import type { Connection } from '@libp2p/interfaces/connection' +import type { IncomingStreamData } from '@libp2p/interface-registrar' +import type { Listener, Transport, CreateListenerOptions, ConnectionHandler } from '@libp2p/interface-transport' +import type { Connection } from '@libp2p/interface-connection' const log = logger('libp2p:circuit') diff --git a/src/connection-manager/auto-dialler.ts b/src/connection-manager/auto-dialler.ts index 8a4e646e37..cb6717ea87 100644 --- a/src/connection-manager/auto-dialler.ts +++ b/src/connection-manager/auto-dialler.ts @@ -7,7 +7,7 @@ import { pipe } from 'it-pipe' import filter from 'it-filter' import sort from 'it-sort' import type { Startable } from '@libp2p/interfaces/startable' -import type { Components } from '@libp2p/interfaces/components' +import type { Components } from '@libp2p/components' const log = logger('libp2p:connection-manager:auto-dialler') diff --git a/src/connection-manager/dialer/auto-dialer.ts b/src/connection-manager/dialer/auto-dialer.ts index bb790a243f..ab63ec2888 100644 --- a/src/connection-manager/dialer/auto-dialer.ts +++ b/src/connection-manager/dialer/auto-dialer.ts @@ -1,6 +1,6 @@ -import type { PeerInfo } from '@libp2p/interfaces/peer-info' +import type { PeerInfo } from '@libp2p/interface-peer-info' import { logger } from '@libp2p/logger' -import type { Components } from '@libp2p/interfaces/components' +import type { Components } from '@libp2p/components' import { TimeoutController } from 'timeout-abort-controller' const log = logger('libp2p:dialer:auto-dialer') diff --git a/src/connection-manager/dialer/dial-request.ts b/src/connection-manager/dialer/dial-request.ts index bb26f474d5..9004bb4d23 100644 --- a/src/connection-manager/dialer/dial-request.ts +++ b/src/connection-manager/dialer/dial-request.ts @@ -5,7 +5,7 @@ import { setMaxListeners } from 'events' import { codes } from '../../errors.js' import { logger } from '@libp2p/logger' import type { Multiaddr } from '@multiformats/multiaddr' -import type { Connection } from '@libp2p/interfaces/connection' +import type { Connection } from '@libp2p/interface-connection' import type { AbortOptions } from '@libp2p/interfaces' import type { Dialer } from './index.js' diff --git a/src/connection-manager/dialer/index.ts b/src/connection-manager/dialer/index.ts index a7abe5165a..30cfff51f8 100644 --- a/src/connection-manager/dialer/index.ts +++ b/src/connection-manager/dialer/index.ts @@ -18,16 +18,16 @@ import { MAX_PER_PEER_DIALS, MAX_ADDRS_TO_DIAL } from '../../constants.js' -import type { Connection } from '@libp2p/interfaces/connection' +import type { Connection } from '@libp2p/interface-connection' import type { AbortOptions } from '@libp2p/interfaces' import type { Startable } from '@libp2p/interfaces/startable' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' import { getPeer } from '../../get-peer.js' import sort from 'it-sort' -import { Components, Initializable } from '@libp2p/interfaces/components' +import { Components, Initializable } from '@libp2p/components' import map from 'it-map' -import type { AddressSorter } from '@libp2p/interfaces/peer-store' -import type { ComponentMetricsTracker } from '@libp2p/interfaces/metrics' +import type { AddressSorter } from '@libp2p/interface-peer-store' +import type { ComponentMetricsTracker } from '@libp2p/interface-metrics' const log = logger('libp2p:dialer') diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index 4a78606dc2..1ec2e00b4a 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -9,14 +9,14 @@ import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events' import type { Startable } from '@libp2p/interfaces/startable' import { trackedMap } from '@libp2p/tracked-map' import { codes } from '../errors.js' -import { isPeerId, PeerId } from '@libp2p/interfaces/peer-id' +import { isPeerId, PeerId } from '@libp2p/interface-peer-id' import { setMaxListeners } from 'events' -import type { Connection } from '@libp2p/interfaces/connection' -import type { ConnectionManager } from '@libp2p/interfaces/connection-manager' -import { Components, Initializable } from '@libp2p/interfaces/components' -import * as STATUS from '@libp2p/interfaces/connection/status' +import type { Connection } from '@libp2p/interface-connection' +import type { ConnectionManager } from '@libp2p/interface-connection-manager' +import { Components, Initializable } from '@libp2p/components' +import * as STATUS from '@libp2p/interface-connection/status' import { Dialer } from './dialer/index.js' -import type { AddressSorter } from '@libp2p/interfaces/peer-store' +import type { AddressSorter } from '@libp2p/interface-peer-store' import type { Resolver } from '@multiformats/multiaddr' const log = logger('libp2p:connection-manager') diff --git a/src/content-routing/index.ts b/src/content-routing/index.ts index b84069847d..5bf28681e6 100644 --- a/src/content-routing/index.ts +++ b/src/content-routing/index.ts @@ -8,11 +8,11 @@ import { import drain from 'it-drain' import merge from 'it-merge' import { pipe } from 'it-pipe' -import type { ContentRouting } from '@libp2p/interfaces/content-routing' +import type { ContentRouting } from '@libp2p/interface-content-routing' import type { AbortOptions } from '@libp2p/interfaces' import type { Startable } from '@libp2p/interfaces/startable' import type { CID } from 'multiformats/cid' -import type { Components } from '@libp2p/interfaces/components' +import type { Components } from '@libp2p/components' export interface CompoundContentRoutingInit { routers: ContentRouting[] diff --git a/src/content-routing/utils.ts b/src/content-routing/utils.ts index a4ca4d15ee..bc6fa84b92 100644 --- a/src/content-routing/utils.ts +++ b/src/content-routing/utils.ts @@ -2,8 +2,8 @@ import errCode from 'err-code' import filter from 'it-filter' import map from 'it-map' import type { Source } from 'it-stream-types' -import type { PeerInfo } from '@libp2p/interfaces/peer-info' -import type { PeerStore } from '@libp2p/interfaces/peer-store' +import type { PeerInfo } from '@libp2p/interface-peer-info' +import type { PeerStore } from '@libp2p/interface-peer-store' /** * Store the multiaddrs from every peer in the passed peer store diff --git a/src/dht/dht-content-routing.ts b/src/dht/dht-content-routing.ts index 12552d47dd..f520db4546 100644 --- a/src/dht/dht-content-routing.ts +++ b/src/dht/dht-content-routing.ts @@ -1,7 +1,7 @@ import drain from 'it-drain' import errCode from 'err-code' -import type { DHT } from '@libp2p/interfaces/dht' -import type { ContentRouting } from '@libp2p/interfaces/content-routing' +import type { DHT } from '@libp2p/interface-dht' +import type { ContentRouting } from '@libp2p/interface-content-routing' import type { CID } from 'multiformats/cid' import type { AbortOptions } from '@libp2p/interfaces' diff --git a/src/dht/dht-peer-routing.ts b/src/dht/dht-peer-routing.ts index 45950cde39..86b6eb574e 100644 --- a/src/dht/dht-peer-routing.ts +++ b/src/dht/dht-peer-routing.ts @@ -1,8 +1,8 @@ import errCode from 'err-code' import { messages, codes } from '../errors.js' -import type { PeerRouting } from '@libp2p/interfaces/peer-routing' -import type { DHT } from '@libp2p/interfaces/dht' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerRouting } from '@libp2p/interface-peer-routing' +import type { DHT } from '@libp2p/interface-dht' +import type { PeerId } from '@libp2p/interface-peer-id' import type { AbortOptions } from '@libp2p/interfaces' /** diff --git a/src/dht/dummy-dht.ts b/src/dht/dummy-dht.ts index 6139f05071..3f37be345e 100644 --- a/src/dht/dummy-dht.ts +++ b/src/dht/dummy-dht.ts @@ -1,9 +1,9 @@ -import type { DualDHT, QueryEvent, SingleDHT } from '@libp2p/interfaces/dht' -import type { PeerDiscoveryEvents } from '@libp2p/interfaces/peer-discovery' +import type { DualDHT, QueryEvent, SingleDHT } from '@libp2p/interface-dht' +import type { PeerDiscoveryEvents } from '@libp2p/interface-peer-discovery' import errCode from 'err-code' import { messages, codes } from '../errors.js' import { EventEmitter } from '@libp2p/interfaces/events' -import { symbol } from '@libp2p/interfaces/peer-discovery' +import { symbol } from '@libp2p/interface-peer-discovery' export class DummyDHT extends EventEmitter implements DualDHT { get [symbol] (): true { diff --git a/src/fetch/index.ts b/src/fetch/index.ts index 59874cb0d5..218b182fe5 100644 --- a/src/fetch/index.ts +++ b/src/fetch/index.ts @@ -5,11 +5,11 @@ import * as lp from 'it-length-prefixed' import { FetchRequest, FetchResponse } from './pb/proto.js' import { handshake } from 'it-handshake' import { PROTOCOL_NAME, PROTOCOL_VERSION } from './constants.js' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' import type { Startable } from '@libp2p/interfaces/startable' -import type { Stream } from '@libp2p/interfaces/connection' -import type { IncomingStreamData } from '@libp2p/interfaces/registrar' -import type { Components } from '@libp2p/interfaces/components' +import type { Stream } from '@libp2p/interface-connection' +import type { IncomingStreamData } from '@libp2p/interface-registrar' +import type { Components } from '@libp2p/components' import type { AbortOptions } from '@libp2p/interfaces' import type { Duplex } from 'it-stream-types' import { abortableDuplex } from 'abortable-iterator' diff --git a/src/get-peer.ts b/src/get-peer.ts index bc4129d176..8406fda45d 100644 --- a/src/get-peer.ts +++ b/src/get-peer.ts @@ -2,9 +2,9 @@ import { peerIdFromString } from '@libp2p/peer-id' import { Multiaddr } from '@multiformats/multiaddr' import errCode from 'err-code' import { codes } from './errors.js' -import { isPeerId } from '@libp2p/interfaces/peer-id' -import type { PeerId } from '@libp2p/interfaces/peer-id' -import type { PeerInfo } from '@libp2p/interfaces/peer-info' +import { isPeerId } from '@libp2p/interface-peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { PeerInfo } from '@libp2p/interface-peer-info' function peerIdFromMultiaddr (ma: Multiaddr) { const idStr = ma.getPeerId() diff --git a/src/identify/index.ts b/src/identify/index.ts index 777c35c1aa..b3fe0e9e33 100644 --- a/src/identify/index.ts +++ b/src/identify/index.ts @@ -18,11 +18,11 @@ import { MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION } from './consts.js' import { codes } from '../errors.js' -import type { IncomingStreamData } from '@libp2p/interfaces/registrar' -import type { Connection, Stream } from '@libp2p/interfaces/connection' +import type { IncomingStreamData } from '@libp2p/interface-registrar' +import type { Connection, Stream } from '@libp2p/interface-connection' import type { Startable } from '@libp2p/interfaces/startable' import { peerIdFromKeys } from '@libp2p/peer-id' -import type { Components } from '@libp2p/interfaces/components' +import type { Components } from '@libp2p/components' import { TimeoutController } from 'timeout-abort-controller' import type { AbortOptions } from '@libp2p/interfaces' import { abortableDuplex } from 'abortable-iterator' @@ -79,8 +79,6 @@ export class IdentifyService implements Startable { this.started = false this.init = init - this.handleMessage = this.handleMessage.bind(this) - this.identifyProtocolStr = `/${init.protocolPrefix}/${MULTICODEC_IDENTIFY_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PROTOCOL_VERSION}` this.identifyPushProtocolStr = `/${init.protocolPrefix}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION}` @@ -127,11 +125,13 @@ export class IdentifyService implements Startable { await this.components.getPeerStore().metadataBook.setValue(this.components.getPeerId(), 'AgentVersion', uint8ArrayFromString(this.host.agentVersion)) await this.components.getPeerStore().metadataBook.setValue(this.components.getPeerId(), 'ProtocolVersion', uint8ArrayFromString(this.host.protocolVersion)) - await this.components.getRegistrar().handle([ - this.identifyProtocolStr, - this.identifyPushProtocolStr - ], (data) => { - void this.handleMessage(data)?.catch(err => { + await this.components.getRegistrar().handle(this.identifyProtocolStr, (data) => { + void this._handleIdentify(data).catch(err => { + log.error(err) + }) + }) + await this.components.getRegistrar().handle(this.identifyPushProtocolStr, (data) => { + void this._handlePush(data).catch(err => { log.error(err) }) }) @@ -353,22 +353,6 @@ export class IdentifyService implements Startable { // this.components.getAddressManager().addObservedAddr(observedAddr) } - /** - * A handler to register with Libp2p to process identify messages - */ - handleMessage (data: IncomingStreamData) { - const { protocol } = data - - switch (protocol) { - case this.identifyProtocolStr: - return this._handleIdentify(data) - case this.identifyPushProtocolStr: - return this._handlePush(data) - default: - log.error('cannot handle unknown protocol %s', protocol) - } - } - /** * Sends the `Identify` response with the Signed Peer Record * to the requesting peer over the given `connection` diff --git a/src/index.ts b/src/index.ts index 9a530f71ed..62a300e64d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,23 +5,23 @@ import type { Startable } from '@libp2p/interfaces/startable' import type { Multiaddr } from '@multiformats/multiaddr' import type { FaultTolerance } from './transport-manager.js' import type { IdentifyServiceInit } from './identify/index.js' -import type { DualDHT } from '@libp2p/interfaces/dht' +import type { DualDHT } from '@libp2p/interface-dht' import type { Datastore } from 'interface-datastore' -import type { PeerStore, PeerStoreInit } from '@libp2p/interfaces/peer-store' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerStore, PeerStoreInit } from '@libp2p/interface-peer-store' +import type { PeerId } from '@libp2p/interface-peer-id' import type { AutoRelayConfig, RelayAdvertiseConfig } from './circuit/index.js' -import type { PeerDiscovery } from '@libp2p/interfaces/peer-discovery' -import type { Connection, ConnectionGater, ConnectionProtector, ProtocolStream } from '@libp2p/interfaces/connection' -import type { Transport } from '@libp2p/interfaces/transport' -import type { StreamMuxerFactory } from '@libp2p/interfaces/stream-muxer' -import type { ConnectionEncrypter } from '@libp2p/interfaces/connection-encrypter' -import type { PeerRouting } from '@libp2p/interfaces/peer-routing' -import type { ContentRouting } from '@libp2p/interfaces/content-routing' -import type { PubSub } from '@libp2p/interfaces/pubsub' -import type { Registrar, StreamHandler } from '@libp2p/interfaces/registrar' -import type { ConnectionManager } from '@libp2p/interfaces/connection-manager' -import type { Metrics, MetricsInit } from '@libp2p/interfaces/metrics' -import type { PeerInfo } from '@libp2p/interfaces/peer-info' +import type { PeerDiscovery } from '@libp2p/interface-peer-discovery' +import type { Connection, ConnectionGater, ConnectionProtector, ProtocolStream } from '@libp2p/interface-connection' +import type { Transport } from '@libp2p/interface-transport' +import type { StreamMuxerFactory } from '@libp2p/interface-stream-muxer' +import type { ConnectionEncrypter } from '@libp2p/interface-connection-encrypter' +import type { PeerRouting } from '@libp2p/interface-peer-routing' +import type { ContentRouting } from '@libp2p/interface-content-routing' +import type { PubSub } from '@libp2p/interface-pubsub' +import type { Registrar, StreamHandler } from '@libp2p/interface-registrar' +import type { ConnectionManager } from '@libp2p/interface-connection-manager' +import type { Metrics, MetricsInit } from '@libp2p/interface-metrics' +import type { PeerInfo } from '@libp2p/interface-peer-info' import type { KeyChain } from './keychain/index.js' import type { ConnectionManagerInit } from './connection-manager/index.js' import type { PingServiceInit } from './ping/index.js' diff --git a/src/insecure/index.ts b/src/insecure/index.ts index 76dc0ca379..446104e0ca 100644 --- a/src/insecure/index.ts +++ b/src/insecure/index.ts @@ -1,11 +1,11 @@ import { logger } from '@libp2p/logger' import { handshake } from 'it-handshake' import * as lp from 'it-length-prefixed' -import { UnexpectedPeerError, InvalidCryptoExchangeError } from '@libp2p/interfaces/connection-encrypter/errors' +import { UnexpectedPeerError, InvalidCryptoExchangeError } from '@libp2p/interface-connection-encrypter/errors' import { Exchange, KeyType } from './pb/proto.js' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' import { peerIdFromBytes, peerIdFromKeys } from '@libp2p/peer-id' -import type { ConnectionEncrypter, SecuredConnection } from '@libp2p/interfaces/connection-encrypter' +import type { ConnectionEncrypter, SecuredConnection } from '@libp2p/interface-connection-encrypter' import type { Duplex } from 'it-stream-types' const log = logger('libp2p:plaintext') diff --git a/src/keychain/index.ts b/src/keychain/index.ts index bbb5bb61a1..67543d491a 100644 --- a/src/keychain/index.ts +++ b/src/keychain/index.ts @@ -10,8 +10,8 @@ import { codes } from '../errors.js' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { generateKeyPair, importKey, unmarshalPrivateKey } from '@libp2p/crypto/keys' -import type { PeerId } from '@libp2p/interfaces/peer-id' -import type { Components } from '@libp2p/interfaces/components' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { Components } from '@libp2p/components' import { pbkdf2, randomBytes } from '@libp2p/crypto' const log = logger('libp2p:keychain') diff --git a/src/libp2p.ts b/src/libp2p.ts index 9d2dafed35..b7ff6b4a0f 100644 --- a/src/libp2p.ts +++ b/src/libp2p.ts @@ -27,25 +27,25 @@ import { DHTPeerRouting } from './dht/dht-peer-routing.js' import { PersistentPeerStore } from '@libp2p/peer-store' import { DHTContentRouting } from './dht/dht-content-routing.js' import { AutoDialer } from './connection-manager/dialer/auto-dialer.js' -import { Initializable, Components, isInitializable } from '@libp2p/interfaces/components' -import type { PeerId } from '@libp2p/interfaces/peer-id' -import type { Connection } from '@libp2p/interfaces/connection' -import type { PeerRouting } from '@libp2p/interfaces/peer-routing' -import type { ContentRouting } from '@libp2p/interfaces/content-routing' -import type { PubSub } from '@libp2p/interfaces/pubsub' -import type { Registrar, StreamHandler } from '@libp2p/interfaces/registrar' -import type { ConnectionManager } from '@libp2p/interfaces/connection-manager' -import type { PeerInfo } from '@libp2p/interfaces/peer-info' +import { Initializable, Components, isInitializable } from '@libp2p/components' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { Connection } from '@libp2p/interface-connection' +import type { PeerRouting } from '@libp2p/interface-peer-routing' +import type { ContentRouting } from '@libp2p/interface-content-routing' +import type { PubSub } from '@libp2p/interface-pubsub' +import type { Registrar, StreamHandler } from '@libp2p/interface-registrar' +import type { ConnectionManager } from '@libp2p/interface-connection-manager' +import type { PeerInfo } from '@libp2p/interface-peer-info' import type { Libp2p, Libp2pEvents, Libp2pInit, Libp2pOptions } from './index.js' import { validateConfig } from './config.js' import { createEd25519PeerId } from '@libp2p/peer-id-factory' -import type { PeerStore } from '@libp2p/interfaces/peer-store' -import type { DualDHT } from '@libp2p/interfaces/dht' +import type { PeerStore } from '@libp2p/interface-peer-store' +import type { DualDHT } from '@libp2p/interface-dht' import { concat as uint8ArrayConcat } from 'uint8arrays/concat' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import errCode from 'err-code' import { unmarshalPublicKey } from '@libp2p/crypto/keys' -import type { Metrics } from '@libp2p/interfaces/metrics' +import type { Metrics } from '@libp2p/interface-metrics' import { DummyDHT } from './dht/dummy-dht.js' import { DummyPubSub } from './pubsub/dummy-pubsub.js' import { PeerSet } from '@libp2p/peer-collections' @@ -491,11 +491,27 @@ export class Libp2pNode extends EventEmitter implements Libp2p { } async handle (protocols: string | string[], handler: StreamHandler): Promise { - return await this.components.getRegistrar().handle(protocols, handler) + if (!Array.isArray(protocols)) { + protocols = [protocols] + } + + await Promise.all( + protocols.map(async protocol => { + await this.components.getRegistrar().handle(protocol, handler) + }) + ) } async unhandle (protocols: string[] | string): Promise { - return await this.components.getRegistrar().unhandle(protocols) + if (!Array.isArray(protocols)) { + protocols = [protocols] + } + + await Promise.all( + protocols.map(async protocol => { + await this.components.getRegistrar().unhandle(protocol) + }) + ) } /** diff --git a/src/metrics/index.ts b/src/metrics/index.ts index 47b560c217..d5e93a388d 100644 --- a/src/metrics/index.ts +++ b/src/metrics/index.ts @@ -3,8 +3,8 @@ import each from 'it-foreach' import LRU from 'hashlru' import { METRICS as defaultOptions } from '../constants.js' import { DefaultStats, StatsInit } from './stats.js' -import type { ComponentMetricsUpdate, Metrics, Stats, TrackStreamOptions } from '@libp2p/interfaces/metrics' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { ComponentMetricsUpdate, Metrics, Stats, TrackStreamOptions } from '@libp2p/interface-metrics' +import type { PeerId } from '@libp2p/interface-peer-id' import type { Startable } from '@libp2p/interfaces/startable' import type { Duplex } from 'it-stream-types' diff --git a/src/metrics/moving-average.ts b/src/metrics/moving-average.ts index 615b62cf24..c3439d98e7 100644 --- a/src/metrics/moving-average.ts +++ b/src/metrics/moving-average.ts @@ -1,4 +1,4 @@ -import type { MovingAverage } from '@libp2p/interfaces/metrics' +import type { MovingAverage } from '@libp2p/interface-metrics' export class DefaultMovingAverage { public movingAverage: number diff --git a/src/metrics/stats.ts b/src/metrics/stats.ts index 2e50bff884..45f251d7e6 100644 --- a/src/metrics/stats.ts +++ b/src/metrics/stats.ts @@ -2,7 +2,7 @@ import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events' import { createMovingAverage } from './moving-average.js' // @ts-expect-error no types import retimer from 'retimer' -import type { MovingAverages, Stats, TransferStats } from '@libp2p/interfaces/metrics' +import type { MovingAverages, Stats, TransferStats } from '@libp2p/interface-metrics' export interface StatsEvents { 'update': CustomEvent diff --git a/src/nat-manager.ts b/src/nat-manager.ts index 8d229f07cf..7e9cb0c61b 100644 --- a/src/nat-manager.ts +++ b/src/nat-manager.ts @@ -8,7 +8,7 @@ import errCode from 'err-code' import { codes } from './errors.js' import { isLoopback } from '@libp2p/utils/multiaddr/is-loopback' import type { Startable } from '@libp2p/interfaces/startable' -import type { Components } from '@libp2p/interfaces/components' +import type { Components } from '@libp2p/components' const log = logger('libp2p:nat') const DEFAULT_TTL = 7200 diff --git a/src/peer-record-updater.ts b/src/peer-record-updater.ts index 9a38db8b76..0901c15d46 100644 --- a/src/peer-record-updater.ts +++ b/src/peer-record-updater.ts @@ -1,5 +1,5 @@ import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record' -import type { Components } from '@libp2p/interfaces/components' +import type { Components } from '@libp2p/components' import type { Startable } from '@libp2p/interfaces/startable' import { logger } from '@libp2p/logger' import { protocols } from '@multiformats/multiaddr' diff --git a/src/peer-routing.ts b/src/peer-routing.ts index a47f1a8275..f380e0d76a 100644 --- a/src/peer-routing.ts +++ b/src/peer-routing.ts @@ -18,12 +18,12 @@ import { // @ts-expect-error module with no types } from 'set-delayed-interval' import { setMaxListeners } from 'events' -import type { PeerId } from '@libp2p/interfaces/peer-id' -import type { PeerRouting } from '@libp2p/interfaces/peer-routing' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { PeerRouting } from '@libp2p/interface-peer-routing' import type { AbortOptions } from '@libp2p/interfaces' import type { Startable } from '@libp2p/interfaces/startable' -import type { PeerInfo } from '@libp2p/interfaces/peer-info' -import type { Components } from '@libp2p/interfaces/components' +import type { PeerInfo } from '@libp2p/interface-peer-info' +import type { Components } from '@libp2p/components' const log = logger('libp2p:peer-routing') diff --git a/src/ping/index.ts b/src/ping/index.ts index 685e1cd5c2..c7766ed339 100644 --- a/src/ping/index.ts +++ b/src/ping/index.ts @@ -6,10 +6,10 @@ import { pipe } from 'it-pipe' import first from 'it-first' import { equals as uint8ArrayEquals } from 'uint8arrays/equals' import { PROTOCOL_NAME, PING_LENGTH, PROTOCOL_VERSION } from './constants.js' -import type { IncomingStreamData } from '@libp2p/interfaces/registrar' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { IncomingStreamData } from '@libp2p/interface-registrar' +import type { PeerId } from '@libp2p/interface-peer-id' import type { Startable } from '@libp2p/interfaces/startable' -import type { Components } from '@libp2p/interfaces/components' +import type { Components } from '@libp2p/components' import type { AbortOptions } from '@libp2p/interfaces' import type { Duplex } from 'it-stream-types' import { abortableDuplex } from 'abortable-iterator' diff --git a/src/pnet/index.ts b/src/pnet/index.ts index 2b3ea2edf3..2a14d52dfb 100644 --- a/src/pnet/index.ts +++ b/src/pnet/index.ts @@ -12,8 +12,7 @@ import { } from './crypto.js' import { handshake } from 'it-handshake' import { NONCE_LENGTH } from './key-generator.js' -import type { MultiaddrConnection } from '@libp2p/interfaces/transport' -import type { ConnectionProtector } from '@libp2p/interfaces/connection' +import type { ConnectionProtector, MultiaddrConnection } from '@libp2p/interface-connection' const log = logger('libp2p:pnet') diff --git a/src/pubsub/dummy-pubsub.ts b/src/pubsub/dummy-pubsub.ts index b44efcb509..4aa82dce93 100644 --- a/src/pubsub/dummy-pubsub.ts +++ b/src/pubsub/dummy-pubsub.ts @@ -1,6 +1,6 @@ import { EventEmitter } from '@libp2p/interfaces/events' -import type { PeerId } from '@libp2p/interfaces/peer-id' -import type { PublishResult, PubSub, PubSubEvents, StrictNoSign, StrictSign } from '@libp2p/interfaces/pubsub' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { PublishResult, PubSub, PubSubEvents, StrictNoSign, StrictSign } from '@libp2p/interface-pubsub' import errCode from 'err-code' import { messages, codes } from '../errors.js' diff --git a/src/registrar.ts b/src/registrar.ts index ef6fd5016b..41d1a194d7 100644 --- a/src/registrar.ts +++ b/src/registrar.ts @@ -1,30 +1,20 @@ import { logger } from '@libp2p/logger' import errCode from 'err-code' import { codes } from './errors.js' -import { isTopology, Topology } from '@libp2p/interfaces/topology' -import type { Registrar, StreamHandler } from '@libp2p/interfaces/registrar' -import type { PeerProtocolsChangeData } from '@libp2p/interfaces/peer-store' -import type { Connection } from '@libp2p/interfaces/connection' -import type { Components } from '@libp2p/interfaces/components' +import { isTopology, StreamHandlerOptions, StreamHandlerRecord } from '@libp2p/interface-registrar' +import type { Registrar, StreamHandler, Topology } from '@libp2p/interface-registrar' +import type { PeerProtocolsChangeData } from '@libp2p/interface-peer-store' +import type { Connection } from '@libp2p/interface-connection' +import type { Components } from '@libp2p/components' const log = logger('libp2p:registrar') -function supportsProtocol (peerProtocols: string[], topologyProtocols: string[]) { - for (const peerProtocol of peerProtocols) { - if (topologyProtocols.includes(peerProtocol)) { - return true - } - } - - return false -} - /** * Responsible for notifying registered protocols of events in the network. */ export class DefaultRegistrar implements Registrar { - private readonly topologies: Map - private readonly handlers: Map + private readonly topologies: Map> + private readonly handlers: Map private readonly components: Components constructor (components: Components) { @@ -42,17 +32,10 @@ export class DefaultRegistrar implements Registrar { } getProtocols () { - const protocols = new Set() - - for (const topology of this.topologies.values()) { - topology.protocols.forEach(protocol => protocols.add(protocol)) - } - - for (const protocol of this.handlers.keys()) { - protocols.add(protocol) - } - - return Array.from(protocols).sort() + return Array.from(new Set([ + ...this.topologies.keys(), + ...this.handlers.keys() + ])).sort() } getHandler (protocol: string) { @@ -66,33 +49,32 @@ export class DefaultRegistrar implements Registrar { } getTopologies (protocol: string) { - const output: Topology[] = [] + const topologies = this.topologies.get(protocol) - for (const { topology, protocols } of this.topologies.values()) { - if (protocols.includes(protocol)) { - output.push(topology) - } + if (topologies == null) { + return [] } - return output + return [ + ...topologies.values() + ] } /** * Registers the `handler` for each protocol */ - async handle (protocols: string | string[], handler: StreamHandler): Promise { - const protocolList = Array.isArray(protocols) ? protocols : [protocols] - - for (const protocol of protocolList) { - if (this.handlers.has(protocol)) { - throw errCode(new Error(`Handler already registered for protocol ${protocol}`), codes.ERR_PROTOCOL_HANDLER_ALREADY_REGISTERED) - } - - this.handlers.set(protocol, handler) + async handle (protocol: string, handler: StreamHandler, options: StreamHandlerOptions = { maxConcurrentStreams: 1 }): Promise { + if (this.handlers.has(protocol)) { + throw errCode(new Error(`Handler already registered for protocol ${protocol}`), codes.ERR_PROTOCOL_HANDLER_ALREADY_REGISTERED) } + this.handlers.set(protocol, { + handler, + options + }) + // Add new protocols to self protocols in the Protobook - await this.components.getPeerStore().protoBook.add(this.components.getPeerId(), protocolList) + await this.components.getPeerStore().protoBook.add(this.components.getPeerId(), [protocol]) } /** @@ -113,7 +95,7 @@ export class DefaultRegistrar implements Registrar { /** * Register handlers for a set of multicodecs given */ - async register (protocols: string | string[], topology: Topology): Promise { + async register (protocol: string, topology: Topology): Promise { if (!isTopology(topology)) { log.error('topology must be an instance of interfaces/topology') throw errCode(new Error('topology must be an instance of interfaces/topology'), codes.ERR_INVALID_PARAMETERS) @@ -122,10 +104,14 @@ export class DefaultRegistrar implements Registrar { // Create topology const id = `${(Math.random() * 1e9).toString(36)}${Date.now()}` - this.topologies.set(id, { - topology, - protocols: Array.isArray(protocols) ? protocols : [protocols] - }) + let topologies = this.topologies.get(protocol) + + if (topologies == null) { + topologies = new Map() + this.topologies.set(protocol, topologies) + } + + topologies.set(id, topology) // Set registrar await topology.setRegistrar(this) @@ -137,7 +123,15 @@ export class DefaultRegistrar implements Registrar { * Unregister topology */ unregister (id: string) { - this.topologies.delete(id) + for (const [protocol, topologies] of this.topologies.entries()) { + if (topologies.has(id)) { + topologies.delete(id) + + if (topologies.size === 0) { + this.topologies.delete(protocol) + } + } + } } /** @@ -148,8 +142,15 @@ export class DefaultRegistrar implements Registrar { void this.components.getPeerStore().protoBook.get(connection.remotePeer) .then(peerProtocols => { - for (const { topology, protocols } of this.topologies.values()) { - if (supportsProtocol(peerProtocols, protocols)) { + for (const protocol of peerProtocols) { + const topologies = this.topologies.get(protocol) + + if (topologies == null) { + // no topologies are interested in this protocol + continue + } + + for (const topology of topologies.values()) { topology.onDisconnect(connection.remotePeer) } } @@ -168,14 +169,28 @@ export class DefaultRegistrar implements Registrar { const removed = oldProtocols.filter(protocol => !protocols.includes(protocol)) const added = protocols.filter(protocol => !oldProtocols.includes(protocol)) - for (const { topology, protocols } of this.topologies.values()) { - if (supportsProtocol(removed, protocols)) { + for (const protocol of removed) { + const topologies = this.topologies.get(protocol) + + if (topologies == null) { + // no topologies are interested in this protocol + continue + } + + for (const topology of topologies.values()) { topology.onDisconnect(peerId) } } - for (const { topology, protocols } of this.topologies.values()) { - if (supportsProtocol(added, protocols)) { + for (const protocol of added) { + const topologies = this.topologies.get(protocol) + + if (topologies == null) { + // no topologies are interested in this protocol + continue + } + + for (const topology of topologies.values()) { const connection = this.components.getConnectionManager().getConnections(peerId)[0] if (connection == null) { diff --git a/src/transport-manager.ts b/src/transport-manager.ts index ba76aa780b..0721cfcdb8 100644 --- a/src/transport-manager.ts +++ b/src/transport-manager.ts @@ -2,13 +2,13 @@ import { logger } from '@libp2p/logger' import pSettle from 'p-settle' import { codes } from './errors.js' import errCode from 'err-code' -import type { Listener, Transport, TransportManager, TransportManagerEvents } from '@libp2p/interfaces/transport' +import type { Listener, Transport, TransportManager, TransportManagerEvents } from '@libp2p/interface-transport' import type { Multiaddr } from '@multiformats/multiaddr' -import type { Connection } from '@libp2p/interfaces/connection' +import type { Connection } from '@libp2p/interface-connection' import type { AbortOptions } from '@libp2p/interfaces' import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events' import type { Startable } from '@libp2p/interfaces/startable' -import type { Components } from '@libp2p/interfaces/components' +import type { Components } from '@libp2p/components' import { trackedMap } from '@libp2p/tracked-map' const log = logger('libp2p:transports') diff --git a/src/upgrader.ts b/src/upgrader.ts index 23f29619d4..961fd10da6 100644 --- a/src/upgrader.ts +++ b/src/upgrader.ts @@ -8,13 +8,13 @@ import { codes } from './errors.js' import { createConnection } from '@libp2p/connection' import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events' import { peerIdFromString } from '@libp2p/peer-id' -import type { Connection, ProtocolStream, Stream } from '@libp2p/interfaces/connection' -import type { ConnectionEncrypter, SecuredConnection } from '@libp2p/interfaces/connection-encrypter' -import type { StreamMuxer, StreamMuxerFactory } from '@libp2p/interfaces/stream-muxer' -import type { PeerId } from '@libp2p/interfaces/peer-id' -import type { MultiaddrConnection, Upgrader, UpgraderEvents } from '@libp2p/interfaces/transport' +import type { MultiaddrConnection, Connection, ProtocolStream, Stream } from '@libp2p/interface-connection' +import type { ConnectionEncrypter, SecuredConnection } from '@libp2p/interface-connection-encrypter' +import type { StreamMuxer, StreamMuxerFactory } from '@libp2p/interface-stream-muxer' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { Upgrader, UpgraderEvents } from '@libp2p/interface-transport' import type { Duplex } from 'it-stream-types' -import type { Components } from '@libp2p/interfaces/components' +import { Components, isInitializable } from '@libp2p/components' import type { AbortOptions } from '@libp2p/interfaces' const log = logger('libp2p:upgrader') @@ -272,7 +272,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg if (muxerFactory != null) { // Create the muxer - muxer = muxerFactory.createStreamMuxer(this.components, { + muxer = muxerFactory.createStreamMuxer({ // Run anytime a remote stream is created onIncomingStream: muxedStream => { if (connection == null) { @@ -313,6 +313,10 @@ export class DefaultUpgrader extends EventEmitter implements Upg } }) + if (isInitializable(muxer)) { + muxer.init(this.components) + } + newStream = async (protocols: string[], options: AbortOptions = {}): Promise => { if (muxer == null) { throw errCode(new Error('Stream is not multiplexed'), codes.ERR_MUXER_UNAVAILABLE) @@ -417,7 +421,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg */ _onStream (opts: OnStreamOptions): void { const { connection, stream, protocol } = opts - const handler = this.components.getRegistrar().getHandler(protocol) + const { handler } = this.components.getRegistrar().getHandler(protocol) handler({ connection, stream, protocol }) } diff --git a/test/addresses/address-manager.spec.ts b/test/addresses/address-manager.spec.ts index 55e5326fb2..9095e62080 100644 --- a/test/addresses/address-manager.spec.ts +++ b/test/addresses/address-manager.spec.ts @@ -7,10 +7,10 @@ import { createNode } from '../utils/creators/peer.js' import { createFromJSON } from '@libp2p/peer-id-factory' import Peers from '../fixtures/peers.js' import { stubInterface } from 'ts-sinon' -import type { TransportManager } from '@libp2p/interfaces/transport' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { TransportManager } from '@libp2p/interface-transport' +import type { PeerId } from '@libp2p/interface-peer-id' import type { Libp2p } from '../../src/index.js' -import { Components } from '@libp2p/interfaces/components' +import { Components } from '@libp2p/components' const listenAddresses = ['/ip4/127.0.0.1/tcp/15006/ws', '/ip4/127.0.0.1/tcp/15008/ws'] const announceAddreses = ['/dns4/peer.io'] diff --git a/test/configuration/pubsub.spec.ts b/test/configuration/pubsub.spec.ts index f3ca170f0d..fdc557e4ac 100644 --- a/test/configuration/pubsub.spec.ts +++ b/test/configuration/pubsub.spec.ts @@ -9,7 +9,7 @@ import { baseOptions, pubsubSubsystemOptions } from './utils.js' import { createPeerId } from '../utils/creators/peer.js' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { FloodSub } from '@libp2p/floodsub' -import type { PubSub } from '@libp2p/interfaces/pubsub' +import type { PubSub } from '@libp2p/interface-pubsub' describe('Pubsub subsystem is configurable', () => { let libp2p: Libp2p diff --git a/test/configuration/utils.ts b/test/configuration/utils.ts index b7cbacf82c..a34385c9e3 100644 --- a/test/configuration/utils.ts +++ b/test/configuration/utils.ts @@ -5,9 +5,9 @@ import { WebSockets } from '@libp2p/websockets' import * as filters from '@libp2p/websockets/filters' import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' import mergeOptions from 'merge-options' -import type { Message, PublishResult, PubSubInit, PubSubRPC, PubSubRPCMessage } from '@libp2p/interfaces/pubsub' +import type { Message, PublishResult, PubSubInit, PubSubRPC, PubSubRPCMessage } from '@libp2p/interface-pubsub' import type { Libp2pInit, Libp2pOptions } from '../../src/index.js' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' import * as cborg from 'cborg' import { peerIdFromString } from '@libp2p/peer-id' diff --git a/test/connection-manager/auto-dialler.spec.ts b/test/connection-manager/auto-dialler.spec.ts index e0e360522c..2704435d79 100644 --- a/test/connection-manager/auto-dialler.spec.ts +++ b/test/connection-manager/auto-dialler.spec.ts @@ -5,10 +5,10 @@ import { AutoDialler } from '../../src/connection-manager/auto-dialler.js' import pWaitFor from 'p-wait-for' import delay from 'delay' import { createEd25519PeerId } from '@libp2p/peer-id-factory' -import { Components } from '@libp2p/interfaces/components' +import { Components } from '@libp2p/components' import { stubInterface } from 'ts-sinon' -import type { ConnectionManager } from '@libp2p/interfaces/connection-manager' -import type { PeerStore, Peer } from '@libp2p/interfaces/peer-store' +import type { ConnectionManager } from '@libp2p/interface-connection-manager' +import type { PeerStore, Peer } from '@libp2p/interface-peer-store' describe('Auto-dialler', () => { it('should not dial self', async () => { diff --git a/test/connection-manager/index.node.ts b/test/connection-manager/index.node.ts index 9c053b0ec5..6e5d693c37 100644 --- a/test/connection-manager/index.node.ts +++ b/test/connection-manager/index.node.ts @@ -2,19 +2,19 @@ import { expect } from 'aegir/chai' import { createNode, createPeerId } from '../utils/creators/peer.js' -import { mockConnection, mockDuplex, mockMultiaddrConnection, mockUpgrader } from '@libp2p/interface-compliance-tests/mocks' +import { mockConnection, mockDuplex, mockMultiaddrConnection, mockUpgrader } from '@libp2p/interface-mocks' import { createBaseOptions } from '../utils/base-options.browser.js' import type { Libp2p } from '../../src/index.js' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' -import { Components } from '@libp2p/interfaces/components' +import { Components } from '@libp2p/components' import { CustomEvent } from '@libp2p/interfaces/events' -import * as STATUS from '@libp2p/interfaces/connection/status' +import * as STATUS from '@libp2p/interface-connection/status' import { stubInterface } from 'ts-sinon' -import type { KeyBook, PeerStore } from '@libp2p/interfaces/peer-store' +import type { KeyBook, PeerStore } from '@libp2p/interface-peer-store' import sinon from 'sinon' import pWaitFor from 'p-wait-for' -import type { Connection } from '@libp2p/interfaces/connection' +import type { Connection } from '@libp2p/interface-connection' import delay from 'delay' import type { Libp2pNode } from '../../src/libp2p.js' import { codes } from '../../src/errors.js' diff --git a/test/connection-manager/index.spec.ts b/test/connection-manager/index.spec.ts index 7253bcd197..950223c0a3 100644 --- a/test/connection-manager/index.spec.ts +++ b/test/connection-manager/index.spec.ts @@ -6,7 +6,7 @@ import { createNode } from '../utils/creators/peer.js' import { createBaseOptions } from '../utils/base-options.browser.js' import type { Libp2pNode } from '../../src/libp2p.js' import type { DefaultConnectionManager } from '../../src/connection-manager/index.js' -import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-compliance-tests/mocks' +import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-mocks' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { CustomEvent } from '@libp2p/interfaces/events' diff --git a/test/content-routing/content-routing.node.ts b/test/content-routing/content-routing.node.ts index 8ced7d6f22..0a6280a775 100644 --- a/test/content-routing/content-routing.node.ts +++ b/test/content-routing/content-routing.node.ts @@ -14,7 +14,7 @@ import { createNode, createPeerId, populateAddressBooks } from '../utils/creator import { createBaseOptions } from '../utils/base-options.js' import { createRoutingOptions } from './utils.js' import type { Libp2p } from '../../src/index.js' -import type { PeerInfo } from '@libp2p/interfaces/peer-info' +import type { PeerInfo } from '@libp2p/interface-peer-info' import type { Libp2pNode } from '../../src/libp2p.js' describe('content-routing', () => { diff --git a/test/content-routing/dht/operation.node.ts b/test/content-routing/dht/operation.node.ts index 65bee28fe1..412ae7659f 100644 --- a/test/content-routing/dht/operation.node.ts +++ b/test/content-routing/dht/operation.node.ts @@ -6,7 +6,7 @@ import pWaitFor from 'p-wait-for' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { subsystemMulticodecs, createSubsystemOptions } from './utils.js' import { createPeerId } from '../../utils/creators/peer.js' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' import { createLibp2pNode, Libp2pNode } from '../../../src/libp2p.js' import { start } from '@libp2p/interfaces/startable' diff --git a/test/core/encryption.spec.ts b/test/core/encryption.spec.ts index a2cca6a5d8..54d4d4c8c2 100644 --- a/test/core/encryption.spec.ts +++ b/test/core/encryption.spec.ts @@ -6,7 +6,7 @@ import { NOISE } from '@chainsafe/libp2p-noise' import { createLibp2p, Libp2pOptions } from '../../src/index.js' import { codes as ErrorCodes } from '../../src/errors.js' import { createPeerId } from '../utils/creators/peer.js' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' describe('Connection encryption configuration', () => { let peerId: PeerId diff --git a/test/core/listening.node.ts b/test/core/listening.node.ts index 26b0162ec9..fdb7e66246 100644 --- a/test/core/listening.node.ts +++ b/test/core/listening.node.ts @@ -4,7 +4,7 @@ import { expect } from 'aegir/chai' import { TCP } from '@libp2p/tcp' import { NOISE } from '@chainsafe/libp2p-noise' import { createPeerId } from '../utils/creators/peer.js' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' const listenAddr = '/ip4/0.0.0.0/tcp/0' diff --git a/test/dialing/dial-request.spec.ts b/test/dialing/dial-request.spec.ts index 3c945922f6..b6dd5222a3 100644 --- a/test/dialing/dial-request.spec.ts +++ b/test/dialing/dial-request.spec.ts @@ -6,7 +6,7 @@ import { AbortError } from '@libp2p/interfaces/errors' import pDefer from 'p-defer' import delay from 'delay' import { DialAction, DialRequest } from '../../src/connection-manager/dialer/dial-request.js' -import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-compliance-tests/mocks' +import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-mocks' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { Multiaddr } from '@multiformats/multiaddr' import { Dialer } from '../../src/connection-manager/dialer/index.js' diff --git a/test/dialing/direct.node.ts b/test/dialing/direct.node.ts index 9c84988307..c5e3b4f5dd 100644 --- a/test/dialing/direct.node.ts +++ b/test/dialing/direct.node.ts @@ -13,7 +13,7 @@ import pSettle, { PromiseResult } from 'p-settle' import pWaitFor from 'p-wait-for' import { pipe } from 'it-pipe' import { pushable } from 'it-pushable' -import { Connection, isConnection } from '@libp2p/interfaces/connection' +import { Connection, isConnection } from '@libp2p/interface-connection' import { AbortError } from '@libp2p/interfaces/errors' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { MemoryDatastore } from 'datastore-core/memory' @@ -22,11 +22,11 @@ import { DefaultAddressManager } from '../../src/address-manager/index.js' import { PersistentPeerStore } from '@libp2p/peer-store' import { DefaultTransportManager } from '../../src/transport-manager.js' import { codes as ErrorCodes } from '../../src/errors.js' -import { mockConnectionGater, mockDuplex, mockMultiaddrConnection, mockUpgrader, mockConnection } from '@libp2p/interface-compliance-tests/mocks' +import { mockConnectionGater, mockDuplex, mockMultiaddrConnection, mockUpgrader, mockConnection } from '@libp2p/interface-mocks' import Peers from '../fixtures/peers.js' -import { Components } from '@libp2p/interfaces/components' +import { Components } from '@libp2p/components' import { createFromJSON } from '@libp2p/peer-id-factory' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' import { PreSharedKeyConnectionProtector } from '../../src/pnet/index.js' import swarmKey from '../fixtures/swarm.key.js' @@ -382,7 +382,7 @@ describe('libp2p.dialer (direct, TCP)', () => { await libp2p.dialProtocol(remoteLibp2p.peerId, '/stream-count/4') // Partially write to the echo stream - const source = pushable() + const source = pushable() void stream.sink(source) source.push(uint8ArrayFromString('hello')) diff --git a/test/dialing/direct.spec.ts b/test/dialing/direct.spec.ts index 444c9d4193..e4112c97cc 100644 --- a/test/dialing/direct.spec.ts +++ b/test/dialing/direct.spec.ts @@ -17,18 +17,18 @@ import { Dialer, DialTarget } from '../../src/connection-manager/dialer/index.js import { publicAddressesFirst } from '@libp2p/utils/address-sort' import { PersistentPeerStore } from '@libp2p/peer-store' import { DefaultTransportManager } from '../../src/transport-manager.js' -import { mockConnectionGater, mockDuplex, mockMultiaddrConnection, mockUpgrader, mockConnection } from '@libp2p/interface-compliance-tests/mocks' +import { mockConnectionGater, mockDuplex, mockMultiaddrConnection, mockUpgrader, mockConnection } from '@libp2p/interface-mocks' import { createPeerId } from '../utils/creators/peer.js' -import type { TransportManager } from '@libp2p/interfaces/transport' -import { Components } from '@libp2p/interfaces/components' +import type { TransportManager } from '@libp2p/interface-transport' +import { Components } from '@libp2p/components' import { peerIdFromString } from '@libp2p/peer-id' -import type { Connection } from '@libp2p/interfaces/connection' +import type { Connection } from '@libp2p/interface-connection' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' import { createFromJSON } from '@libp2p/peer-id-factory' import Peers from '../fixtures/peers.js' import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' import { pEvent } from 'p-event' const unsupportedAddr = new Multiaddr('/ip4/127.0.0.1/tcp/9999') diff --git a/test/dialing/resolver.spec.ts b/test/dialing/resolver.spec.ts index 3e39b22702..ded6e52df1 100644 --- a/test/dialing/resolver.spec.ts +++ b/test/dialing/resolver.spec.ts @@ -7,11 +7,11 @@ import { codes as ErrorCodes } from '../../src/errors.js' import { createNode } from '../utils/creators/peer.js' import { createBaseOptions } from '../utils/base-options.browser.js' import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' import type { Libp2pNode } from '../../src/libp2p.js' import { Circuit } from '../../src/circuit/transport.js' import pDefer from 'p-defer' -import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-compliance-tests/mocks' +import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-mocks' import { peerIdFromString } from '@libp2p/peer-id' import { WebSockets } from '@libp2p/websockets' diff --git a/test/fetch/fetch.node.ts b/test/fetch/fetch.node.ts index c33b73a796..32a089d96a 100644 --- a/test/fetch/fetch.node.ts +++ b/test/fetch/fetch.node.ts @@ -7,7 +7,7 @@ import { Mplex } from '@libp2p/mplex' import { NOISE } from '@chainsafe/libp2p-noise' import { createPeerId } from '../utils/creators/peer.js' import { codes } from '../../src/errors.js' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' async function createNode (peerId: PeerId) { return await createLibp2pNode({ diff --git a/test/fetch/index.spec.ts b/test/fetch/index.spec.ts index 0e9d173b1b..f776d77727 100644 --- a/test/fetch/index.spec.ts +++ b/test/fetch/index.spec.ts @@ -4,9 +4,9 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' import { FetchService } from '../../src/fetch/index.js' import Peers from '../fixtures/peers.js' -import { mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-compliance-tests/mocks' +import { mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-mocks' import { createFromJSON } from '@libp2p/peer-id-factory' -import { Components } from '@libp2p/interfaces/components' +import { Components } from '@libp2p/components' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' import { start, stop } from '@libp2p/interfaces/startable' import { CustomEvent } from '@libp2p/interfaces/events' diff --git a/test/identify/index.spec.ts b/test/identify/index.spec.ts index b47f9f0121..b59bbe0796 100644 --- a/test/identify/index.spec.ts +++ b/test/identify/index.spec.ts @@ -14,9 +14,9 @@ import { MemoryDatastore } from 'datastore-core/memory' import * as lp from 'it-length-prefixed' import drain from 'it-drain' import { pipe } from 'it-pipe' -import { mockConnectionGater, mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-compliance-tests/mocks' +import { mockConnectionGater, mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-mocks' import { createFromJSON } from '@libp2p/peer-id-factory' -import { Components } from '@libp2p/interfaces/components' +import { Components } from '@libp2p/components' import { PeerRecordUpdater } from '../../src/peer-record-updater.js' import { MULTICODEC_IDENTIFY, diff --git a/test/identify/push.spec.ts b/test/identify/push.spec.ts index 8f0b62e667..e9b5a7dfbd 100644 --- a/test/identify/push.spec.ts +++ b/test/identify/push.spec.ts @@ -10,9 +10,9 @@ import { DefaultAddressManager } from '../../src/address-manager/index.js' import { MemoryDatastore } from 'datastore-core/memory' import drain from 'it-drain' import { pipe } from 'it-pipe' -import { mockConnectionGater, mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-compliance-tests/mocks' +import { mockConnectionGater, mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-mocks' import { createFromJSON } from '@libp2p/peer-id-factory' -import { Components } from '@libp2p/interfaces/components' +import { Components } from '@libp2p/components' import { PeerRecordUpdater } from '../../src/peer-record-updater.js' import { MULTICODEC_IDENTIFY, diff --git a/test/identify/service.spec.ts b/test/identify/service.spec.ts index 3f198eb2ab..e772bad3e7 100644 --- a/test/identify/service.spec.ts +++ b/test/identify/service.spec.ts @@ -11,7 +11,7 @@ import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' import { createFromJSON } from '@libp2p/peer-id-factory' import pWaitFor from 'p-wait-for' import { peerIdFromString } from '@libp2p/peer-id' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' import type { Libp2pNode } from '../../src/libp2p.js' import { pEvent } from 'p-event' diff --git a/test/insecure/compliance.spec.ts b/test/insecure/compliance.spec.ts index 2c919f2df7..88b013d1d0 100644 --- a/test/insecure/compliance.spec.ts +++ b/test/insecure/compliance.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import suite from '@libp2p/interface-compliance-tests/connection-encrypter' +import suite from '@libp2p/interface-connection-encrypter-compliance-tests' import { Plaintext } from '../../src/insecure/index.js' describe('plaintext compliance', () => { diff --git a/test/insecure/plaintext.spec.ts b/test/insecure/plaintext.spec.ts index 2ad62faabb..7e3a9a7edf 100644 --- a/test/insecure/plaintext.spec.ts +++ b/test/insecure/plaintext.spec.ts @@ -7,11 +7,11 @@ import { Plaintext } from '../../src/insecure/index.js' import { InvalidCryptoExchangeError, UnexpectedPeerError -} from '@libp2p/interfaces/connection-encrypter/errors' -import type { PeerId } from '@libp2p/interfaces/peer-id' +} from '@libp2p/interface-connection-encrypter/errors' +import type { PeerId } from '@libp2p/interface-peer-id' import { createFromJSON, createRSAPeerId } from '@libp2p/peer-id-factory' -import type { ConnectionEncrypter } from '@libp2p/interfaces/connection-encrypter' -import { mockMultiaddrConnPair } from '@libp2p/interface-compliance-tests/mocks' +import type { ConnectionEncrypter } from '@libp2p/interface-connection-encrypter' +import { mockMultiaddrConnPair } from '@libp2p/interface-mocks' import { Multiaddr } from '@multiformats/multiaddr' import { peerIdFromBytes } from '@libp2p/peer-id' diff --git a/test/interop.ts b/test/interop.ts index 511b6a07d8..bf83312e6c 100644 --- a/test/interop.ts +++ b/test/interop.ts @@ -14,7 +14,7 @@ import { logger } from '@libp2p/logger' import { Mplex } from '@libp2p/mplex' import fs from 'fs' import { unmarshalPrivateKey } from '@libp2p/crypto/keys' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' import { peerIdFromKeys } from '@libp2p/peer-id' import { FloodSub } from '@libp2p/floodsub' diff --git a/test/keychain/cms-interop.spec.ts b/test/keychain/cms-interop.spec.ts index 5a3494c7ef..6349ce4d8b 100644 --- a/test/keychain/cms-interop.spec.ts +++ b/test/keychain/cms-interop.spec.ts @@ -6,7 +6,7 @@ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { MemoryDatastore } from 'datastore-core/memory' import { KeyChain } from '../../src/keychain/index.js' -import { Components } from '@libp2p/interfaces/components' +import { Components } from '@libp2p/components' describe('cms interop', () => { const passPhrase = 'this is not a secure phrase' diff --git a/test/keychain/keychain.spec.ts b/test/keychain/keychain.spec.ts index fc7feb796a..dd742d485f 100644 --- a/test/keychain/keychain.spec.ts +++ b/test/keychain/keychain.spec.ts @@ -9,9 +9,9 @@ import { Key } from 'interface-datastore/key' import { MemoryDatastore } from 'datastore-core/memory' import { KeyChain, KeyChainInit, KeyInfo } from '../../src/keychain/index.js' import { pbkdf2 } from '@libp2p/crypto' -import { Components } from '@libp2p/interfaces/components' +import { Components } from '@libp2p/components' import type { Datastore } from 'interface-datastore' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' import { createFromPrivKey } from '@libp2p/peer-id-factory' import { unmarshalPrivateKey } from '@libp2p/crypto/keys' diff --git a/test/keychain/peerid.spec.ts b/test/keychain/peerid.spec.ts index c776bd901c..49d5da1a6d 100644 --- a/test/keychain/peerid.spec.ts +++ b/test/keychain/peerid.spec.ts @@ -4,7 +4,7 @@ import { expect } from 'aegir/chai' import { base58btc } from 'multiformats/bases/base58' import { supportedKeys, unmarshalPrivateKey, unmarshalPublicKey } from '@libp2p/crypto/keys' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' import { createFromPrivKey } from '@libp2p/peer-id-factory' const sample = { diff --git a/test/metrics/index.spec.ts b/test/metrics/index.spec.ts index 2efe63eee1..7bb0062f84 100644 --- a/test/metrics/index.spec.ts +++ b/test/metrics/index.spec.ts @@ -14,7 +14,7 @@ import { createPeerId } from '../utils/creators/peer.js' import toBuffer from 'it-to-buffer' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { peerIdFromString } from '@libp2p/peer-id' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' describe('Metrics', () => { let peerId: PeerId @@ -199,7 +199,7 @@ describe('Metrics', () => { }) const bytes = randomBytes(1024) - const input = pushable() + const input = pushable() const deferredPromise = pipe(input, local, drain) diff --git a/test/nat-manager/nat-manager.node.ts b/test/nat-manager/nat-manager.node.ts index 30e251ee52..01049a2a0d 100644 --- a/test/nat-manager/nat-manager.node.ts +++ b/test/nat-manager/nat-manager.node.ts @@ -4,13 +4,13 @@ import { expect } from 'aegir/chai' import { DefaultAddressManager } from '../../src/address-manager/index.js' import { DefaultTransportManager, FaultTolerance } from '../../src/transport-manager.js' import { TCP } from '@libp2p/tcp' -import { mockUpgrader } from '@libp2p/interface-compliance-tests/mocks' +import { mockUpgrader } from '@libp2p/interface-mocks' import { NatManager } from '../../src/nat-manager.js' import delay from 'delay' import Peers from '../fixtures/peers.js' import { codes } from '../../src/errors.js' import { createFromJSON } from '@libp2p/peer-id-factory' -import { Components } from '@libp2p/interfaces/components' +import { Components } from '@libp2p/components' import type { NatAPI } from '@achingbrain/nat-port-mapper' import { StubbedInstance, stubInterface } from 'ts-sinon' import { start, stop } from '@libp2p/interfaces/startable' diff --git a/test/peer-discovery/index.node.ts b/test/peer-discovery/index.node.ts index f33250757f..9b8591b0d8 100644 --- a/test/peer-discovery/index.node.ts +++ b/test/peer-discovery/index.node.ts @@ -11,10 +11,10 @@ import { Multiaddr } from '@multiformats/multiaddr' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { createBaseOptions } from '../utils/base-options.js' import { createPeerId } from '../utils/creators/peer.js' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' import { CustomEvent } from '@libp2p/interfaces/events' -import type { PeerInfo } from '@libp2p/interfaces/peer-info' +import type { PeerInfo } from '@libp2p/interface-peer-info' const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') diff --git a/test/peer-discovery/index.spec.ts b/test/peer-discovery/index.spec.ts index 6e453c5cd4..d76f06ba57 100644 --- a/test/peer-discovery/index.spec.ts +++ b/test/peer-discovery/index.spec.ts @@ -6,9 +6,9 @@ import defer from 'p-defer' import { Multiaddr } from '@multiformats/multiaddr' import { createBaseOptions } from '../utils/base-options.browser.js' import { createPeerId } from '../utils/creators/peer.js' -import { isPeerId, PeerId } from '@libp2p/interfaces/peer-id' +import { isPeerId, PeerId } from '@libp2p/interface-peer-id' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' -import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-compliance-tests/mocks' +import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-mocks' import type { Startable } from '@libp2p/interfaces/startable' describe('peer discovery', () => { diff --git a/test/peer-routing/peer-routing.node.ts b/test/peer-routing/peer-routing.node.ts index f98abfa0d9..f924d86b2e 100644 --- a/test/peer-routing/peer-routing.node.ts +++ b/test/peer-routing/peer-routing.node.ts @@ -16,11 +16,11 @@ import { createNode, createPeerId, populateAddressBooks } from '../utils/creator import type { Libp2pNode } from '../../src/libp2p.js' import { createBaseOptions } from '../utils/base-options.js' import { createRoutingOptions } from './utils.js' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' import { createEd25519PeerId } from '@libp2p/peer-id-factory' -import { EventTypes, MessageType } from '@libp2p/interfaces/dht' +import { EventTypes, MessageType } from '@libp2p/interface-dht' import { peerIdFromString } from '@libp2p/peer-id' -import type { PeerInfo } from '@libp2p/interfaces/peer-info' +import type { PeerInfo } from '@libp2p/interface-peer-info' import { KadDHT } from '@libp2p/kad-dht' describe('peer-routing', () => { diff --git a/test/ping/index.spec.ts b/test/ping/index.spec.ts index d97837bb13..b8be7e6425 100644 --- a/test/ping/index.spec.ts +++ b/test/ping/index.spec.ts @@ -4,9 +4,9 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' import { PingService } from '../../src/ping/index.js' import Peers from '../fixtures/peers.js' -import { mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-compliance-tests/mocks' +import { mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-mocks' import { createFromJSON } from '@libp2p/peer-id-factory' -import { Components } from '@libp2p/interfaces/components' +import { Components } from '@libp2p/components' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' import { start, stop } from '@libp2p/interfaces/startable' import { CustomEvent } from '@libp2p/interfaces/events' diff --git a/test/pnet/index.spec.ts b/test/pnet/index.spec.ts index cb1fae3ac1..6675db8e06 100644 --- a/test/pnet/index.spec.ts +++ b/test/pnet/index.spec.ts @@ -5,7 +5,7 @@ import all from 'it-all' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { PreSharedKeyConnectionProtector, generateKey } from '../../src/pnet/index.js' import { INVALID_PSK } from '../../src/pnet/errors.js' -import { mockMultiaddrConnPair } from '@libp2p/interface-compliance-tests/mocks' +import { mockMultiaddrConnPair } from '@libp2p/interface-mocks' import { Multiaddr } from '@multiformats/multiaddr' import { createEd25519PeerId } from '@libp2p/peer-id-factory' diff --git a/test/registrar/registrar.spec.ts b/test/registrar/registrar.spec.ts index 259ae13e9d..4848c64ae8 100644 --- a/test/registrar/registrar.spec.ts +++ b/test/registrar/registrar.spec.ts @@ -6,21 +6,21 @@ import { MemoryDatastore } from 'datastore-core/memory' import { createTopology } from '@libp2p/topology' import { PersistentPeerStore } from '@libp2p/peer-store' import { DefaultRegistrar } from '../../src/registrar.js' -import { mockDuplex, mockMultiaddrConnection, mockUpgrader, mockConnection } from '@libp2p/interface-compliance-tests/mocks' +import { mockDuplex, mockMultiaddrConnection, mockUpgrader, mockConnection } from '@libp2p/interface-mocks' import { createPeerId, createNode } from '../utils/creators/peer.js' import { createBaseOptions } from '../utils/base-options.browser.js' -import type { Registrar } from '@libp2p/interfaces/registrar' -import type { PeerId } from '@libp2p/interfaces/peer-id' -import { Components } from '@libp2p/interfaces/components' +import type { Registrar } from '@libp2p/interface-registrar' +import type { PeerId } from '@libp2p/interface-peer-id' +import { Components } from '@libp2p/components' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { CustomEvent } from '@libp2p/interfaces/events' -import type { Connection } from '@libp2p/interfaces/connection' +import type { Connection } from '@libp2p/interface-connection' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' import { Plaintext } from '../../src/insecure/index.js' import { WebSockets } from '@libp2p/websockets' import { Mplex } from '@libp2p/mplex' -import type { PeerProtocolsChangeData } from '@libp2p/interfaces/peer-store' +import type { PeerProtocolsChangeData } from '@libp2p/interface-peer-store' const protocol = '/test/1.0.0' @@ -238,12 +238,12 @@ describe('registrar', () => { const echoHandler = () => {} await libp2p.handle(['/echo/1.0.0', '/echo/1.0.1'], echoHandler) - expect(registrar.getHandler('/echo/1.0.0')).to.equal(echoHandler) - expect(registrar.getHandler('/echo/1.0.1')).to.equal(echoHandler) + expect(registrar.getHandler('/echo/1.0.0')).to.have.property('handler', echoHandler) + expect(registrar.getHandler('/echo/1.0.1')).to.have.property('handler', echoHandler) await libp2p.unhandle(['/echo/1.0.0']) expect(registrar.getProtocols()).to.not.have.any.keys(['/echo/1.0.0']) - expect(registrar.getHandler('/echo/1.0.1')).to.equal(echoHandler) + expect(registrar.getHandler('/echo/1.0.1')).to.have.property('handler', echoHandler) }) }) }) diff --git a/test/transports/transport-manager.node.ts b/test/transports/transport-manager.node.ts index ce0f57e938..b5fbcf7c8d 100644 --- a/test/transports/transport-manager.node.ts +++ b/test/transports/transport-manager.node.ts @@ -8,13 +8,13 @@ import { PersistentPeerStore } from '@libp2p/peer-store' import { PeerRecord } from '@libp2p/peer-record' import { TCP } from '@libp2p/tcp' import { Multiaddr } from '@multiformats/multiaddr' -import { mockUpgrader } from '@libp2p/interface-compliance-tests/mocks' +import { mockUpgrader } from '@libp2p/interface-mocks' import sinon from 'sinon' import Peers from '../fixtures/peers.js' import pWaitFor from 'p-wait-for' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' import { createFromJSON } from '@libp2p/peer-id-factory' -import { Components } from '@libp2p/interfaces/components' +import { Components } from '@libp2p/components' import { PeerRecordUpdater } from '../../src/peer-record-updater.js' const addrs = [ diff --git a/test/transports/transport-manager.spec.ts b/test/transports/transport-manager.spec.ts index 059b5c1007..8ca53aa96e 100644 --- a/test/transports/transport-manager.spec.ts +++ b/test/transports/transport-manager.spec.ts @@ -8,13 +8,13 @@ import * as filters from '@libp2p/websockets/filters' import { NOISE } from '@chainsafe/libp2p-noise' import { DefaultAddressManager } from '../../src/address-manager/index.js' import { DefaultTransportManager, FaultTolerance } from '../../src/transport-manager.js' -import { mockUpgrader } from '@libp2p/interface-compliance-tests/mocks' +import { mockUpgrader } from '@libp2p/interface-mocks' import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' import { codes as ErrorCodes } from '../../src/errors.js' import Peers from '../fixtures/peers.js' -import { Components } from '@libp2p/interfaces/components' +import { Components } from '@libp2p/components' import { createEd25519PeerId, createFromJSON } from '@libp2p/peer-id-factory' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') diff --git a/test/upgrading/upgrader.spec.ts b/test/upgrading/upgrader.spec.ts index 26e3169959..216343d513 100644 --- a/test/upgrading/upgrader.spec.ts +++ b/test/upgrading/upgrader.spec.ts @@ -14,16 +14,16 @@ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import swarmKey from '../fixtures/swarm.key.js' import { DefaultUpgrader } from '../../src/upgrader.js' import { codes } from '../../src/errors.js' -import { mockConnectionGater, mockMultiaddrConnPair, mockRegistrar, mockStream } from '@libp2p/interface-compliance-tests/mocks' +import { mockConnectionGater, mockMultiaddrConnPair, mockRegistrar, mockStream } from '@libp2p/interface-mocks' import Peers from '../fixtures/peers.js' -import type { Upgrader } from '@libp2p/interfaces/transport' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { Upgrader } from '@libp2p/interface-transport' +import type { PeerId } from '@libp2p/interface-peer-id' import { createFromJSON } from '@libp2p/peer-id-factory' -import { Components } from '@libp2p/interfaces/components' +import { Components } from '@libp2p/components' import { Plaintext } from '../../src/insecure/index.js' -import type { ConnectionEncrypter, SecuredConnection } from '@libp2p/interfaces/connection-encrypter' -import type { StreamMuxer, StreamMuxerFactory, StreamMuxerInit } from '@libp2p/interfaces/stream-muxer' -import type { Stream } from '@libp2p/interfaces/connection' +import type { ConnectionEncrypter, SecuredConnection } from '@libp2p/interface-connection-encrypter' +import type { StreamMuxer, StreamMuxerFactory, StreamMuxerInit } from '@libp2p/interface-stream-muxer' +import type { Stream } from '@libp2p/interface-connection' import pDefer from 'p-defer' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' import { pEvent } from 'p-event' @@ -248,7 +248,7 @@ describe('Upgrader', () => { class OtherMuxerFactory implements StreamMuxerFactory { protocol = '/muxer-local' - createStreamMuxer (components: Components, init?: StreamMuxerInit): StreamMuxer { + createStreamMuxer (init?: StreamMuxerInit): StreamMuxer { return new OtherMuxer() } } diff --git a/test/utils/creators/peer.ts b/test/utils/creators/peer.ts index e010c0e694..85569e5b76 100644 --- a/test/utils/creators/peer.ts +++ b/test/utils/creators/peer.ts @@ -4,7 +4,7 @@ import { createBaseOptions } from '../base-options.browser.js' import { createEd25519PeerId, createFromJSON, createRSAPeerId } from '@libp2p/peer-id-factory' import { createLibp2pNode, Libp2pNode } from '../../../src/libp2p.js' import type { AddressesConfig, Libp2pOptions } from '../../../src/index.js' -import type { PeerId } from '@libp2p/interfaces/peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') From 53717296468ef17fdc3e0dda9d5908b15d2772a1 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 16 Jun 2022 08:37:58 +0100 Subject: [PATCH 379/447] fix: specify max stream args separately (#1254) --- src/registrar.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/registrar.ts b/src/registrar.ts index 41d1a194d7..5cc8a7e710 100644 --- a/src/registrar.ts +++ b/src/registrar.ts @@ -2,6 +2,7 @@ import { logger } from '@libp2p/logger' import errCode from 'err-code' import { codes } from './errors.js' import { isTopology, StreamHandlerOptions, StreamHandlerRecord } from '@libp2p/interface-registrar' +import merge from 'merge-options' import type { Registrar, StreamHandler, Topology } from '@libp2p/interface-registrar' import type { PeerProtocolsChangeData } from '@libp2p/interface-peer-store' import type { Connection } from '@libp2p/interface-connection' @@ -9,6 +10,9 @@ import type { Components } from '@libp2p/components' const log = logger('libp2p:registrar') +const DEFAULT_MAX_INCOMING_STREAMS = 1 +const DEFAULT_MAX_OUTGOING_STREAMS = 1 + /** * Responsible for notifying registered protocols of events in the network. */ @@ -63,11 +67,16 @@ export class DefaultRegistrar implements Registrar { /** * Registers the `handler` for each protocol */ - async handle (protocol: string, handler: StreamHandler, options: StreamHandlerOptions = { maxConcurrentStreams: 1 }): Promise { + async handle (protocol: string, handler: StreamHandler, opts?: StreamHandlerOptions): Promise { if (this.handlers.has(protocol)) { throw errCode(new Error(`Handler already registered for protocol ${protocol}`), codes.ERR_PROTOCOL_HANDLER_ALREADY_REGISTERED) } + const options = merge({ + maxIncomingStreams: DEFAULT_MAX_INCOMING_STREAMS, + maxOutgoingStreams: DEFAULT_MAX_OUTGOING_STREAMS + }, opts) + this.handlers.set(protocol, { handler, options From de30c2cec79d1e9d758cbcddc11d315b17843343 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 17 Jun 2022 14:46:31 +0100 Subject: [PATCH 380/447] feat!: limit protocol streams per-connection (#1255) * feat: limit protocol streams per-connection Uses the `maxInboundStreams` and `maxOutboundStreams` of the `registrar.handle` opts to limit the number of concurrent streams open on each connection on a per-protocol basis. Both values default to 1 so some tuning will be necessary to set appropriate values for some protocols. * chore: make error codes consistent * chore: fix up examples --- .gitignore | 2 +- doc/API.md | 8 +- examples/chat/src/dialer.js | 2 +- examples/connection-encryption/1.js | 2 +- examples/delegated-routing/package.json | 8 +- examples/echo/src/dialer.js | 2 +- examples/libp2p-in-the-browser/package.json | 6 +- examples/package.json | 2 +- examples/pnet/index.js | 2 +- examples/protocol-and-stream-muxing/1.js | 4 +- examples/protocol-and-stream-muxing/2.js | 11 +- examples/protocol-and-stream-muxing/3.js | 4 +- examples/protocol-and-stream-muxing/README.md | 20 +-- examples/transports/2.js | 2 +- examples/transports/3.js | 4 +- examples/transports/4.js | 2 +- examples/transports/README.md | 6 +- examples/webrtc-direct/dialer.js | 2 +- examples/webrtc-direct/listener.js | 2 +- examples/webrtc-direct/package.json | 2 +- package.json | 32 ++-- src/circuit/circuit/hop.ts | 4 +- src/circuit/circuit/stop.ts | 2 +- src/config.ts | 14 +- src/errors.ts | 5 +- src/fetch/index.ts | 133 ++++++++++------ src/identify/index.ts | 17 ++- src/index.ts | 8 +- src/libp2p.ts | 6 +- src/ping/index.ts | 11 +- src/registrar.ts | 12 +- src/upgrader.ts | 88 +++++++++-- test/content-routing/dht/operation.node.ts | 4 +- test/dialing/direct.node.ts | 14 +- test/dialing/direct.spec.ts | 4 +- test/fetch/index.spec.ts | 12 +- test/identify/index.spec.ts | 17 ++- test/identify/push.spec.ts | 14 +- test/metrics/index.node.ts | 4 +- test/ping/index.spec.ts | 12 +- test/ping/ping.node.ts | 7 +- test/relay/relay.node.ts | 4 +- test/upgrading/upgrader.spec.ts | 144 +++++++++++++++++- 43 files changed, 476 insertions(+), 185 deletions(-) diff --git a/.gitignore b/.gitignore index 69f5439f9c..7e4d369a6d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ test/repo-tests* logs *.log -coverage +.coverage .nyc_output # Runtime data diff --git a/doc/API.md b/doc/API.md index d8ba8fd83e..c26a8c71f2 100644 --- a/doc/API.md +++ b/doc/API.md @@ -389,7 +389,7 @@ await libp2p.hangUp(remotePeerId) Sets up [multistream-select routing](https://github.com/multiformats/multistream-select) of protocols to their application handlers. Whenever a stream is opened on one of the provided protocols, the handler will be called. `handle` must be called in order to register a handler and support for a given protocol. This also informs other peers of the protocols you support. -`libp2p.handle(protocols, handler)` +`libp2p.handle(protocols, handler, options)` In the event of a new handler for the same protocol being added, the first one is discarded. @@ -399,6 +399,7 @@ In the event of a new handler for the same protocol being added, the first one i |------|------|-------------| | protocols | `Array|string` | protocols to register | | handler | `function({ connection:*, stream:*, protocol:string })` | handler to call | +| options | `StreamHandlerOptions` | Options including protocol stream limits | #### Example @@ -409,7 +410,10 @@ const handler = ({ connection, stream, protocol }) => { // use stream or connection according to the needs } -libp2p.handle('/echo/1.0.0', handler) +libp2p.handle('/echo/1.0.0', handler, { + maxInboundStreams: 5, + maxOutboundStreams: 5 +}) ``` ### unhandle diff --git a/examples/chat/src/dialer.js b/examples/chat/src/dialer.js index e9d59262e5..8f37e7551b 100644 --- a/examples/chat/src/dialer.js +++ b/examples/chat/src/dialer.js @@ -32,7 +32,7 @@ async function run () { // Dial to the remote peer (the "listener") const listenerMa = new Multiaddr(`/ip4/127.0.0.1/tcp/10333/p2p/${idListener.toString()}`) - const { stream } = await nodeDialer.dialProtocol(listenerMa, '/chat/1.0.0') + const stream = await nodeDialer.dialProtocol(listenerMa, '/chat/1.0.0') console.log('Dialer dialed to listener on protocol: /chat/1.0.0') console.log('Type a message and see what happens') diff --git a/examples/connection-encryption/1.js b/examples/connection-encryption/1.js index eafb4ff7e7..0e774fcb99 100644 --- a/examples/connection-encryption/1.js +++ b/examples/connection-encryption/1.js @@ -40,7 +40,7 @@ const createNode = async () => { ) }) - const { stream } = await node1.dialProtocol(node2.peerId, '/a-protocol') + const stream = await node1.dialProtocol(node2.peerId, '/a-protocol') await pipe( [uint8ArrayFromString('This information is sent out encrypted to the other peer')], diff --git a/examples/delegated-routing/package.json b/examples/delegated-routing/package.json index 399cc0afbb..3892af938f 100644 --- a/examples/delegated-routing/package.json +++ b/examples/delegated-routing/package.json @@ -8,10 +8,10 @@ "libp2p": "../../", "@libp2p/delegated-content-routing": "^2.0.1", "@libp2p/delegated-peer-routing": "^2.0.1", - "@libp2p/kad-dht": "^2.0.0", - "@libp2p/mplex": "^2.0.0", - "@libp2p/webrtc-star": "^2.0.0", - "@libp2p/websockets": "^2.0.0", + "@libp2p/kad-dht": "^3.0.0", + "@libp2p/mplex": "^3.0.0", + "@libp2p/webrtc-star": "^2.0.1", + "@libp2p/websockets": "^3.0.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "5.0.0" diff --git a/examples/echo/src/dialer.js b/examples/echo/src/dialer.js index 74a6508798..435a9ea220 100644 --- a/examples/echo/src/dialer.js +++ b/examples/echo/src/dialer.js @@ -37,7 +37,7 @@ async function run() { // Dial the listener node console.log('Dialing to peer:', listenerMultiaddr) - const { stream } = await dialerNode.dialProtocol(listenerMultiaddr, '/echo/1.0.0') + const stream = await dialerNode.dialProtocol(listenerMultiaddr, '/echo/1.0.0') console.log('nodeA dialed to nodeB on protocol: /echo/1.0.0') diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json index 6dbc839662..4736108cae 100644 --- a/examples/libp2p-in-the-browser/package.json +++ b/examples/libp2p-in-the-browser/package.json @@ -11,9 +11,9 @@ "dependencies": { "@chainsafe/libp2p-noise": "^6.2.0", "@libp2p/bootstrap": "^2.0.0", - "@libp2p/mplex": "^2.0.0", - "@libp2p/webrtc-star": "^2.0.0", - "@libp2p/websockets": "^2.0.0", + "@libp2p/mplex": "^3.0.0", + "@libp2p/webrtc-star": "^2.0.1", + "@libp2p/websockets": "^3.0.0", "libp2p": "../../" }, "devDependencies": { diff --git a/examples/package.json b/examples/package.json index 70984696c5..a699ca30c0 100644 --- a/examples/package.json +++ b/examples/package.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@libp2p/pubsub-peer-discovery": "^6.0.0", - "@libp2p/floodsub": "^2.0.0", + "@libp2p/floodsub": "^3.0.0", "@nodeutils/defaults-deep": "^1.1.0", "execa": "^2.1.0", "fs-extra": "^8.1.0", diff --git a/examples/pnet/index.js b/examples/pnet/index.js index 59adbaa88f..fa8268df84 100644 --- a/examples/pnet/index.js +++ b/examples/pnet/index.js @@ -43,7 +43,7 @@ generateKey(otherSwarmKey) ) }) - const { stream } = await node1.dialProtocol(node2.peerId, '/private') + const stream = await node1.dialProtocol(node2.peerId, '/private') await pipe( [uint8ArrayFromString('This message is sent on a private network')], diff --git a/examples/protocol-and-stream-muxing/1.js b/examples/protocol-and-stream-muxing/1.js index 364bffdbc7..f1ab2f20df 100644 --- a/examples/protocol-and-stream-muxing/1.js +++ b/examples/protocol-and-stream-muxing/1.js @@ -60,14 +60,14 @@ const createNode = async () => { }) */ - const { stream } = await node1.dialProtocol(node2.peerId, ['/your-protocol']) + const stream = await node1.dialProtocol(node2.peerId, ['/your-protocol']) await pipe( [uint8ArrayFromString('my own protocol, wow!')], stream ) /* - const { stream } = node1.dialProtocol(node2.peerId, ['/another-protocol/1.0.0']) + const stream = node1.dialProtocol(node2.peerId, ['/another-protocol/1.0.0']) await pipe( ['my own protocol, wow!'], diff --git a/examples/protocol-and-stream-muxing/2.js b/examples/protocol-and-stream-muxing/2.js index 44b0b1ebb3..2605938d20 100644 --- a/examples/protocol-and-stream-muxing/2.js +++ b/examples/protocol-and-stream-muxing/2.js @@ -38,22 +38,25 @@ const createNode = async () => { console.log(`from: ${protocol}, msg: ${uint8ArrayToString(msg)}`) } } - ) + ).finally(() => { + // clean up resources + stream.close() + }) }) - const { stream: stream1 } = await node1.dialProtocol(node2.peerId, ['/a']) + const stream1 = await node1.dialProtocol(node2.peerId, ['/a']) await pipe( [uint8ArrayFromString('protocol (a)')], stream1 ) - const { stream: stream2 } = await node1.dialProtocol(node2.peerId, ['/b']) + const stream2 = await node1.dialProtocol(node2.peerId, ['/b']) await pipe( [uint8ArrayFromString('protocol (b)')], stream2 ) - const { stream: stream3 } = await node1.dialProtocol(node2.peerId, ['/b']) + const stream3 = await node1.dialProtocol(node2.peerId, ['/b']) await pipe( [uint8ArrayFromString('another stream on protocol (b)')], stream3 diff --git a/examples/protocol-and-stream-muxing/3.js b/examples/protocol-and-stream-muxing/3.js index a368743509..af63bdea8e 100644 --- a/examples/protocol-and-stream-muxing/3.js +++ b/examples/protocol-and-stream-muxing/3.js @@ -54,13 +54,13 @@ const createNode = async () => { ) }) - const { stream: stream1 } = await node1.dialProtocol(node2.peerId, ['/node-2']) + const stream1 = await node1.dialProtocol(node2.peerId, ['/node-2']) await pipe( [uint8ArrayFromString('from 1 to 2')], stream1 ) - const { stream: stream2 } = await node2.dialProtocol(node1.peerId, ['/node-1']) + const stream2 = await node2.dialProtocol(node1.peerId, ['/node-1']) await pipe( [uint8ArrayFromString('from 2 to 1')], stream2 diff --git a/examples/protocol-and-stream-muxing/README.md b/examples/protocol-and-stream-muxing/README.md index cb34a65dba..3e76b3966b 100644 --- a/examples/protocol-and-stream-muxing/README.md +++ b/examples/protocol-and-stream-muxing/README.md @@ -40,7 +40,7 @@ node2.handle('/your-protocol', ({ stream }) => { After the protocol is _handled_, now we can dial to it. ```JavaScript -const { stream } = await node1.dialProtocol(node2.peerId, ['/your-protocol']) +const stream = await node1.dialProtocol(node2.peerId, ['/your-protocol']) await pipe( ['my own protocol, wow!'], @@ -62,7 +62,7 @@ node2.handle('/another-protocol/1.0.1', ({ stream }) => { ) }) // ... -const { stream } = await node1.dialProtocol(node2.peerId, ['/another-protocol/1.0.0']) +const stream = await node1.dialProtocol(node2.peerId, ['/another-protocol/1.0.0']) await pipe( ['my own protocol, wow!'], @@ -133,19 +133,19 @@ node2.handle(['/a', '/b'], ({ protocol, stream }) => { ) }) -const { stream } = await node1.dialProtocol(node2.peerId, ['/a']) +const stream = await node1.dialProtocol(node2.peerId, ['/a']) await pipe( ['protocol (a)'], stream ) -const { stream: stream2 } = await node1.dialProtocol(node2.peerId, ['/b']) +const stream2 = await node1.dialProtocol(node2.peerId, ['/b']) await pipe( ['protocol (b)'], stream2 ) -const { stream: stream3 } = await node1.dialProtocol(node2.peerId, ['/b']) +const stream3 = await node1.dialProtocol(node2.peerId, ['/b']) await pipe( ['another stream on protocol (b)'], stream3 @@ -167,7 +167,7 @@ There is one last trick on _protocol and stream multiplexing_ that libp2p uses t With the aid of both mechanisms, we can reuse an incomming connection to dial streams out too, this is specially useful when you are behind tricky NAT, firewalls or if you are running in a browser, where you can't have listening addrs, but you can dial out. By dialing out, you enable other peers to talk with you in Protocols that they want, simply by opening a new multiplexed stream. -You can see this working on example [3.js](./3.js). +You can see this working on example [3.js](./3.js). As we've seen earlier, we can create our node with this createNode function. ```js @@ -229,14 +229,14 @@ node2.handle('/node-2', ({ stream }) => { }) // Dialing node2 from node1 -const { stream: stream1 } = await node1.dialProtocol(node2.peerId, ['/node-2']) +const stream1 = await node1.dialProtocol(node2.peerId, ['/node-2']) await pipe( ['from 1 to 2'], stream1 ) // Dialing node1 from node2 -const { stream: stream2 } = await node2.dialProtocol(node1.peerId, ['/node-1']) +const stream2 = await node2.dialProtocol(node1.peerId, ['/node-1']) await pipe( ['from 2 to 1'], stream2 @@ -256,14 +256,14 @@ So, we have successfully set up a bidirectional connection with protocol muxing. The code below will result into an error as `the dial address is not valid`. ```js // Dialing from node2 to node1 -const { stream: stream2 } = await node2.dialProtocol(node1.peerId, ['/node-1']) +const stream2 = await node2.dialProtocol(node1.peerId, ['/node-1']) await pipe( ['from 2 to 1'], stream2 ) // Dialing from node1 to node2 -const { stream: stream1 } = await node1.dialProtocol(node2.peerId, ['/node-2']) +const stream1 = await node1.dialProtocol(node2.peerId, ['/node-2']) await pipe( ['from 1 to 2'], stream1 diff --git a/examples/transports/2.js b/examples/transports/2.js index d157da1191..9dee88780c 100644 --- a/examples/transports/2.js +++ b/examples/transports/2.js @@ -48,7 +48,7 @@ function printAddrs (node, number) { }) await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs()) - const { stream } = await node1.dialProtocol(node2.peerId, '/print') + const stream = await node1.dialProtocol(node2.peerId, '/print') await pipe( ['Hello', ' ', 'p2p', ' ', 'world', '!'].map(str => uint8ArrayFromString(str)), diff --git a/examples/transports/3.js b/examples/transports/3.js index b1a233f867..0bc9fa7097 100644 --- a/examples/transports/3.js +++ b/examples/transports/3.js @@ -63,14 +63,14 @@ function print ({ stream }) { await node3.peerStore.addressBook.set(node1.peerId, node1.getMultiaddrs()) // node 1 (TCP) dials to node 2 (TCP+WebSockets) - const { stream } = await node1.dialProtocol(node2.peerId, '/print') + const stream = await node1.dialProtocol(node2.peerId, '/print') await pipe( [uint8ArrayFromString('node 1 dialed to node 2 successfully')], stream ) // node 2 (TCP+WebSockets) dials to node 2 (WebSockets) - const { stream: stream2 } = await node2.dialProtocol(node3.peerId, '/print') + const stream2 = await node2.dialProtocol(node3.peerId, '/print') await pipe( [uint8ArrayFromString('node 2 dialed to node 3 successfully')], stream2 diff --git a/examples/transports/4.js b/examples/transports/4.js index 389217aaf4..0e13c569a2 100644 --- a/examples/transports/4.js +++ b/examples/transports/4.js @@ -78,7 +78,7 @@ function print ({ stream }) { const targetAddr = node1.getMultiaddrs()[0]; // node 2 (Secure WebSockets) dials to node 1 (Secure Websockets) - const { stream } = await node2.dialProtocol(targetAddr, '/print') + const stream = await node2.dialProtocol(targetAddr, '/print') await pipe( [uint8ArrayFromString('node 2 dialed to node 1 successfully')], stream diff --git a/examples/transports/README.md b/examples/transports/README.md index 1d3f5d4fd8..ca951834b9 100644 --- a/examples/transports/README.md +++ b/examples/transports/README.md @@ -139,7 +139,7 @@ Then add, }) await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) - const { stream } = await node1.dialProtocol(node2.peerId, '/print') + const stream = await node1.dialProtocol(node2.peerId, '/print') await pipe( ['Hello', ' ', 'p2p', ' ', 'world', '!'], @@ -225,14 +225,14 @@ await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) await node3.peerStore.addressBook.set(node1.peerId, node1.multiaddrs) // node 1 (TCP) dials to node 2 (TCP+WebSockets) -const { stream } = await node1.dialProtocol(node2.peerId, '/print') +const stream = await node1.dialProtocol(node2.peerId, '/print') await pipe( ['node 1 dialed to node 2 successfully'], stream ) // node 2 (TCP+WebSockets) dials to node 2 (WebSockets) -const { stream: stream2 } = await node2.dialProtocol(node3.peerId, '/print') +const stream2 = await node2.dialProtocol(node3.peerId, '/print') await pipe( ['node 2 dialed to node 3 successfully'], stream2 diff --git a/examples/webrtc-direct/dialer.js b/examples/webrtc-direct/dialer.js index d2357dfa8c..1663054cfb 100644 --- a/examples/webrtc-direct/dialer.js +++ b/examples/webrtc-direct/dialer.js @@ -1,5 +1,5 @@ import { createLibp2p } from 'libp2p' -import { WebRTCDirect } from '@achingbrain/webrtc-direct' +import { WebRTCDirect } from '@libp2p/webrtc-direct' import { Mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' import { Bootstrap } from '@libp2p/bootstrap' diff --git a/examples/webrtc-direct/listener.js b/examples/webrtc-direct/listener.js index e27cb66b61..8a822d0b4e 100644 --- a/examples/webrtc-direct/listener.js +++ b/examples/webrtc-direct/listener.js @@ -1,5 +1,5 @@ import { createLibp2p } from 'libp2p' -import { WebRTCDirect } from '@achingbrain/webrtc-direct' +import { WebRTCDirect } from '@libp2p/webrtc-direct' import { Mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' import { createFromJSON } from '@libp2p/peer-id-factory' diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index c1d34ffee2..57d33ee0a5 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -12,7 +12,7 @@ "@libp2p/webrtc-direct": "^2.0.0", "@chainsafe/libp2p-noise": "^6.2.0", "@libp2p/bootstrap": "^2.0.0", - "@libp2p/mplex": "^2.0.0", + "@libp2p/mplex": "^3.0.0", "libp2p": "../../", "wrtc": "^0.4.7" }, diff --git a/package.json b/package.json index 997908acdc..37e572e957 100644 --- a/package.json +++ b/package.json @@ -97,11 +97,11 @@ }, "dependencies": { "@achingbrain/nat-port-mapper": "^1.0.3", - "@libp2p/components": "^1.0.0", - "@libp2p/connection": "^3.0.0", + "@libp2p/components": "^2.0.0", + "@libp2p/connection": "^4.0.0", "@libp2p/crypto": "^1.0.0", "@libp2p/interface-address-manager": "^1.0.1", - "@libp2p/interface-connection": "^1.0.1", + "@libp2p/interface-connection": "^2.0.0", "@libp2p/interface-connection-encrypter": "^1.0.2", "@libp2p/interface-content-routing": "^1.0.1", "@libp2p/interface-dht": "^1.0.0", @@ -111,18 +111,18 @@ "@libp2p/interface-peer-info": "^1.0.1", "@libp2p/interface-peer-routing": "^1.0.0", "@libp2p/interface-peer-store": "^1.0.0", - "@libp2p/interface-pubsub": "^1.0.1", - "@libp2p/interface-registrar": "^1.0.0", + "@libp2p/interface-pubsub": "^1.0.3", + "@libp2p/interface-registrar": "^2.0.0", "@libp2p/interface-stream-muxer": "^1.0.1", "@libp2p/interface-transport": "^1.0.0", "@libp2p/interfaces": "^3.0.2", "@libp2p/logger": "^2.0.0", - "@libp2p/multistream-select": "^2.0.0", + "@libp2p/multistream-select": "^2.0.1", "@libp2p/peer-collections": "^1.0.2", "@libp2p/peer-id": "^1.1.10", "@libp2p/peer-id-factory": "^1.0.9", "@libp2p/peer-record": "^2.0.0", - "@libp2p/peer-store": "^2.0.0", + "@libp2p/peer-store": "^3.0.0", "@libp2p/tracked-map": "^1.0.5", "@libp2p/utils": "^2.0.0", "@multiformats/mafmt": "^11.0.2", @@ -171,24 +171,24 @@ "@libp2p/daemon-server": "^2.0.0", "@libp2p/delegated-content-routing": "^2.0.0", "@libp2p/delegated-peer-routing": "^2.0.0", - "@libp2p/floodsub": "^2.0.0", + "@libp2p/floodsub": "^3.0.0", "@libp2p/interface-compliance-tests": "^3.0.1", "@libp2p/interface-connection-encrypter-compliance-tests": "^1.0.0", - "@libp2p/interface-mocks": "^1.0.1", + "@libp2p/interface-mocks": "^2.0.0", "@libp2p/interop": "^2.0.0", - "@libp2p/kad-dht": "^2.0.0", + "@libp2p/kad-dht": "^3.0.0", "@libp2p/mdns": "^2.0.0", - "@libp2p/mplex": "^2.0.0", - "@libp2p/pubsub": "^2.0.0", - "@libp2p/tcp": "^2.0.0", - "@libp2p/topology": "^2.0.0", + "@libp2p/mplex": "^3.0.0", + "@libp2p/pubsub": "^3.0.1", + "@libp2p/tcp": "^3.0.0", + "@libp2p/topology": "^3.0.0", "@libp2p/webrtc-star": "^2.0.0", - "@libp2p/websockets": "^2.0.0", + "@libp2p/websockets": "^3.0.0", "@types/node-forge": "^1.0.0", "@types/p-fifo": "^1.0.0", "@types/varint": "^6.0.0", "@types/xsalsa20": "^1.1.0", - "aegir": "^37.0.9", + "aegir": "^37.3.0", "cborg": "^1.8.1", "delay": "^5.0.0", "execa": "^6.1.0", diff --git a/src/circuit/circuit/hop.ts b/src/circuit/circuit/hop.ts index 4f5474d150..d8bbe03ed6 100644 --- a/src/circuit/circuit/hop.ts +++ b/src/circuit/circuit/hop.ts @@ -134,7 +134,7 @@ export async function hop (options: HopConfig): Promise> { } = options // Create a new stream to the relay - const { stream } = await connection.newStream(RELAY_CODEC) + const stream = await connection.newStream(RELAY_CODEC) // Send the HOP request const streamHandler = new StreamHandler({ stream }) streamHandler.write(request) @@ -169,7 +169,7 @@ export async function canHop (options: CanHopOptions) { } = options // Create a new stream to the relay - const { stream } = await connection.newStream(RELAY_CODEC) + const stream = await connection.newStream(RELAY_CODEC) // Send the HOP request const streamHandler = new StreamHandler({ stream }) diff --git a/src/circuit/circuit/stop.ts b/src/circuit/circuit/stop.ts index c953ce56bb..75c97f66f1 100644 --- a/src/circuit/circuit/stop.ts +++ b/src/circuit/circuit/stop.ts @@ -56,7 +56,7 @@ export async function stop (options: StopOptions) { request } = options - const { stream } = await connection.newStream([RELAY_CODEC]) + const stream = await connection.newStream([RELAY_CODEC]) log('starting stop request to %p', connection.remotePeer) const streamHandler = new StreamHandler({ stream }) diff --git a/src/config.ts b/src/config.ts index 432d7e9a2b..f261914213 100644 --- a/src/config.ts +++ b/src/config.ts @@ -79,13 +79,21 @@ const DefaultConfig: Partial = { host: { agentVersion: AGENT_VERSION }, - timeout: 30000 + timeout: 30000, + maxInboundStreams: 1, + maxOutboundStreams: 1, + maxPushIncomingStreams: 1, + maxPushOutgoingStreams: 1 }, ping: { - protocolPrefix: 'ipfs' + protocolPrefix: 'ipfs', + maxInboundStreams: 1, + maxOutboundStreams: 1 }, fetch: { - protocolPrefix: 'libp2p' + protocolPrefix: 'libp2p', + maxInboundStreams: 1, + maxOutboundStreams: 1 } } diff --git a/src/errors.ts b/src/errors.ts index ec02ed6002..50e2441d1a 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -70,5 +70,8 @@ export enum codes { ERR_NOT_IMPLEMENTED = 'ERR_NOT_IMPLEMENTED', ERR_WRONG_PING_ACK = 'ERR_WRONG_PING_ACK', ERR_INVALID_RECORD = 'ERR_INVALID_RECORD', - ERR_ALREADY_SUCCEEDED = 'ERR_ALREADY_SUCCEEDED' + ERR_ALREADY_SUCCEEDED = 'ERR_ALREADY_SUCCEEDED', + ERR_NO_HANDLER_FOR_PROTOCOL = 'ERR_NO_HANDLER_FOR_PROTOCOL', + ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS = 'ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS', + ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS = 'ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS' } diff --git a/src/fetch/index.ts b/src/fetch/index.ts index 218b182fe5..04d7efc817 100644 --- a/src/fetch/index.ts +++ b/src/fetch/index.ts @@ -3,7 +3,6 @@ import errCode from 'err-code' import { codes } from '../errors.js' import * as lp from 'it-length-prefixed' import { FetchRequest, FetchResponse } from './pb/proto.js' -import { handshake } from 'it-handshake' import { PROTOCOL_NAME, PROTOCOL_VERSION } from './constants.js' import type { PeerId } from '@libp2p/interface-peer-id' import type { Startable } from '@libp2p/interfaces/startable' @@ -13,11 +12,15 @@ import type { Components } from '@libp2p/components' import type { AbortOptions } from '@libp2p/interfaces' import type { Duplex } from 'it-stream-types' import { abortableDuplex } from 'abortable-iterator' +import { pipe } from 'it-pipe' +import first from 'it-first' const log = logger('libp2p:fetch') export interface FetchServiceInit { protocolPrefix: string + maxInboundStreams: number + maxOutboundStreams: number } export interface HandleMessageOptions { @@ -40,6 +43,7 @@ export class FetchService implements Startable { private readonly components: Components private readonly lookupFunctions: Map private started: boolean + private readonly init: FetchServiceInit constructor (components: Components, init: FetchServiceInit) { this.started = false @@ -47,13 +51,21 @@ export class FetchService implements Startable { this.protocol = `/${init.protocolPrefix ?? 'libp2p'}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}` this.lookupFunctions = new Map() // Maps key prefix to value lookup function this.handleMessage = this.handleMessage.bind(this) + this.init = init } async start () { await this.components.getRegistrar().handle(this.protocol, (data) => { - void this.handleMessage(data).catch(err => { - log.error(err) - }) + void this.handleMessage(data) + .catch(err => { + log.error(err) + }) + .finally(() => { + data.stream.close() + }) + }, { + maxInboundStreams: this.init.maxInboundStreams, + maxOutboundStreams: this.init.maxOutboundStreams }) this.started = true } @@ -74,7 +86,7 @@ export class FetchService implements Startable { log('dialing %s to %p', this.protocol, peer) const connection = await this.components.getConnectionManager().openConnection(peer, options) - const { stream } = await connection.newStream([this.protocol], options) + const stream = await connection.newStream([this.protocol], options) let source: Duplex = stream // make stream abortable if AbortSignal passed @@ -82,28 +94,42 @@ export class FetchService implements Startable { source = abortableDuplex(stream, options.signal) } - const shake = handshake(source) - - // send message - shake.write(lp.encode.single(FetchRequest.encode({ identifier: key })).slice()) - - // read response - // @ts-expect-error fromReader returns a Source which has no .next method - const response = FetchResponse.decode((await lp.decode.fromReader(shake.reader).next()).value.slice()) - switch (response.status) { - case (FetchResponse.StatusCode.OK): { - return response.data - } - case (FetchResponse.StatusCode.NOT_FOUND): { - return null - } - case (FetchResponse.StatusCode.ERROR): { - const errmsg = (new TextDecoder()).decode(response.data) - throw errCode(new Error('Error in fetch protocol response: ' + errmsg), codes.ERR_INVALID_PARAMETERS) - } - default: { - throw errCode(new Error('Unknown response status'), codes.ERR_INVALID_MESSAGE) - } + try { + const result = await pipe( + [FetchRequest.encode({ identifier: key })], + lp.encode(), + source, + lp.decode(), + async function (source) { + const buf = await first(source) + + if (buf == null) { + throw errCode(new Error('No data received'), codes.ERR_INVALID_MESSAGE) + } + + const response = FetchResponse.decode(buf) + + switch (response.status) { + case (FetchResponse.StatusCode.OK): { + return response.data + } + case (FetchResponse.StatusCode.NOT_FOUND): { + return null + } + case (FetchResponse.StatusCode.ERROR): { + const errmsg = (new TextDecoder()).decode(response.data) + throw errCode(new Error('Error in fetch protocol response: ' + errmsg), codes.ERR_INVALID_PARAMETERS) + } + default: { + throw errCode(new Error('Unknown response status'), codes.ERR_INVALID_MESSAGE) + } + } + } + ) + + return result ?? null + } finally { + stream.close() } } @@ -114,25 +140,40 @@ export class FetchService implements Startable { */ async handleMessage (data: IncomingStreamData) { const { stream } = data - const shake = handshake(stream) - // @ts-expect-error fromReader returns a Source which has no .next method - const request = FetchRequest.decode((await lp.decode.fromReader(shake.reader).next()).value.slice()) - - let response: FetchResponse - const lookup = this._getLookupFunction(request.identifier) - if (lookup != null) { - const data = await lookup(request.identifier) - if (data != null) { - response = { status: FetchResponse.StatusCode.OK, data } - } else { - response = { status: FetchResponse.StatusCode.NOT_FOUND, data: new Uint8Array(0) } - } - } else { - const errmsg = (new TextEncoder()).encode('No lookup function registered for key: ' + request.identifier) - response = { status: FetchResponse.StatusCode.ERROR, data: errmsg } - } - - shake.write(lp.encode.single(FetchResponse.encode(response)).slice()) + const self = this + + await pipe( + stream, + lp.decode(), + async function * (source) { + const buf = await first(source) + + if (buf == null) { + throw errCode(new Error('No data received'), codes.ERR_INVALID_MESSAGE) + } + + // for await (const buf of source) { + const request = FetchRequest.decode(buf) + + let response: FetchResponse + const lookup = self._getLookupFunction(request.identifier) + if (lookup != null) { + const data = await lookup(request.identifier) + if (data != null) { + response = { status: FetchResponse.StatusCode.OK, data } + } else { + response = { status: FetchResponse.StatusCode.NOT_FOUND, data: new Uint8Array(0) } + } + } else { + const errmsg = (new TextEncoder()).encode('No lookup function registered for key: ' + request.identifier) + response = { status: FetchResponse.StatusCode.ERROR, data: errmsg } + } + + yield FetchResponse.encode(response) + }, + lp.encode(), + stream + ) } /** diff --git a/src/identify/index.ts b/src/identify/index.ts index b3fe0e9e33..e921308db5 100644 --- a/src/identify/index.ts +++ b/src/identify/index.ts @@ -60,6 +60,12 @@ export interface IdentifyServiceInit { * Identify responses larger than this in bytes will be rejected (default: 8192) */ maxIdentifyMessageSize?: number + + maxInboundStreams: number + maxOutboundStreams: number + + maxPushIncomingStreams: number + maxPushOutgoingStreams: number } export class IdentifyService implements Startable { @@ -129,11 +135,17 @@ export class IdentifyService implements Startable { void this._handleIdentify(data).catch(err => { log.error(err) }) + }, { + maxInboundStreams: this.init.maxInboundStreams, + maxOutboundStreams: this.init.maxOutboundStreams }) await this.components.getRegistrar().handle(this.identifyPushProtocolStr, (data) => { void this._handlePush(data).catch(err => { log.error(err) }) + }, { + maxInboundStreams: this.init.maxPushIncomingStreams, + maxOutboundStreams: this.init.maxPushOutgoingStreams }) this.started = true @@ -159,10 +171,9 @@ export class IdentifyService implements Startable { let stream: Stream | undefined try { - const data = await connection.newStream([this.identifyPushProtocolStr], { + stream = await connection.newStream([this.identifyPushProtocolStr], { signal: timeoutController.signal }) - stream = data.stream // make stream abortable const source: Duplex = abortableDuplex(stream, timeoutController.signal) @@ -218,7 +229,7 @@ export class IdentifyService implements Startable { } async _identify (connection: Connection, options: AbortOptions = {}): Promise { - const { stream } = await connection.newStream([this.identifyProtocolStr], options) + const stream = await connection.newStream([this.identifyProtocolStr], options) let source: Duplex = stream let timeoutController let signal = options.signal diff --git a/src/index.ts b/src/index.ts index 62a300e64d..b7aa62878e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,14 +11,14 @@ import type { PeerStore, PeerStoreInit } from '@libp2p/interface-peer-store' import type { PeerId } from '@libp2p/interface-peer-id' import type { AutoRelayConfig, RelayAdvertiseConfig } from './circuit/index.js' import type { PeerDiscovery } from '@libp2p/interface-peer-discovery' -import type { Connection, ConnectionGater, ConnectionProtector, ProtocolStream } from '@libp2p/interface-connection' +import type { Connection, ConnectionGater, ConnectionProtector, Stream } from '@libp2p/interface-connection' import type { Transport } from '@libp2p/interface-transport' import type { StreamMuxerFactory } from '@libp2p/interface-stream-muxer' import type { ConnectionEncrypter } from '@libp2p/interface-connection-encrypter' import type { PeerRouting } from '@libp2p/interface-peer-routing' import type { ContentRouting } from '@libp2p/interface-content-routing' import type { PubSub } from '@libp2p/interface-pubsub' -import type { Registrar, StreamHandler } from '@libp2p/interface-registrar' +import type { Registrar, StreamHandler, StreamHandlerOptions } from '@libp2p/interface-registrar' import type { ConnectionManager } from '@libp2p/interface-connection-manager' import type { Metrics, MetricsInit } from '@libp2p/interface-metrics' import type { PeerInfo } from '@libp2p/interface-peer-info' @@ -177,7 +177,7 @@ export interface Libp2p extends Startable, EventEmitter { * If successful, the known metadata of the peer will be added to the nodes `peerStore`, * and the `MuxedStream` will be returned together with the successful negotiated protocol. */ - dialProtocol: (peer: PeerId | Multiaddr, protocols: string | string[], options?: AbortOptions) => Promise + dialProtocol: (peer: PeerId | Multiaddr, protocols: string | string[], options?: AbortOptions) => Promise /** * Disconnects all connections to the given `peer` @@ -187,7 +187,7 @@ export interface Libp2p extends Startable, EventEmitter { /** * Registers the `handler` for each protocol */ - handle: (protocol: string | string[], handler: StreamHandler) => Promise + handle: (protocol: string | string[], handler: StreamHandler, options?: StreamHandlerOptions) => Promise /** * Removes the handler for each protocol. The protocol diff --git a/src/libp2p.ts b/src/libp2p.ts index b7ff6b4a0f..32676c48d4 100644 --- a/src/libp2p.ts +++ b/src/libp2p.ts @@ -33,7 +33,7 @@ import type { Connection } from '@libp2p/interface-connection' import type { PeerRouting } from '@libp2p/interface-peer-routing' import type { ContentRouting } from '@libp2p/interface-content-routing' import type { PubSub } from '@libp2p/interface-pubsub' -import type { Registrar, StreamHandler } from '@libp2p/interface-registrar' +import type { Registrar, StreamHandler, StreamHandlerOptions } from '@libp2p/interface-registrar' import type { ConnectionManager } from '@libp2p/interface-connection-manager' import type { PeerInfo } from '@libp2p/interface-peer-info' import type { Libp2p, Libp2pEvents, Libp2pInit, Libp2pOptions } from './index.js' @@ -490,14 +490,14 @@ export class Libp2pNode extends EventEmitter implements Libp2p { return await this.pingService.ping(id, options) } - async handle (protocols: string | string[], handler: StreamHandler): Promise { + async handle (protocols: string | string[], handler: StreamHandler, options?: StreamHandlerOptions): Promise { if (!Array.isArray(protocols)) { protocols = [protocols] } await Promise.all( protocols.map(async protocol => { - await this.components.getRegistrar().handle(protocol, handler) + await this.components.getRegistrar().handle(protocol, handler, options) }) ) } diff --git a/src/ping/index.ts b/src/ping/index.ts index c7766ed339..f755bd51bc 100644 --- a/src/ping/index.ts +++ b/src/ping/index.ts @@ -18,21 +18,28 @@ const log = logger('libp2p:ping') export interface PingServiceInit { protocolPrefix: string + maxInboundStreams: number + maxOutboundStreams: number } export class PingService implements Startable { public readonly protocol: string private readonly components: Components private started: boolean + private readonly init: PingServiceInit constructor (components: Components, init: PingServiceInit) { this.components = components this.started = false this.protocol = `/${init.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}` + this.init = init } async start () { - await this.components.getRegistrar().handle(this.protocol, this.handleMessage) + await this.components.getRegistrar().handle(this.protocol, this.handleMessage, { + maxInboundStreams: this.init.maxInboundStreams, + maxOutboundStreams: this.init.maxOutboundStreams + }) this.started = true } @@ -67,7 +74,7 @@ export class PingService implements Startable { log('dialing %s to %p', this.protocol, peer) const connection = await this.components.getConnectionManager().openConnection(peer, options) - const { stream } = await connection.newStream([this.protocol], options) + const stream = await connection.newStream([this.protocol], options) const start = Date.now() const data = randomBytes(PING_LENGTH) diff --git a/src/registrar.ts b/src/registrar.ts index 5cc8a7e710..d9304d2574 100644 --- a/src/registrar.ts +++ b/src/registrar.ts @@ -10,8 +10,8 @@ import type { Components } from '@libp2p/components' const log = logger('libp2p:registrar') -const DEFAULT_MAX_INCOMING_STREAMS = 1 -const DEFAULT_MAX_OUTGOING_STREAMS = 1 +export const DEFAULT_MAX_INBOUND_STREAMS = 1 +export const DEFAULT_MAX_OUTBOUND_STREAMS = 1 /** * Responsible for notifying registered protocols of events in the network. @@ -46,7 +46,7 @@ export class DefaultRegistrar implements Registrar { const handler = this.handlers.get(protocol) if (handler == null) { - throw new Error(`No handler registered for protocol ${protocol}`) + throw errCode(new Error(`No handler registered for protocol ${protocol}`), codes.ERR_NO_HANDLER_FOR_PROTOCOL) } return handler @@ -72,9 +72,9 @@ export class DefaultRegistrar implements Registrar { throw errCode(new Error(`Handler already registered for protocol ${protocol}`), codes.ERR_PROTOCOL_HANDLER_ALREADY_REGISTERED) } - const options = merge({ - maxIncomingStreams: DEFAULT_MAX_INCOMING_STREAMS, - maxOutgoingStreams: DEFAULT_MAX_OUTGOING_STREAMS + const options = merge.bind({ ignoreUndefined: true })({ + maxInboundStreams: DEFAULT_MAX_INBOUND_STREAMS, + maxOutboundStreams: DEFAULT_MAX_OUTBOUND_STREAMS }, opts) this.handlers.set(protocol, { diff --git a/src/upgrader.ts b/src/upgrader.ts index 961fd10da6..cb344dff6e 100644 --- a/src/upgrader.ts +++ b/src/upgrader.ts @@ -8,7 +8,7 @@ import { codes } from './errors.js' import { createConnection } from '@libp2p/connection' import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events' import { peerIdFromString } from '@libp2p/peer-id' -import type { MultiaddrConnection, Connection, ProtocolStream, Stream } from '@libp2p/interface-connection' +import type { MultiaddrConnection, Connection, Stream } from '@libp2p/interface-connection' import type { ConnectionEncrypter, SecuredConnection } from '@libp2p/interface-connection-encrypter' import type { StreamMuxer, StreamMuxerFactory } from '@libp2p/interface-stream-muxer' import type { PeerId } from '@libp2p/interface-peer-id' @@ -16,6 +16,8 @@ import type { Upgrader, UpgraderEvents } from '@libp2p/interface-transport' import type { Duplex } from 'it-stream-types' import { Components, isInitializable } from '@libp2p/components' import type { AbortOptions } from '@libp2p/interfaces' +import type { Registrar } from '@libp2p/interface-registrar' +import { DEFAULT_MAX_INBOUND_STREAMS, DEFAULT_MAX_OUTBOUND_STREAMS } from './registrar.js' const log = logger('libp2p:upgrader') @@ -43,6 +45,46 @@ export interface UpgraderInit { muxers: StreamMuxerFactory[] } +function findIncomingStreamLimit (protocol: string, registrar: Registrar) { + try { + const { options } = registrar.getHandler(protocol) + + return options.maxInboundStreams + } catch (err: any) { + if (err.code !== codes.ERR_NO_HANDLER_FOR_PROTOCOL) { + throw err + } + } + + return DEFAULT_MAX_INBOUND_STREAMS +} + +function findOutgoingStreamLimit (protocol: string, registrar: Registrar) { + try { + const { options } = registrar.getHandler(protocol) + + return options.maxOutboundStreams + } catch (err: any) { + if (err.code !== codes.ERR_NO_HANDLER_FOR_PROTOCOL) { + throw err + } + } + + return DEFAULT_MAX_OUTBOUND_STREAMS +} + +function countStreams (protocol: string, direction: 'inbound' | 'outbound', connection: Connection) { + let streamCount = 0 + + connection.streams.forEach(stream => { + if (stream.stat.direction === direction && stream.stat.protocol === protocol) { + streamCount++ + } + }) + + return streamCount +} + export class DefaultUpgrader extends EventEmitter implements Upgrader { private readonly components: Components private readonly connectionEncryption: Map @@ -267,7 +309,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg } = opts let muxer: StreamMuxer | undefined - let newStream: ((multicodecs: string[], options?: AbortOptions) => Promise) | undefined + let newStream: ((multicodecs: string[], options?: AbortOptions) => Promise) | undefined let connection: Connection // eslint-disable-line prefer-const if (muxerFactory != null) { @@ -296,13 +338,22 @@ export class DefaultUpgrader extends EventEmitter implements Upg return } - connection.addStream(muxedStream, { protocol }) + const incomingLimit = findIncomingStreamLimit(protocol, this.components.getRegistrar()) + const streamCount = countStreams(protocol, 'inbound', connection) + + if (streamCount === incomingLimit) { + throw errCode(new Error('Too many incoming protocol streams'), codes.ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS) + } + + muxedStream.stat.protocol = protocol + + connection.addStream(muxedStream) this._onStream({ connection, stream: { ...muxedStream, ...stream }, protocol }) }) .catch(err => { log.error(err) - if (muxedStream.timeline.close == null) { + if (muxedStream.stat.timeline.close == null) { muxedStream.close() } }) @@ -317,7 +368,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg muxer.init(this.components) } - newStream = async (protocols: string[], options: AbortOptions = {}): Promise => { + newStream = async (protocols: string[], options: AbortOptions = {}): Promise => { if (muxer == null) { throw errCode(new Error('Stream is not multiplexed'), codes.ERR_MUXER_UNAVAILABLE) } @@ -334,11 +385,27 @@ export class DefaultUpgrader extends EventEmitter implements Upg stream = metrics.trackStream({ stream, remotePeer, protocol }) } - return { stream: { ...muxedStream, ...stream }, protocol } + const outgoingLimit = findOutgoingStreamLimit(protocol, this.components.getRegistrar()) + const streamCount = countStreams(protocol, 'outbound', connection) + + if (streamCount === outgoingLimit) { + throw errCode(new Error('Too many outgoing protocol streams'), codes.ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS) + } + + muxedStream.stat.protocol = protocol + + return { + ...muxedStream, + ...stream, + stat: { + ...muxedStream.stat, + protocol + } + } } catch (err: any) { log.error('could not create new stream', err) - if (muxedStream.timeline.close == null) { + if (muxedStream.stat.timeline.close == null) { muxedStream.close() } @@ -402,9 +469,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg await maConn.close() // Ensure remaining streams are closed if (muxer != null) { - await Promise.all(muxer.streams.map(async stream => { - await stream.close() - })) + muxer.streams.forEach(s => s.close()) } } }) @@ -422,7 +487,8 @@ export class DefaultUpgrader extends EventEmitter implements Upg _onStream (opts: OnStreamOptions): void { const { connection, stream, protocol } = opts const { handler } = this.components.getRegistrar().getHandler(protocol) - handler({ connection, stream, protocol }) + + handler({ connection, stream }) } /** diff --git a/test/content-routing/dht/operation.node.ts b/test/content-routing/dht/operation.node.ts index 412ae7659f..82b03e6784 100644 --- a/test/content-routing/dht/operation.node.ts +++ b/test/content-routing/dht/operation.node.ts @@ -73,9 +73,9 @@ describe('DHT subsystem operates correctly', () => { }) it('should get notified of connected peers on dial', async () => { - const connection = await libp2p.dialProtocol(remAddr, subsystemMulticodecs) + const stream = await libp2p.dialProtocol(remAddr, subsystemMulticodecs) - expect(connection).to.exist() + expect(stream).to.exist() return await Promise.all([ pWaitFor(() => libp2p.dht.lan.routingTable.size === 1), diff --git a/test/dialing/direct.node.ts b/test/dialing/direct.node.ts index c5e3b4f5dd..428aba065a 100644 --- a/test/dialing/direct.node.ts +++ b/test/dialing/direct.node.ts @@ -307,9 +307,9 @@ describe('libp2p.dialer (direct, TCP)', () => { const connection = await libp2p.dial(remoteAddr) expect(connection).to.exist() - const { stream, protocol } = await connection.newStream(['/echo/1.0.0']) + const stream = await connection.newStream(['/echo/1.0.0']) expect(stream).to.exist() - expect(protocol).to.equal('/echo/1.0.0') + expect(stream).to.have.nested.property('stat.protocol', '/echo/1.0.0') expect(dialerDialSpy.callCount).to.be.greaterThan(0) await connection.close() }) @@ -336,9 +336,9 @@ describe('libp2p.dialer (direct, TCP)', () => { const connection = await libp2p.dial(remotePeerId) expect(connection).to.exist() - const { stream, protocol } = await connection.newStream('/echo/1.0.0') + const stream = await connection.newStream('/echo/1.0.0') expect(stream).to.exist() - expect(protocol).to.equal('/echo/1.0.0') + expect(stream).to.have.nested.property('stat.protocol', '/echo/1.0.0') await connection.close() expect(dialerDialSpy.callCount).to.be.greaterThan(0) }) @@ -377,7 +377,7 @@ describe('libp2p.dialer (direct, TCP)', () => { const connection = await libp2p.dial(remotePeerId) // Create local to remote streams - const { stream } = await connection.newStream('/echo/1.0.0') + const stream = await connection.newStream('/echo/1.0.0') await connection.newStream('/stream-count/3') await libp2p.dialProtocol(remoteLibp2p.peerId, '/stream-count/4') @@ -487,9 +487,9 @@ describe('libp2p.dialer (direct, TCP)', () => { const connection = await libp2p.dial(remoteAddr) expect(connection).to.exist() - const { stream, protocol } = await connection.newStream('/echo/1.0.0') + const stream = await connection.newStream('/echo/1.0.0') expect(stream).to.exist() - expect(protocol).to.equal('/echo/1.0.0') + expect(stream).to.have.nested.property('stat.protocol', '/echo/1.0.0') await connection.close() expect(protectorProtectSpy.callCount).to.equal(1) }) diff --git a/test/dialing/direct.spec.ts b/test/dialing/direct.spec.ts index e4112c97cc..1af0e7c6a5 100644 --- a/test/dialing/direct.spec.ts +++ b/test/dialing/direct.spec.ts @@ -427,9 +427,9 @@ describe('libp2p.dialer (direct, WebSockets)', () => { const connection = await libp2p.dial(MULTIADDRS_WEBSOCKETS[0]) expect(connection).to.exist() - const { stream, protocol } = await connection.newStream('/echo/1.0.0') + const stream = await connection.newStream('/echo/1.0.0') expect(stream).to.exist() - expect(protocol).to.equal('/echo/1.0.0') + expect(stream).to.have.nested.property('stat.protocol', '/echo/1.0.0') await connection.close() expect(dialerDialSpy.callCount).to.be.at.least(1) expect(addressBookAddSpy.callCount).to.be.at.least(1) diff --git a/test/fetch/index.spec.ts b/test/fetch/index.spec.ts index f776d77727..eadf4f2991 100644 --- a/test/fetch/index.spec.ts +++ b/test/fetch/index.spec.ts @@ -2,7 +2,7 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' -import { FetchService } from '../../src/fetch/index.js' +import { FetchService, FetchServiceInit } from '../../src/fetch/index.js' import Peers from '../fixtures/peers.js' import { mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-mocks' import { createFromJSON } from '@libp2p/peer-id-factory' @@ -14,8 +14,10 @@ import { TimeoutController } from 'timeout-abort-controller' import delay from 'delay' import { pipe } from 'it-pipe' -const defaultInit = { - protocolPrefix: 'ipfs' +const defaultInit: FetchServiceInit = { + protocolPrefix: 'ipfs', + maxInboundStreams: 1, + maxOutboundStreams: 1 } async function createComponents (index: number) { @@ -127,7 +129,7 @@ describe('fetch', () => { // should have closed stream expect(newStreamSpy).to.have.property('callCount', 1) - const { stream } = await newStreamSpy.getCall(0).returnValue - expect(stream).to.have.nested.property('timeline.close') + const stream = await newStreamSpy.getCall(0).returnValue + expect(stream).to.have.nested.property('stat.timeline.close') }) }) diff --git a/test/identify/index.spec.ts b/test/identify/index.spec.ts index b59bbe0796..3d6623a0f0 100644 --- a/test/identify/index.spec.ts +++ b/test/identify/index.spec.ts @@ -6,7 +6,7 @@ import sinon from 'sinon' import { Multiaddr } from '@multiformats/multiaddr' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { codes } from '../../src/errors.js' -import { IdentifyService, Message } from '../../src/identify/index.js' +import { IdentifyService, IdentifyServiceInit, Message } from '../../src/identify/index.js' import Peers from '../fixtures/peers.js' import { PersistentPeerStore } from '@libp2p/peer-store' import { DefaultAddressManager } from '../../src/address-manager/index.js' @@ -32,11 +32,15 @@ import pDefer from 'p-defer' const listenMaddrs = [new Multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] -const defaultInit = { +const defaultInit: IdentifyServiceInit = { protocolPrefix: 'ipfs', host: { agentVersion: 'v1.0.0' - } + }, + maxInboundStreams: 1, + maxOutboundStreams: 1, + maxPushIncomingStreams: 1, + maxPushOutgoingStreams: 1 } const protocols = [MULTICODEC_IDENTIFY, MULTICODEC_IDENTIFY_PUSH] @@ -130,6 +134,7 @@ describe('identify', () => { it('should be able to identify another peer with no certified peer records support', async () => { const agentVersion = 'js-libp2p/5.0.0' const localIdentify = new IdentifyService(localComponents, { + ...defaultInit, protocolPrefix: 'ipfs', host: { agentVersion: agentVersion @@ -137,6 +142,7 @@ describe('identify', () => { }) await start(localIdentify) const remoteIdentify = new IdentifyService(remoteComponents, { + ...defaultInit, protocolPrefix: 'ipfs', host: { agentVersion: agentVersion @@ -209,6 +215,7 @@ describe('identify', () => { it('should store own host data and protocol version into metadataBook on start', async () => { const agentVersion = 'js-project/1.0.0' const localIdentify = new IdentifyService(localComponents, { + ...defaultInit, protocolPrefix: 'ipfs', host: { agentVersion @@ -270,8 +277,8 @@ describe('identify', () => { // should have closed stream expect(newStreamSpy).to.have.property('callCount', 1) - const { stream } = await newStreamSpy.getCall(0).returnValue - expect(stream).to.have.nested.property('timeline.close') + const stream = await newStreamSpy.getCall(0).returnValue + expect(stream).to.have.nested.property('stat.timeline.close') }) it('should limit incoming identify message sizes', async () => { diff --git a/test/identify/push.spec.ts b/test/identify/push.spec.ts index e9b5a7dfbd..9dbf95c8ba 100644 --- a/test/identify/push.spec.ts +++ b/test/identify/push.spec.ts @@ -3,7 +3,7 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' import { Multiaddr } from '@multiformats/multiaddr' -import { IdentifyService } from '../../src/identify/index.js' +import { IdentifyService, IdentifyServiceInit } from '../../src/identify/index.js' import Peers from '../fixtures/peers.js' import { PersistentPeerStore } from '@libp2p/peer-store' import { DefaultAddressManager } from '../../src/address-manager/index.js' @@ -27,11 +27,15 @@ import { start, stop } from '@libp2p/interfaces/startable' const listenMaddrs = [new Multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] -const defaultInit = { +const defaultInit: IdentifyServiceInit = { protocolPrefix: 'ipfs', host: { agentVersion: 'v1.0.0' - } + }, + maxInboundStreams: 1, + maxOutboundStreams: 1, + maxPushIncomingStreams: 1, + maxPushOutgoingStreams: 1 } const protocols = [MULTICODEC_IDENTIFY, MULTICODEC_IDENTIFY_PUSH] @@ -213,8 +217,8 @@ describe('identify (push)', () => { // should have closed stream expect(newStreamSpy).to.have.property('callCount', 1) - const { stream } = await newStreamSpy.getCall(0).returnValue - expect(stream).to.have.nested.property('timeline.close') + const stream = await newStreamSpy.getCall(0).returnValue + expect(stream).to.have.nested.property('stat.timeline.close') // method should have returned before the remote handler completes as we timed // out so we ignore the return value diff --git a/test/metrics/index.node.ts b/test/metrics/index.node.ts index 1be7558d2f..89296b1e7b 100644 --- a/test/metrics/index.node.ts +++ b/test/metrics/index.node.ts @@ -91,7 +91,7 @@ describe('libp2p.metrics', () => { }) const connection = await libp2p.dial(remoteLibp2p.peerId) - const { stream } = await connection.newStream('/echo/1.0.0') + const stream = await connection.newStream('/echo/1.0.0') const bytes = randomBytes(512) const result = await pipe( @@ -156,7 +156,7 @@ describe('libp2p.metrics', () => { }) const connection = await libp2p.dial(remoteLibp2p.peerId) - const { stream } = await connection.newStream('/echo/1.0.0') + const stream = await connection.newStream('/echo/1.0.0') const bytes = randomBytes(512) await pipe( diff --git a/test/ping/index.spec.ts b/test/ping/index.spec.ts index b8be7e6425..1acf352eec 100644 --- a/test/ping/index.spec.ts +++ b/test/ping/index.spec.ts @@ -2,7 +2,7 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' -import { PingService } from '../../src/ping/index.js' +import { PingService, PingServiceInit } from '../../src/ping/index.js' import Peers from '../fixtures/peers.js' import { mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-mocks' import { createFromJSON } from '@libp2p/peer-id-factory' @@ -14,8 +14,10 @@ import { TimeoutController } from 'timeout-abort-controller' import delay from 'delay' import { pipe } from 'it-pipe' -const defaultInit = { - protocolPrefix: 'ipfs' +const defaultInit: PingServiceInit = { + protocolPrefix: 'ipfs', + maxInboundStreams: 1, + maxOutboundStreams: 1 } async function createComponents (index: number) { @@ -116,7 +118,7 @@ describe('ping', () => { // should have closed stream expect(newStreamSpy).to.have.property('callCount', 1) - const { stream } = await newStreamSpy.getCall(0).returnValue - expect(stream).to.have.nested.property('timeline.close') + const stream = await newStreamSpy.getCall(0).returnValue + expect(stream).to.have.nested.property('stat.timeline.close') }) }) diff --git a/test/ping/ping.node.ts b/test/ping/ping.node.ts index 8b64ffba60..5ee6a13a38 100644 --- a/test/ping/ping.node.ts +++ b/test/ping/ping.node.ts @@ -1,7 +1,6 @@ /* eslint-env mocha */ import { expect } from 'aegir/chai' -import pTimes from 'p-times' import { pipe } from 'it-pipe' import { createNode, populateAddressBooks } from '../utils/creators/peer.js' import { createBaseOptions } from '../utils/base-options.js' @@ -41,7 +40,11 @@ describe('ping', () => { }) it('ping several times for getting an average', async () => { - const latencies = await pTimes(5, async () => await nodes[1].ping(nodes[0].peerId)) + const latencies = [] + + for (let i = 0; i < 5; i++) { + latencies.push(await nodes[1].ping(nodes[0].peerId)) + } const averageLatency = latencies.reduce((p, c) => p + c, 0) / latencies.length expect(averageLatency).to.be.a('Number') diff --git a/test/relay/relay.node.ts b/test/relay/relay.node.ts index 8b33ff07bb..60e1bf5f60 100644 --- a/test/relay/relay.node.ts +++ b/test/relay/relay.node.ts @@ -80,7 +80,7 @@ describe('Dialing (via relay, TCP)', () => { expect(connection.remotePeer.toBytes()).to.eql(dstLibp2p.peerId.toBytes()) expect(connection.remoteAddr).to.eql(dialAddr) - const { stream: echoStream } = await connection.newStream('/echo/1.0.0') + const echoStream = await connection.newStream('/echo/1.0.0') const input = uint8ArrayFromString('hello') const [output] = await pipe( @@ -156,7 +156,7 @@ describe('Dialing (via relay, TCP)', () => { // send an invalid relay message from the relay to the destination peer const connections = relayLibp2p.getConnections(dstLibp2p.peerId) - const { stream } = await connections[0].newStream(RELAY_CODEC) + const stream = await connections[0].newStream(RELAY_CODEC) const streamHandler = new StreamHandler({ stream }) streamHandler.write({ type: CircuitRelay.Type.STATUS diff --git a/test/upgrading/upgrader.spec.ts b/test/upgrading/upgrader.spec.ts index 216343d513..ac7806bac3 100644 --- a/test/upgrading/upgrader.spec.ts +++ b/test/upgrading/upgrader.spec.ts @@ -85,9 +85,15 @@ describe('Upgrader', () => { await localComponents.getRegistrar().handle('/echo/1.0.0', ({ stream }) => { void pipe(stream, stream) + }, { + maxInboundStreams: 10, + maxOutboundStreams: 10 }) await remoteComponents.getRegistrar().handle('/echo/1.0.0', ({ stream }) => { void pipe(stream, stream) + }, { + maxInboundStreams: 10, + maxOutboundStreams: 10 }) }) @@ -105,8 +111,8 @@ describe('Upgrader', () => { expect(connections).to.have.length(2) - const { stream, protocol } = await connections[0].newStream('/echo/1.0.0') - expect(protocol).to.equal('/echo/1.0.0') + const stream = await connections[0].newStream('/echo/1.0.0') + expect(stream).to.have.nested.property('stat.protocol', '/echo/1.0.0') const hello = uint8ArrayFromString('hello there!') const result = await pipe( @@ -175,8 +181,8 @@ describe('Upgrader', () => { expect(connections).to.have.length(2) - const { stream, protocol } = await connections[0].newStream('/echo/1.0.0') - expect(protocol).to.equal('/echo/1.0.0') + const stream = await connections[0].newStream('/echo/1.0.0') + expect(stream).to.have.nested.property('stat.protocol', '/echo/1.0.0') const hello = uint8ArrayFromString('hello there!') const result = await pipe( @@ -515,11 +521,11 @@ describe('libp2p.upgrader', () => { ]) const remoteLibp2pUpgraderOnStreamSpy = sinon.spy(remoteLibp2p.components.getUpgrader() as DefaultUpgrader, '_onStream') - const { stream } = await localConnection.newStream(['/echo/1.0.0']) - expect(stream).to.include.keys(['id', 'close', 'reset', 'timeline']) + const stream = await localConnection.newStream(['/echo/1.0.0']) + expect(stream).to.include.keys(['id', 'close', 'reset', 'stat']) const [arg0] = remoteLibp2pUpgraderOnStreamSpy.getCall(0).args - expect(arg0.stream).to.include.keys(['id', 'close', 'reset', 'timeline']) + expect(arg0.stream).to.include.keys(['id', 'close', 'reset', 'stat']) }) it('should emit connect and disconnect events', async () => { @@ -579,4 +585,128 @@ describe('libp2p.upgrader', () => { // @ts-expect-error detail is only on CustomEvent type expect(remotePeer.equals(event.detail.remotePeer)).to.equal(true) }) + + it('should limit the number of incoming streams that can be opened using a protocol', async () => { + const protocol = '/a-test-protocol/1.0.0' + const remotePeer = peers[1] + libp2p = await createLibp2pNode({ + peerId: peers[0], + transports: [ + new WebSockets() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + await libp2p.start() + + remoteLibp2p = await createLibp2pNode({ + peerId: remotePeer, + transports: [ + new WebSockets() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + await remoteLibp2p.start() + + const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) + + const [localToRemote] = await Promise.all([ + libp2p.components.getUpgrader().upgradeOutbound(outbound), + remoteLibp2p.components.getUpgrader().upgradeInbound(inbound) + ]) + + let streamCount = 0 + + await libp2p.handle(protocol, (data) => {}, { + maxInboundStreams: 10, + maxOutboundStreams: 10 + }) + + await remoteLibp2p.handle(protocol, (data) => { + streamCount++ + }, { + maxInboundStreams: 1, + maxOutboundStreams: 1 + }) + + expect(streamCount).to.equal(0) + + await localToRemote.newStream(protocol) + + expect(streamCount).to.equal(1) + + await expect(localToRemote.newStream(protocol)).to.eventually.be.rejected() + .with.property('code', 'ERR_UNDER_READ') + }) + + it('should limit the number of outgoing streams that can be opened using a protocol', async () => { + const protocol = '/a-test-protocol/1.0.0' + const remotePeer = peers[1] + libp2p = await createLibp2pNode({ + peerId: peers[0], + transports: [ + new WebSockets() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + await libp2p.start() + + remoteLibp2p = await createLibp2pNode({ + peerId: remotePeer, + transports: [ + new WebSockets() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + NOISE + ] + }) + await remoteLibp2p.start() + + const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) + + const [localToRemote] = await Promise.all([ + libp2p.components.getUpgrader().upgradeOutbound(outbound), + remoteLibp2p.components.getUpgrader().upgradeInbound(inbound) + ]) + + let streamCount = 0 + + await libp2p.handle(protocol, (data) => {}, { + maxInboundStreams: 1, + maxOutboundStreams: 1 + }) + + await remoteLibp2p.handle(protocol, (data) => { + streamCount++ + }, { + maxInboundStreams: 10, + maxOutboundStreams: 10 + }) + + expect(streamCount).to.equal(0) + + await localToRemote.newStream(protocol) + + expect(streamCount).to.equal(1) + + await expect(localToRemote.newStream(protocol)).to.eventually.be.rejected() + .with.property('code', codes.ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS) + }) }) From a5077cbc6b3ab8431475422f2330b08b641758cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jun 2022 13:16:32 +0100 Subject: [PATCH 381/447] chore(deps-dev): bump @chainsafe/libp2p-noise from 6.2.0 to 7.0.1 (#1272) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 37e572e957..dd62fcbc0b 100644 --- a/package.json +++ b/package.json @@ -165,7 +165,7 @@ "xsalsa20": "^1.1.0" }, "devDependencies": { - "@chainsafe/libp2p-noise": "^6.2.0", + "@chainsafe/libp2p-noise": "^7.0.1", "@libp2p/bootstrap": "^2.0.0", "@libp2p/daemon-client": "^2.0.0", "@libp2p/daemon-server": "^2.0.0", From 676cee2947f5017a67f46ad0fcddcfb17b76f4a4 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 24 Jun 2022 13:33:28 +0100 Subject: [PATCH 382/447] docs: update websockets import var (#1274) --- doc/CONFIGURATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index fd38e2711a..9ad4230f9d 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -247,7 +247,7 @@ import { GossipSub } from 'libp2p-gossipsub' const node = await createLibp2p({ transports: [ new TCP(), - new WS() + new WebSockets() ], streamMuxers: [new Mplex()], connectionEncryption: [new Noise()], From b270527c8fee08dd687d1d977e697ee0def596d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jun 2022 13:33:37 +0100 Subject: [PATCH 383/447] chore(deps-dev): bump @libp2p/webrtc-star from 2.0.1 to 3.0.0 (#1266) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dd62fcbc0b..89ebc2b601 100644 --- a/package.json +++ b/package.json @@ -182,7 +182,7 @@ "@libp2p/pubsub": "^3.0.1", "@libp2p/tcp": "^3.0.0", "@libp2p/topology": "^3.0.0", - "@libp2p/webrtc-star": "^2.0.0", + "@libp2p/webrtc-star": "^3.0.0", "@libp2p/websockets": "^3.0.0", "@types/node-forge": "^1.0.0", "@types/p-fifo": "^1.0.0", From ceb44f9e9804fdbee90aaa4732005a5d39494669 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 24 Jun 2022 16:28:56 +0100 Subject: [PATCH 384/447] docs: add build step to docs (#1275) Now that we need to build ts to js a build step is necessary. Add it to the instructions where we are telling people to clone the repo first. Refs: #1273 --- README.md | 1 + examples/auto-relay/README.md | 2 +- examples/chat/README.md | 2 +- examples/echo/README.md | 2 +- examples/libp2p-in-the-browser/README.md | 3 ++- examples/peer-and-content-routing/README.md | 2 +- examples/pnet/README.md | 2 +- examples/pubsub/README.md | 2 +- 8 files changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 028a459406..e48170726d 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,7 @@ You can find multiple examples on the [examples folder](./examples) that will gu > git clone https://github.com/libp2p/js-libp2p.git > cd js-libp2p > npm install +> npm run build ``` ### Tests diff --git a/examples/auto-relay/README.md b/examples/auto-relay/README.md index e03d493939..d547213189 100644 --- a/examples/auto-relay/README.md +++ b/examples/auto-relay/README.md @@ -5,7 +5,7 @@ While direct connections to nodes are preferable, it's not always possible to do ## 0. Setup the example -Before moving into the examples, you should run `npm install` on the top level `js-libp2p` folder, in order to install all the dependencies needed for this example. Once the install finishes, you should move into the example folder with `cd examples/auto-relay`. +Before moving into the examples, you should run `npm install` and `npm run build` on the top level `js-libp2p` folder, in order to install all the dependencies needed for this example. Once the install finishes, you should move into the example folder with `cd examples/auto-relay`. This example comes with 3 main files. A `relay.js` file to be used in the first step, a `listener.js` file to be used in the second step and a `dialer.js` file to be used on the third step. All of these scripts will run their own libp2p node, which will interact with the previous ones. All nodes must be running in order for you to proceed. diff --git a/examples/chat/README.md b/examples/chat/README.md index 87175dc7c9..02f5690629 100644 --- a/examples/chat/README.md +++ b/examples/chat/README.md @@ -3,7 +3,7 @@ This example creates a simple chat app in your terminal. ## Setup -1. Install the modules in the libp2p root directory, `npm install`. +1. Install the modules in the libp2p root directory, `npm install` and `npm run build`. 2. Open 2 terminal windows in the `./examples/chat/src` directory. ## Running diff --git a/examples/echo/README.md b/examples/echo/README.md index 495b83ab58..2cea61df40 100644 --- a/examples/echo/README.md +++ b/examples/echo/README.md @@ -3,7 +3,7 @@ This example performs a simple echo from the listener to the dialer. ## Setup -1. Install the modules from libp2p root, `npm install`. +1. Install the modules from libp2p root, `npm install` and `npm run build`. 2. Open 2 terminal windows in the `./src` directory. ## Running diff --git a/examples/libp2p-in-the-browser/README.md b/examples/libp2p-in-the-browser/README.md index e98a5ad69e..9c5513f2d6 100644 --- a/examples/libp2p-in-the-browser/README.md +++ b/examples/libp2p-in-the-browser/README.md @@ -6,11 +6,12 @@ This example leverages the [Parcel.js bundler](https://parceljs.org/) to compile In order to run the example: -- Install dependencey at the root of the js-libp2p repository (if not already done), +- Install dependencey at the root of the js-libp2p repository (if not already done), - then, install the dependencies from same directory as this README: ``` npm install +npm run build cd ./examples/libp2p-in-the-browser npm install ``` diff --git a/examples/peer-and-content-routing/README.md b/examples/peer-and-content-routing/README.md index ad523414ff..8e8ec3b516 100644 --- a/examples/peer-and-content-routing/README.md +++ b/examples/peer-and-content-routing/README.md @@ -8,7 +8,7 @@ Content Routing is the category of modules that offer a way to find where conten # 1. Using Peer Routing to find other peers -This example builds on top of the [Protocol and Stream Muxing](../protocol-and-stream-muxing). We need to install `libp2p-kad-dht`, go ahead and `npm install libp2p-kad-dht`. If you want to see the final version, open [1.js](./1.js). +This example builds on top of the [Protocol and Stream Muxing](../protocol-and-stream-muxing). We need to install `@libp2p/kad-dht`, go ahead and `npm install @libp2p/kad-dht`. If you want to see the final version, open [1.js](./1.js). First, let's update our config to support Peer Routing and Content Routing. diff --git a/examples/pnet/README.md b/examples/pnet/README.md index b14f8f4ab6..2f59721724 100644 --- a/examples/pnet/README.md +++ b/examples/pnet/README.md @@ -2,7 +2,7 @@ This example shows how to set up a private network of libp2p nodes. ## Setup -1. Install the modules in the libp2p root directory, `npm install`. +1. Install the modules in the libp2p root directory, `npm install` and `npm run build`. ## Run Running the example will cause two nodes with the same swarm key to be started and exchange basic information. diff --git a/examples/pubsub/README.md b/examples/pubsub/README.md index 0913e1fde0..1bf86c31c2 100644 --- a/examples/pubsub/README.md +++ b/examples/pubsub/README.md @@ -10,7 +10,7 @@ We've seen many interesting use cases appear with this, here are some highlights ## 0. Set up the example -Before moving into the examples, you should run `npm install` on the top level `js-libp2p` folder, in order to install all the dependencies needed for this example. In addition, you will need to install the example related dependencies by doing `cd examples && npm install`. Once the install finishes, you should move into the example folder with `cd pubsub`. +Before moving into the examples, you should run `npm install` and `npm run build` on the top level `js-libp2p` folder, in order to install all the dependencies needed for this example. In addition, you will need to install the example related dependencies by doing `cd examples && npm install`. Once the install finishes, you should move into the example folder with `cd pubsub`. ## 1. Setting up a simple PubSub network on top of libp2p From b1b2b216daf12caccd67503dfd7b296b191c5b83 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Mon, 27 Jun 2022 15:34:03 +0100 Subject: [PATCH 385/447] feat!: use tag values to choose which connections to close (#1276) Uses peer tag values to select low-value connections to prune when we have too many connections open. BREAKING CHANGE: `connectionManager.peerValue` has been removed, use `peerStore.tagPeer` instead --- doc/API.md | 105 +++++++++++++++++++------- doc/CONFIGURATION.md | 1 - doc/CONNECTION_MANAGER.md | 1 - package.json | 1 + src/connection-manager/index.ts | 92 +++++++++++----------- test/connection-manager/index.spec.ts | 27 +++---- 6 files changed, 135 insertions(+), 92 deletions(-) diff --git a/doc/API.md b/doc/API.md index c26a8c71f2..a4ae0830dd 100644 --- a/doc/API.md +++ b/doc/API.md @@ -46,6 +46,9 @@ * [`peerStore.delete`](#peerstoredelete) * [`peerStore.get`](#peerstoreget) * [`peerStore.peers`](#peerstorepeers) + * [`peerStore.tagPeer`](#peerstoretagpeer) + * [`peerStore.unTagPeer`](#peerstoreuntagpeer) + * [`peerStore.getTags`](#peerstoregettags) * [`pubsub.getSubscribers`](#pubsubgetsubscribers) * [`pubsub.getTopics`](#pubsubgettopics) * [`pubsub.publish`](#pubsubpublish) @@ -56,7 +59,6 @@ * [`pubsub.topicValidators.set`](#pubsubtopicvalidatorsset) * [`pubsub.topicValidators.delete`](#pubsubtopicvalidatorsdelete) * [`connectionManager.get`](#connectionmanagerget) - * [`connectionManager.setPeerValue`](#connectionmanagersetpeervalue) * [`connectionManager.size`](#connectionmanagersize) * [`keychain.createKey`](#keychaincreatekey) * [`keychain.renameKey`](#keychainrenamekey) @@ -1399,6 +1401,81 @@ for (let [peerIdString, peer] of peerStore.peers.entries()) { } ``` +### peerStore.tagPeer + +Tags a peer with the specified tag and optional value/expiry time + +`peerStore.tagPeer(peerId, tag, options)` + +#### Parameters + +| Name | Type | Description | +|------|------|-------------| +| peerId | `PeerId` | The peer to tag | +| tag | `string` | The name of the tag to add | +| options | `{ value?: number, ttl?: number }` | An optional value (1-100) and an optional ttl after which the tag will expire (ms) | + +#### Returns + +| Type | Description | +|------|-------------| +| `Promise` | Promise resolves once the tag is stored | + +#### Example + +```js +await peerStore.tagPeer(peerId, 'my-tag', { value: 100, ttl: Date.now() + 60000 }) +``` + +### peerStore.unTagPeer + +Remove the tag from the specified peer + +`peerStore.unTagPeer(peerId, tag)` + +#### Parameters + +| Name | Type | Description | +|------|------|-------------| +| peerId | `PeerId` | The peer to untag | +| tag | `string` | The name of the tag to remove | + +#### Returns + +| Type | Description | +|------|-------------| +| `Promise` | Promise resolves once the tag has been removed | + +#### Example + +```js +await peerStore.unTagPeer(peerId, 'my-tag') +``` + +### peerStore.getTags + +Remove the tag from the specified peer + +`peerStore.getTags(peerId)` + +#### Parameters + +| Name | Type | Description | +|------|------|-------------| +| peerId | `PeerId` | The peer to get the tags for | + +#### Returns + +| Type | Description | +|------|-------------| +| `Promise>` | The promise resolves to the list of tags for the passed peer | + +#### Example + +```js +await peerStore.getTags(peerId) +``` + ### pubsub.getSubscribers Gets a list of the peer-ids that are subscribed to one topic. @@ -1672,32 +1749,6 @@ Get a connection with a given peer, if it exists. libp2p.connectionManager.get(peerId) ``` -### connectionManager.setPeerValue - -Enables users to change the value of certain peers in a range of 0 to 1. Peers with the lowest values will have their Connections pruned first, if any Connection Manager limits are exceeded. See [./CONFIGURATION.md#configuring-connection-manager](./CONFIGURATION.md#configuring-connection-manager) for details on how to configure these limits. - -`libp2p.connectionManager.setPeerValue(peerId, value)` - -#### Parameters - -| Name | Type | Description | -|------|------|-------------| -| peerId | [`PeerId`][peer-id] | The peer to set the value for | -| value | `number` | The value of the peer from 0 to 1 | - -#### Returns - -| Type | Description | -|------|-------------| -| `void` | | - -#### Example - -```js -libp2p.connectionManager.setPeerValue(highPriorityPeerId, 1) -libp2p.connectionManager.setPeerValue(lowPriorityPeerId, 0) -``` - ### connectionManager.size Getter for obtaining the current number of open connections. diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 9ad4230f9d..e15c0816dc 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -556,7 +556,6 @@ const node = await createLibp2p({ maxConnections: Infinity, minConnections: 0, pollInterval: 2000, - defaultPeerValue: 1, // The below values will only be taken into account when Metrics are enabled maxData: Infinity, maxSentData: Infinity, diff --git a/doc/CONNECTION_MANAGER.md b/doc/CONNECTION_MANAGER.md index 8d0a3aaf47..f25e340a5c 100644 --- a/doc/CONNECTION_MANAGER.md +++ b/doc/CONNECTION_MANAGER.md @@ -17,4 +17,3 @@ The following is a list of available options for setting limits for the Connecti - `maxEventLoopDelay`: sets the maximum event loop delay (measured in milliseconds) this node is willing to endure before it starts disconnecting peers. Defaults to `Infinity`. - `pollInterval`: sets the poll interval (in milliseconds) for assessing the current state and determining if this peer needs to force a disconnect. Defaults to `2000` (2 seconds). - `movingAverageInterval`: the interval used to calculate moving averages (in milliseconds). Defaults to `60000` (1 minute). This must be an available interval configured in `Metrics` -- `defaultPeerValue`: number between 0 and 1. Defaults to 1. diff --git a/package.json b/package.json index 89ebc2b601..75843b2b6d 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "generate:proto:fetch": "protons ./src/fetch/pb/proto.proto", "generate:proto:identify": "protons ./src/identify/pb/message.proto", "generate:proto:plaintext": "protons ./src/insecure/pb/proto.proto", + "generate:proto:tags": "protons ./src/connection-manager/tags/tags.proto", "test": "aegir test", "test:node": "aegir test -t node -f \"./dist/test/**/*.{node,spec}.js\" --cov", "test:chrome": "aegir test -t browser -f \"./dist/test/**/*.spec.js\" --cov", diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index 1ec2e00b4a..79487226f5 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -18,6 +18,7 @@ import * as STATUS from '@libp2p/interface-connection/status' import { Dialer } from './dialer/index.js' import type { AddressSorter } from '@libp2p/interface-peer-store' import type { Resolver } from '@multiformats/multiaddr' +import { PeerMap } from '@libp2p/peer-collections' const log = logger('libp2p:connection-manager') @@ -30,13 +31,11 @@ const defaultOptions: Partial = { maxEventLoopDelay: Infinity, pollInterval: 2000, autoDialInterval: 10000, - movingAverageInterval: 60000, - defaultPeerValue: 0.5 + movingAverageInterval: 60000 } const METRICS_COMPONENT = 'connection-manager' const METRICS_PEER_CONNECTIONS = 'peer-connections' -const METRICS_PEER_VALUES = 'peer-values' export interface ConnectionManagerInit { /** @@ -79,11 +78,6 @@ export interface ConnectionManagerInit { */ movingAverageInterval?: number - /** - * The value of the peer - */ - defaultPeerValue?: number - /** * If true, try to connect to all discovered peers up to the connection manager limit */ @@ -138,7 +132,6 @@ export class DefaultConnectionManager extends EventEmitter - private readonly peerValues: Map private readonly connections: Map private started: boolean private timer?: ReturnType @@ -155,17 +148,6 @@ export class DefaultConnectionManager extends EventEmitter} - */ - this.peerValues = trackedMap({ - component: METRICS_COMPONENT, - metric: METRICS_PEER_VALUES, - metrics: this.components.getMetrics() - }) - /** * Map of connections per peer */ @@ -271,18 +253,6 @@ export class DefaultConnectionManager extends EventEmitter 1) { - throw new Error('value should be a number between 0 and 1') - } - - this.peerValues.set(peerId.toString(), value) - } - /** * Checks the libp2p metrics to determine if any values have exceeded * the configured maximums. @@ -340,10 +310,6 @@ export class DefaultConnectionManager extends EventEmitter('peer:disconnect', { detail: connection })) this.components.getMetrics()?.onPeerDisconnected(connection.remotePeer) @@ -475,7 +440,7 @@ export class DefaultConnectionManager extends EventEmitter limit) { - log('%s: limit exceeded: %p, %d, pruning %d connection(s)', this.components.getPeerId(), name, value, toPrune) + log('%s: limit exceeded: %p, %d/%d, pruning %d connection(s)', this.components.getPeerId(), name, value, limit, toPrune) await this._maybePruneConnections(toPrune) } } @@ -491,22 +456,49 @@ export class DefaultConnectionManager extends EventEmitter a[1] - b[1]))) - log.trace('sorted peer values: %j', peerValues) + const peerValues = new PeerMap() - const toClose = [] + // work out peer values + for (const connection of connections) { + const remotePeer = connection.remotePeer - for (const [peerId] of peerValues) { - log('too many connections open - closing a connection to %p', peerId) + if (peerValues.has(remotePeer)) { + continue + } - for (const connection of connections) { - if (connection.remotePeer.toString() === peerId) { - toClose.push(connection) - } + const tags = await this.components.getPeerStore().getTags(remotePeer) - if (toClose.length === toPrune) { - break - } + // sum all tag values + peerValues.set(remotePeer, tags.reduce((acc, curr) => { + return acc + curr.value + }, 0)) + } + + // sort by value, lowest to highest + const sortedConnections = connections.sort((a, b) => { + const peerAValue = peerValues.get(a.remotePeer) ?? 0 + const peerBValue = peerValues.get(b.remotePeer) ?? 0 + + if (peerAValue > peerBValue) { + return 1 + } + + if (peerAValue < peerBValue) { + return -1 + } + + return 0 + }) + + // close some connections + const toClose = [] + + for (const connection of sortedConnections) { + log('too many connections open - closing a connection to %p', connection.remotePeer) + toClose.push(connection) + + if (toClose.length === toPrune) { + break } } diff --git a/test/connection-manager/index.spec.ts b/test/connection-manager/index.spec.ts index 950223c0a3..10b0686956 100644 --- a/test/connection-manager/index.spec.ts +++ b/test/connection-manager/index.spec.ts @@ -51,7 +51,7 @@ describe('Connection Manager', () => { expect(libp2p.components.getMetrics()).to.exist() }) - it('should close lowest value peer connection when the maximum has been reached', async () => { + it('should close connections with low tag values first', async () => { const max = 5 libp2p = await createNode({ config: createBaseOptions({ @@ -67,20 +67,21 @@ describe('Connection Manager', () => { const connectionManager = libp2p.components.getConnectionManager() as DefaultConnectionManager const connectionManagerMaybeDisconnectOneSpy = sinon.spy(connectionManager, '_maybePruneConnections') - - // Add 1 too many connections const spies = new Map>>() - await Promise.all([...new Array(max + 1)].map(async (_, index) => { + + // Add 1 connection too many + for (let i = 0; i < max + 1; i++) { const connection = mockConnection(mockMultiaddrConnection(mockDuplex(), await createEd25519PeerId())) const spy = sinon.spy(connection, 'close') - // The connections have the same remote id, give them random ones - // so that we can verify the correct connection was closed - // sinon.stub(connection.remotePeer, 'toString').returns(index) - const value = Math.random() + + const value = Math.round(Math.random() * 100) spies.set(value, spy) - connectionManager.setPeerValue(connection.remotePeer, value) + await libp2p.peerStore.tagPeer(connection.remotePeer, 'test-tag', { + value + }) + await connectionManager._onConnect(new CustomEvent('connection', { detail: connection })) - })) + } // get the lowest value const lowest = Array.from(spies.keys()).sort((a, b) => { @@ -100,7 +101,7 @@ describe('Connection Manager', () => { expect(lowestSpy).to.have.property('callCount', 1) }) - it('should close connection when the maximum has been reached even without peer values', async () => { + it('should close connection when the maximum has been reached even without tags', async () => { const max = 5 libp2p = await createNode({ config: createBaseOptions({ @@ -119,11 +120,11 @@ describe('Connection Manager', () => { // Add 1 too many connections const spy = sinon.spy() - await Promise.all([...new Array(max + 1)].map(async () => { + for (let i = 0; i < max + 1; i++) { const connection = mockConnection(mockMultiaddrConnection(mockDuplex(), await createEd25519PeerId())) sinon.stub(connection, 'close').callsFake(async () => spy()) // eslint-disable-line await connectionManager._onConnect(new CustomEvent('connection', { detail: connection })) - })) + } expect(connectionManagerMaybeDisconnectOneSpy.callCount).to.equal(1) expect(spy).to.have.property('callCount', 1) From 2836acc90f8eafd2106539a80ac7d3b307c0bd02 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 28 Jun 2022 08:05:16 +0100 Subject: [PATCH 386/447] fix: use keep-alive tag to reconnect to peers on startup (#1278) Instead of trying to connect to every peer in the peer store when we start a node, only connect to the peers that have been marked with a `keep-alive` tag. --- doc/CONFIGURATION.md | 1 + package.json | 2 +- src/connection-manager/index.ts | 47 +++++++++++++++++++++++++++ src/libp2p.ts | 12 ------- test/connection-manager/index.spec.ts | 27 ++++++++++++++- test/fetch/index.spec.ts | 4 +++ test/peer-discovery/index.spec.ts | 45 ++----------------------- test/ping/index.spec.ts | 4 +++ test/utils/base-options.browser.ts | 3 ++ 9 files changed, 88 insertions(+), 57 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index e15c0816dc..0740154aa4 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -510,6 +510,7 @@ Dialing in libp2p can be configured to limit the rate of dialing, and how long d | 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 | | addressSorter | `(Array
) => Array
` | Sort the known addresses of a peer before trying to dial. | +| startupReconnectTimeout | `number` | When a node is restarted, we try to connect to any peers marked with the `keep-alive` tag up until to this timeout in ms is reached (default: 60000) | The below configuration example shows how the dialer should be configured, with the current defaults: diff --git a/package.json b/package.json index 75843b2b6d..83734fe5f3 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "@libp2p/interface-peer-id": "^1.0.2", "@libp2p/interface-peer-info": "^1.0.1", "@libp2p/interface-peer-routing": "^1.0.0", - "@libp2p/interface-peer-store": "^1.0.0", + "@libp2p/interface-peer-store": "^1.2.0", "@libp2p/interface-pubsub": "^1.0.3", "@libp2p/interface-registrar": "^2.0.0", "@libp2p/interface-stream-muxer": "^1.0.1", diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index 79487226f5..92637a3855 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -19,6 +19,8 @@ import { Dialer } from './dialer/index.js' import type { AddressSorter } from '@libp2p/interface-peer-store' import type { Resolver } from '@multiformats/multiaddr' import { PeerMap } from '@libp2p/peer-collections' +import { TimeoutController } from 'timeout-abort-controller' +import { KEEP_ALIVE } from '@libp2p/interface-peer-store/tags' const log = logger('libp2p:connection-manager') @@ -36,6 +38,7 @@ const defaultOptions: Partial = { const METRICS_COMPONENT = 'connection-manager' const METRICS_PEER_CONNECTIONS = 'peer-connections' +const STARTUP_RECONNECT_TIMEOUT = 60000 export interface ConnectionManagerInit { /** @@ -118,6 +121,12 @@ export interface ConnectionManagerInit { * Multiaddr resolvers to use when dialing */ resolvers?: Record + + /** + * On startup we try to dial any peer that has previously been + * tagged with KEEP_ALIVE up to this timeout in ms. (default: 60000) + */ + startupReconnectTimeout?: number } export interface ConnectionManagerEvents { @@ -136,6 +145,8 @@ export class DefaultConnectionManager extends EventEmitter private readonly latencyMonitor: LatencyMonitor + private readonly startupReconnectTimeout: number + private connectOnStartupController?: TimeoutController constructor (init: ConnectionManagerInit) { super() @@ -174,6 +185,8 @@ export class DefaultConnectionManager extends EventEmitter { + const keepAlivePeers: PeerId[] = [] + + for (const peer of await this.components.getPeerStore().all()) { + const tags = await this.components.getPeerStore().getTags(peer.id) + const hasKeepAlive = tags.filter(tag => tag.name === KEEP_ALIVE).length > 0 + + if (hasKeepAlive) { + keepAlivePeers.push(peer.id) + } + } + + this.connectOnStartupController?.clear() + this.connectOnStartupController = new TimeoutController(this.startupReconnectTimeout) + + await Promise.all( + keepAlivePeers.map(async peer => { + await this.openConnection(peer, { + signal: this.connectOnStartupController?.signal + }) + .catch(err => { + log.error(err) + }) + }) + ) + }) + .finally(() => { + this.connectOnStartupController?.clear() + }) } async beforeStop () { + // if we are still dialing KEEP_ALIVE peers, abort those dials + this.connectOnStartupController?.abort() this.components.getUpgrader().removeEventListener('connection', this.onConnect) this.components.getUpgrader().removeEventListener('connectionEnd', this.onDisconnect) } diff --git a/src/libp2p.ts b/src/libp2p.ts index 32676c48d4..002075c2bd 100644 --- a/src/libp2p.ts +++ b/src/libp2p.ts @@ -309,18 +309,6 @@ export class Libp2pNode extends EventEmitter implements Libp2p { ) log('libp2p has started') - - // 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 - await this.components.getPeerStore().forEach(peer => { - this.dispatchEvent(new CustomEvent('peer:discovery', { - detail: { - id: peer.id, - multiaddrs: peer.addresses.map(addr => addr.multiaddr), - protocols: peer.protocols - } - })) - }) } catch (err: any) { log.error('An error occurred starting libp2p', err) await this.stop() diff --git a/test/connection-manager/index.spec.ts b/test/connection-manager/index.spec.ts index 10b0686956..c4d9cc0a14 100644 --- a/test/connection-manager/index.spec.ts +++ b/test/connection-manager/index.spec.ts @@ -9,6 +9,7 @@ import type { DefaultConnectionManager } from '../../src/connection-manager/inde import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-mocks' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { CustomEvent } from '@libp2p/interfaces/events' +import { KEEP_ALIVE } from '@libp2p/interface-peer-store/tags' describe('Connection Manager', () => { let libp2p: Libp2pNode @@ -74,7 +75,7 @@ describe('Connection Manager', () => { const connection = mockConnection(mockMultiaddrConnection(mockDuplex(), await createEd25519PeerId())) const spy = sinon.spy(connection, 'close') - const value = Math.round(Math.random() * 100) + const value = i * 10 spies.set(value, spy) await libp2p.peerStore.tagPeer(connection.remotePeer, 'test-tag', { value @@ -141,4 +142,28 @@ describe('Connection Manager', () => { started: false })).to.eventually.rejected('maxConnections must be greater') }) + + it('should reconnect to important peers on startup', async () => { + const peerId = await createEd25519PeerId() + + libp2p = await createNode({ + config: createBaseOptions(), + started: false + }) + + const connectionManager = libp2p.components.getConnectionManager() as DefaultConnectionManager + const connectionManagerOpenConnectionSpy = sinon.spy(connectionManager, 'openConnection') + + await libp2p.start() + + expect(connectionManagerOpenConnectionSpy.called).to.be.false('Attempted to connect to peers') + + await libp2p.peerStore.tagPeer(peerId, KEEP_ALIVE) + + await libp2p.stop() + await libp2p.start() + + expect(connectionManagerOpenConnectionSpy.called).to.be.true('Did not attempt to connect to important peer') + expect(connectionManagerOpenConnectionSpy.getCall(0).args[0].toString()).to.equal(peerId.toString(), 'Attempted to connect to the wrong peer') + }) }) diff --git a/test/fetch/index.spec.ts b/test/fetch/index.spec.ts index eadf4f2991..555782c2dc 100644 --- a/test/fetch/index.spec.ts +++ b/test/fetch/index.spec.ts @@ -13,6 +13,8 @@ import { CustomEvent } from '@libp2p/interfaces/events' import { TimeoutController } from 'timeout-abort-controller' import delay from 'delay' import { pipe } from 'it-pipe' +import { PersistentPeerStore } from '@libp2p/peer-store' +import { MemoryDatastore } from 'datastore-core' const defaultInit: FetchServiceInit = { protocolPrefix: 'ipfs', @@ -27,6 +29,8 @@ async function createComponents (index: number) { peerId, registrar: mockRegistrar(), upgrader: mockUpgrader(), + peerStore: new PersistentPeerStore(), + datastore: new MemoryDatastore(), connectionManager: new DefaultConnectionManager({ minConnections: 50, maxConnections: 1000, diff --git a/test/peer-discovery/index.spec.ts b/test/peer-discovery/index.spec.ts index d76f06ba57..117e426768 100644 --- a/test/peer-discovery/index.spec.ts +++ b/test/peer-discovery/index.spec.ts @@ -2,26 +2,19 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' -import defer from 'p-defer' -import { Multiaddr } from '@multiformats/multiaddr' import { createBaseOptions } from '../utils/base-options.browser.js' import { createPeerId } from '../utils/creators/peer.js' -import { isPeerId, PeerId } from '@libp2p/interface-peer-id' +import type { PeerId } from '@libp2p/interface-peer-id' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' -import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-mocks' import type { Startable } from '@libp2p/interfaces/startable' describe('peer discovery', () => { describe('basic functions', () => { let peerId: PeerId - let remotePeerId: PeerId let libp2p: Libp2pNode before(async () => { - [peerId, remotePeerId] = await Promise.all([ - createPeerId(), - createPeerId() - ]) + peerId = await createPeerId() }) afterEach(async () => { @@ -32,40 +25,6 @@ describe('peer discovery', () => { sinon.reset() }) - it('should dial known peers on startup below the minConnections watermark', async () => { - libp2p = await createLibp2pNode(createBaseOptions({ - peerId, - connectionManager: { - minConnections: 2 - } - })) - - await libp2p.peerStore.addressBook.set(remotePeerId, [new Multiaddr('/ip4/165.1.1.1/tcp/80')]) - - const deferred = defer() - sinon.stub(libp2p.components.getConnectionManager(), 'openConnection').callsFake(async (id) => { - if (!isPeerId(id)) { - throw new Error('Tried to dial something that was not a peer ID') - } - - if (!remotePeerId.equals(id)) { - throw new Error('Tried to dial wrong peer ID') - } - - deferred.resolve() - return mockConnection(mockMultiaddrConnection(mockDuplex(), id)) - }) - - const spy = sinon.spy() - libp2p.addEventListener('peer:discovery', spy) - - await libp2p.start() - await deferred.promise - - expect(spy.calledOnce).to.equal(true) - expect(spy.getCall(0).args[0].detail.id.toString()).to.equal(remotePeerId.toString()) - }) - it('should stop discovery on libp2p start/stop', async () => { let started = 0 let stopped = 0 diff --git a/test/ping/index.spec.ts b/test/ping/index.spec.ts index 1acf352eec..fe885df4bc 100644 --- a/test/ping/index.spec.ts +++ b/test/ping/index.spec.ts @@ -13,6 +13,8 @@ import { CustomEvent } from '@libp2p/interfaces/events' import { TimeoutController } from 'timeout-abort-controller' import delay from 'delay' import { pipe } from 'it-pipe' +import { PersistentPeerStore } from '@libp2p/peer-store' +import { MemoryDatastore } from 'datastore-core' const defaultInit: PingServiceInit = { protocolPrefix: 'ipfs', @@ -27,6 +29,8 @@ async function createComponents (index: number) { peerId, registrar: mockRegistrar(), upgrader: mockUpgrader(), + peerStore: new PersistentPeerStore(), + datastore: new MemoryDatastore(), connectionManager: new DefaultConnectionManager({ minConnections: 50, maxConnections: 1000, diff --git a/test/utils/base-options.browser.ts b/test/utils/base-options.browser.ts index 0fb4bc9aac..3ac2f8d9d9 100644 --- a/test/utils/base-options.browser.ts +++ b/test/utils/base-options.browser.ts @@ -24,6 +24,9 @@ export function createBaseOptions (overrides?: Libp2pOptions): Libp2pOptions { hop: { enabled: false } + }, + nat: { + enabled: false } } From 5af93883ce80ea1176c1d91c3030f06cd2036f68 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 29 Jun 2022 06:59:51 +0100 Subject: [PATCH 387/447] chore: update deps (#1284) Update libp2p deps --- package.json | 14 +++++++------- test/upgrading/upgrader.spec.ts | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 83734fe5f3..611e90eed2 100644 --- a/package.json +++ b/package.json @@ -114,18 +114,18 @@ "@libp2p/interface-peer-store": "^1.2.0", "@libp2p/interface-pubsub": "^1.0.3", "@libp2p/interface-registrar": "^2.0.0", - "@libp2p/interface-stream-muxer": "^1.0.1", + "@libp2p/interface-stream-muxer": "^2.0.1", "@libp2p/interface-transport": "^1.0.0", "@libp2p/interfaces": "^3.0.2", "@libp2p/logger": "^2.0.0", "@libp2p/multistream-select": "^2.0.1", - "@libp2p/peer-collections": "^1.0.2", + "@libp2p/peer-collections": "^2.0.0", "@libp2p/peer-id": "^1.1.10", "@libp2p/peer-id-factory": "^1.0.9", - "@libp2p/peer-record": "^2.0.0", + "@libp2p/peer-record": "^3.0.0", "@libp2p/peer-store": "^3.0.0", - "@libp2p/tracked-map": "^1.0.5", - "@libp2p/utils": "^2.0.0", + "@libp2p/tracked-map": "^2.0.0", + "@libp2p/utils": "^3.0.0", "@multiformats/mafmt": "^11.0.2", "@multiformats/multiaddr": "^10.1.8", "abortable-iterator": "^4.0.2", @@ -175,11 +175,11 @@ "@libp2p/floodsub": "^3.0.0", "@libp2p/interface-compliance-tests": "^3.0.1", "@libp2p/interface-connection-encrypter-compliance-tests": "^1.0.0", - "@libp2p/interface-mocks": "^2.0.0", + "@libp2p/interface-mocks": "^3.0.1", "@libp2p/interop": "^2.0.0", "@libp2p/kad-dht": "^3.0.0", "@libp2p/mdns": "^2.0.0", - "@libp2p/mplex": "^3.0.0", + "@libp2p/mplex": "^4.0.0", "@libp2p/pubsub": "^3.0.1", "@libp2p/tcp": "^3.0.0", "@libp2p/topology": "^3.0.0", diff --git a/test/upgrading/upgrader.spec.ts b/test/upgrading/upgrader.spec.ts index ac7806bac3..6951d39b30 100644 --- a/test/upgrading/upgrader.spec.ts +++ b/test/upgrading/upgrader.spec.ts @@ -249,6 +249,7 @@ describe('Upgrader', () => { source = [] async sink () {} + close () {} } class OtherMuxerFactory implements StreamMuxerFactory { From e6f646ed361d231ec2306fee40fcb87c18af0c41 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 1 Jul 2022 18:33:17 +0200 Subject: [PATCH 388/447] chore: update deps (#1285) --- package.json | 8 +-- src/connection-manager/index.ts | 97 ++++++++++++++++++++++++++++++--- src/metrics/index.ts | 12 ++-- test/metrics/index.spec.ts | 2 +- 4 files changed, 103 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 611e90eed2..dbb8801308 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ }, "dependencies": { "@achingbrain/nat-port-mapper": "^1.0.3", - "@libp2p/components": "^2.0.0", + "@libp2p/components": "^2.0.1", "@libp2p/connection": "^4.0.0", "@libp2p/crypto": "^1.0.0", "@libp2p/interface-address-manager": "^1.0.1", @@ -106,7 +106,7 @@ "@libp2p/interface-connection-encrypter": "^1.0.2", "@libp2p/interface-content-routing": "^1.0.1", "@libp2p/interface-dht": "^1.0.0", - "@libp2p/interface-metrics": "^1.0.2", + "@libp2p/interface-metrics": "^2.0.0", "@libp2p/interface-peer-discovery": "^1.0.0", "@libp2p/interface-peer-id": "^1.0.2", "@libp2p/interface-peer-info": "^1.0.1", @@ -124,7 +124,7 @@ "@libp2p/peer-id-factory": "^1.0.9", "@libp2p/peer-record": "^3.0.0", "@libp2p/peer-store": "^3.0.0", - "@libp2p/tracked-map": "^2.0.0", + "@libp2p/tracked-map": "^2.0.1", "@libp2p/utils": "^3.0.0", "@multiformats/mafmt": "^11.0.2", "@multiformats/multiaddr": "^10.1.8", @@ -178,7 +178,7 @@ "@libp2p/interface-mocks": "^3.0.1", "@libp2p/interop": "^2.0.0", "@libp2p/kad-dht": "^3.0.0", - "@libp2p/mdns": "^2.0.0", + "@libp2p/mdns": "^3.0.0", "@libp2p/mplex": "^4.0.0", "@libp2p/pubsub": "^3.0.1", "@libp2p/tcp": "^3.0.0", diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index 92637a3855..0b32c02e97 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -7,7 +7,6 @@ import retimer from 'retimer' import type { AbortOptions } from '@libp2p/interfaces' import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events' import type { Startable } from '@libp2p/interfaces/startable' -import { trackedMap } from '@libp2p/tracked-map' import { codes } from '../errors.js' import { isPeerId, PeerId } from '@libp2p/interface-peer-id' import { setMaxListeners } from 'events' @@ -36,8 +35,8 @@ const defaultOptions: Partial = { movingAverageInterval: 60000 } +const METRICS_SYSTEM = 'libp2p' const METRICS_COMPONENT = 'connection-manager' -const METRICS_PEER_CONNECTIONS = 'peer-connections' const STARTUP_RECONNECT_TIMEOUT = 60000 export interface ConnectionManagerInit { @@ -162,11 +161,7 @@ export class DefaultConnectionManager extends EventEmitter { + const metric = { + inbound: 0, + outbound: 0 + } + + for (const conns of this.connections.values()) { + for (const conn of conns) { + if (conn.stat.direction === 'inbound') { + metric.inbound++ + } else { + metric.outbound++ + } + } + } + + return metric + } + }) + + // track total number of streams per protocol + this.components.getMetrics()?.updateComponentMetric({ + system: METRICS_SYSTEM, + component: METRICS_COMPONENT, + metric: 'protocol-streams-total', + label: 'protocol', + value: () => { + const metric: Record = {} + + for (const conns of this.connections.values()) { + for (const conn of conns) { + for (const stream of conn.streams) { + const key = `${stream.stat.direction} ${stream.stat.protocol ?? 'unnegotiated'}` + + metric[key] = (metric[key] ?? 0) + 1 + } + } + } + + return metric + } + }) + + // track 90th percentile of streams per protocol + this.components.getMetrics()?.updateComponentMetric({ + system: METRICS_SYSTEM, + component: METRICS_COMPONENT, + metric: 'protocol-streams-per-connection-90th-percentile', + label: 'protocol', + value: () => { + const allStreams: Record = {} + + for (const conns of this.connections.values()) { + for (const conn of conns) { + const streams: Record = {} + + for (const stream of conn.streams) { + const key = `${stream.stat.direction} ${stream.stat.protocol ?? 'unnegotiated'}` + + streams[key] = (streams[key] ?? 0) + 1 + } + + for (const [protocol, count] of Object.entries(streams)) { + allStreams[protocol] = allStreams[protocol] ?? [] + allStreams[protocol].push(count) + } + } + } + + const metric: Record = {} + + for (let [protocol, counts] of Object.entries(allStreams)) { + counts = counts.sort((a, b) => a - b) + + const index = Math.floor(counts.length * 0.9) + metric[protocol] = counts[index] + } + + return metric + } + }) } isStarted () { diff --git a/src/metrics/index.ts b/src/metrics/index.ts index d5e93a388d..6c4429bb2f 100644 --- a/src/metrics/index.ts +++ b/src/metrics/index.ts @@ -3,7 +3,7 @@ import each from 'it-foreach' import LRU from 'hashlru' import { METRICS as defaultOptions } from '../constants.js' import { DefaultStats, StatsInit } from './stats.js' -import type { ComponentMetricsUpdate, Metrics, Stats, TrackStreamOptions } from '@libp2p/interface-metrics' +import type { ComponentMetricsUpdate, Metrics, Stats, TrackedMetric, TrackStreamOptions } from '@libp2p/interface-metrics' import type { PeerId } from '@libp2p/interface-peer-id' import type { Startable } from '@libp2p/interfaces/startable' import type { Duplex } from 'it-stream-types' @@ -41,7 +41,7 @@ export class DefaultMetrics implements Metrics, Startable { private readonly protocolStats: Map private readonly oldPeers: ReturnType private running: boolean - private readonly systems: Map>> + private readonly systems: Map>> private readonly statsInit: StatsInit constructor (init: MetricsInit) { @@ -115,7 +115,7 @@ export class DefaultMetrics implements Metrics, Startable { } updateComponentMetric (update: ComponentMetricsUpdate) { - const { system = 'libp2p', component, metric, value } = update + const { system = 'libp2p', component, metric, value, label, help } = update if (!this.systems.has(system)) { this.systems.set(system, new Map()) @@ -137,7 +137,11 @@ export class DefaultMetrics implements Metrics, Startable { throw new Error('Unknown metric component') } - componentMetrics.set(metric, value) + componentMetrics.set(metric, { + label, + help, + calculate: typeof value !== 'function' ? () => value : value + }) } /** diff --git a/test/metrics/index.spec.ts b/test/metrics/index.spec.ts index 7bb0062f84..b4ef8b015d 100644 --- a/test/metrics/index.spec.ts +++ b/test/metrics/index.spec.ts @@ -296,6 +296,6 @@ describe('Metrics', () => { expect(metrics.getComponentMetrics()).to.have.lengthOf(1) expect(metrics.getComponentMetrics().get('libp2p')?.get(component)).to.have.lengthOf(1) - expect(metrics.getComponentMetrics().get('libp2p')?.get(component)?.get(metric)).to.equal(value) + expect(metrics.getComponentMetrics().get('libp2p')?.get(component)?.get(metric)?.calculate()).to.equal(value) }) }) From b1b91398e27d0b8852a74a87f0d8ccc5f34340b4 Mon Sep 17 00:00:00 2001 From: Cayman Date: Thu, 14 Jul 2022 07:03:06 -0500 Subject: [PATCH 389/447] fix: update muxer behavior (#1289) - use `direction` in `muxerFactory.createStreamMuxer` - use `muxer.close` instead of `muxer.streams.forEach(s => s.close())` --- src/upgrader.ts | 3 ++- test/relay/auto-relay.node.ts | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/upgrader.ts b/src/upgrader.ts index cb344dff6e..9cf8fc50c7 100644 --- a/src/upgrader.ts +++ b/src/upgrader.ts @@ -315,6 +315,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg if (muxerFactory != null) { // Create the muxer muxer = muxerFactory.createStreamMuxer({ + direction, // Run anytime a remote stream is created onIncomingStream: muxedStream => { if (connection == null) { @@ -469,7 +470,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg await maConn.close() // Ensure remaining streams are closed if (muxer != null) { - muxer.streams.forEach(s => s.close()) + muxer.close() } } }) diff --git a/test/relay/auto-relay.node.ts b/test/relay/auto-relay.node.ts index 94dd19e429..1363144e85 100644 --- a/test/relay/auto-relay.node.ts +++ b/test/relay/auto-relay.node.ts @@ -1,6 +1,7 @@ /* eslint-env mocha */ import { expect } from 'aegir/chai' +import { pEvent } from 'p-event' import defer from 'p-defer' import pWaitFor from 'p-wait-for' import sinon from 'sinon' @@ -205,6 +206,7 @@ describe('auto-relay', () => { // Disconnect from peer used for relay await relayLibp2p2.stop() + await pEvent(relayLibp2p1.connectionManager, 'peer:disconnect', { timeout: 500 }) // Should not be using the relay any more await expect(usingAsRelay(relayLibp2p1, relayLibp2p2, { From 750ed9c35f095aa6e136a801ccd792f2190f38a1 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 14 Jul 2022 13:35:12 +0000 Subject: [PATCH 390/447] fix: add timeout for incoming connections and build-in protocols (#1292) Ensure that we don't wait forever for upgrading an inbound connection to occur. Note that transports should return an AbortableSource when passed an AbortSignal so outbound connections to not need the same fix. Also adds default timeouts for the ping, fetch, and identify protocols. --- examples/webrtc-direct/test.js | 4 +- src/config.ts | 10 +- src/connection-manager/dialer/index.ts | 20 ++-- src/connection-manager/index.ts | 60 +++++++--- src/constants.ts | 5 + src/fetch/index.ts | 29 ++++- src/identify/index.ts | 23 ++-- src/libp2p.ts | 3 +- src/ping/index.ts | 32 ++++-- src/upgrader.ts | 153 +++++++++++++++---------- test/connection-manager/index.node.ts | 6 +- test/connection-manager/index.spec.ts | 5 + test/dialing/direct.node.ts | 3 +- test/dialing/direct.spec.ts | 3 +- test/fetch/index.spec.ts | 6 +- test/identify/index.spec.ts | 6 +- test/identify/push.spec.ts | 6 +- test/ping/index.spec.ts | 6 +- test/registrar/registrar.spec.ts | 3 +- test/upgrading/upgrader.spec.ts | 24 ++-- 20 files changed, 265 insertions(+), 142 deletions(-) diff --git a/examples/webrtc-direct/test.js b/examples/webrtc-direct/test.js index a658e84185..513a21e928 100644 --- a/examples/webrtc-direct/test.js +++ b/examples/webrtc-direct/test.js @@ -60,8 +60,8 @@ export async function test () { selector => { const text = document.querySelector(selector).innerText return text.includes('libp2p id is') && - text.includes('Found peer') && - text.includes('Connected to') + text.includes('Found peer 12D3KooWCuo3MdXfMgaqpLC5Houi1TRoFqgK9aoxok4NK5udMu8m') && + text.includes('Connected to 12D3KooWCuo3MdXfMgaqpLC5Houi1TRoFqgK9aoxok4NK5udMu8m') }, '#output', { timeout: 10000 } diff --git a/src/config.ts b/src/config.ts index f261914213..c82b9d1d40 100644 --- a/src/config.ts +++ b/src/config.ts @@ -26,6 +26,7 @@ const DefaultConfig: Partial = { maxParallelDials: Constants.MAX_PARALLEL_DIALS, maxDialsPerPeer: Constants.MAX_PER_PEER_DIALS, dialTimeout: Constants.DIAL_TIMEOUT, + inboundUpgradeTimeout: Constants.INBOUND_UPGRADE_TIMEOUT, resolvers: { dnsaddr: dnsaddrResolver }, @@ -79,7 +80,8 @@ const DefaultConfig: Partial = { host: { agentVersion: AGENT_VERSION }, - timeout: 30000, + // https://github.com/libp2p/go-libp2p/blob/8d2e54e1637041d5cf4fac1e531287560bd1f4ac/p2p/protocol/identify/id.go#L48 + timeout: 60000, maxInboundStreams: 1, maxOutboundStreams: 1, maxPushIncomingStreams: 1, @@ -88,12 +90,14 @@ const DefaultConfig: Partial = { ping: { protocolPrefix: 'ipfs', maxInboundStreams: 1, - maxOutboundStreams: 1 + maxOutboundStreams: 1, + timeout: 10000 }, fetch: { protocolPrefix: 'libp2p', maxInboundStreams: 1, - maxOutboundStreams: 1 + maxOutboundStreams: 1, + timeout: 10000 } } diff --git a/src/connection-manager/dialer/index.ts b/src/connection-manager/dialer/index.ts index 30cfff51f8..bcdc15a41b 100644 --- a/src/connection-manager/dialer/index.ts +++ b/src/connection-manager/dialer/index.ts @@ -177,7 +177,7 @@ export class Dialer implements Startable, Initializable { log('creating dial target for %p', id) - const dialTarget = await this._createCancellableDialTarget(id) + const dialTarget = await this._createCancellableDialTarget(id, options) if (dialTarget.addrs.length === 0) { throw errCode(new Error('The dial request has no valid addresses'), codes.ERR_NO_VALID_ADDRESSES) @@ -207,7 +207,7 @@ export class Dialer implements Startable, Initializable { * The dial to the first address that is successfully able to upgrade a connection * will be used. */ - async _createCancellableDialTarget (peer: PeerId): Promise { + async _createCancellableDialTarget (peer: PeerId, options: AbortOptions): Promise { // Make dial target promise cancellable const id = `${(parseInt(String(Math.random() * 1e9), 10)).toString()}${Date.now()}` const cancellablePromise = new Promise((resolve, reject) => { @@ -216,7 +216,7 @@ export class Dialer implements Startable, Initializable { try { const dialTarget = await Promise.race([ - this._createDialTarget(peer), + this._createDialTarget(peer, options), cancellablePromise ]) @@ -232,7 +232,7 @@ export class Dialer implements Startable, Initializable { * If a multiaddr is received it should be the first address attempted. * Multiaddrs not supported by the available transports will be filtered out. */ - async _createDialTarget (peer: PeerId): Promise { + async _createDialTarget (peer: PeerId, options: AbortOptions): Promise { const knownAddrs = await pipe( await this.components.getPeerStore().addressBook.get(peer), (source) => filter(source, async (address) => { @@ -253,7 +253,7 @@ export class Dialer implements Startable, Initializable { const addrs: Multiaddr[] = [] for (const a of knownAddrs) { - const resolvedAddrs = await this._resolve(a) + const resolvedAddrs = await this._resolve(a, options) resolvedAddrs.forEach(ra => addrs.push(ra)) } @@ -341,7 +341,7 @@ export class Dialer implements Startable, Initializable { /** * Resolve multiaddr recursively */ - async _resolve (ma: Multiaddr): Promise { + async _resolve (ma: Multiaddr, options: AbortOptions): Promise { // 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') @@ -351,9 +351,9 @@ export class Dialer implements Startable, Initializable { return [ma] } - const resolvedMultiaddrs = await this._resolveRecord(ma) + const resolvedMultiaddrs = await this._resolveRecord(ma, options) const recursiveMultiaddrs = await Promise.all(resolvedMultiaddrs.map(async (nm) => { - return await this._resolve(nm) + return await this._resolve(nm, options) })) const addrs = recursiveMultiaddrs.flat() @@ -368,10 +368,10 @@ export class Dialer implements Startable, Initializable { /** * Resolve a given multiaddr. If this fails, an empty array will be returned */ - async _resolveRecord (ma: Multiaddr): Promise { + async _resolveRecord (ma: Multiaddr, options: AbortOptions): Promise { try { ma = new Multiaddr(ma.toString()) // Use current multiaddr module - const multiaddrs = await ma.resolve() + const multiaddrs = await ma.resolve(options) return multiaddrs } catch (err) { log.error(`multiaddr ${ma.toString()} could not be resolved`, err) diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index 0b32c02e97..ff63583977 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -107,10 +107,17 @@ export interface ConnectionManagerInit { maxAddrsToDial?: number /** - * How long a dial attempt is allowed to take + * How long a dial attempt is allowed to take, including DNS resolution + * of the multiaddr, opening a socket and upgrading it to a Connection. */ dialTimeout?: number + /** + * When a new inbound connection is opened, the upgrade process (e.g. protect, + * encrypt, multiplex etc) must complete within this number of ms. + */ + inboundUpgradeTimeout: number + /** * Number of max concurrent dials per peer */ @@ -146,6 +153,7 @@ export class DefaultConnectionManager extends EventEmitter { + async openConnection (peerId: PeerId, options: AbortOptions = {}): Promise { log('dial to %p', peerId) const existingConnections = this.getConnections(peerId) @@ -496,30 +505,43 @@ export class DefaultConnectionManager extends EventEmitter { diff --git a/src/constants.ts b/src/constants.ts index fcb7e94312..ac05512983 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -4,6 +4,11 @@ */ export const DIAL_TIMEOUT = 30e3 +/** + * How long in ms an inbound connection upgrade is allowed to take + */ +export const INBOUND_UPGRADE_TIMEOUT = 30e3 + /** * Maximum allowed concurrent dials */ diff --git a/src/fetch/index.ts b/src/fetch/index.ts index 04d7efc817..9add8e8f46 100644 --- a/src/fetch/index.ts +++ b/src/fetch/index.ts @@ -10,10 +10,10 @@ import type { Stream } from '@libp2p/interface-connection' import type { IncomingStreamData } from '@libp2p/interface-registrar' import type { Components } from '@libp2p/components' import type { AbortOptions } from '@libp2p/interfaces' -import type { Duplex } from 'it-stream-types' import { abortableDuplex } from 'abortable-iterator' import { pipe } from 'it-pipe' import first from 'it-first' +import { TimeoutController } from 'timeout-abort-controller' const log = logger('libp2p:fetch') @@ -21,6 +21,11 @@ export interface FetchServiceInit { protocolPrefix: string maxInboundStreams: number maxOutboundStreams: number + + /** + * How long we should wait for a remote peer to send any data + */ + timeout: number } export interface HandleMessageOptions { @@ -86,14 +91,22 @@ export class FetchService implements Startable { log('dialing %s to %p', this.protocol, peer) const connection = await this.components.getConnectionManager().openConnection(peer, options) - const stream = await connection.newStream([this.protocol], options) - let source: Duplex = stream + let timeoutController + let signal = options.signal - // make stream abortable if AbortSignal passed - if (options.signal != null) { - source = abortableDuplex(stream, options.signal) + // create a timeout if no abort signal passed + if (signal == null) { + timeoutController = new TimeoutController(this.init.timeout) + signal = timeoutController.signal } + const stream = await connection.newStream([this.protocol], { + signal + }) + + // make stream abortable + const source = abortableDuplex(stream, signal) + try { const result = await pipe( [FetchRequest.encode({ identifier: key })], @@ -129,6 +142,10 @@ export class FetchService implements Startable { return result ?? null } finally { + if (timeoutController != null) { + timeoutController.clear() + } + stream.close() } } diff --git a/src/identify/index.ts b/src/identify/index.ts index e921308db5..8a871f1982 100644 --- a/src/identify/index.ts +++ b/src/identify/index.ts @@ -30,9 +30,6 @@ import type { Duplex } from 'it-stream-types' const log = logger('libp2p:identify') -// https://github.com/libp2p/go-libp2p/blob/8d2e54e1637041d5cf4fac1e531287560bd1f4ac/p2p/protocol/identify/id.go#L48 -const IDENTIFY_TIMEOUT = 60000 - // https://github.com/libp2p/go-libp2p/blob/8d2e54e1637041d5cf4fac1e531287560bd1f4ac/p2p/protocol/identify/id.go#L52 const MAX_IDENTIFY_MESSAGE_SIZE = 1024 * 8 @@ -54,7 +51,7 @@ export interface IdentifyServiceInit { /** * How long we should wait for a remote peer to send their identify response */ - timeout?: number + timeout: number /** * Identify responses larger than this in bytes will be rejected (default: 8192) @@ -167,7 +164,7 @@ export class IdentifyService implements Startable { const protocols = await this.components.getPeerStore().protoBook.get(this.components.getPeerId()) const pushes = connections.map(async connection => { - const timeoutController = new TimeoutController(this.init.timeout ?? IDENTIFY_TIMEOUT) + const timeoutController = new TimeoutController(this.init.timeout) let stream: Stream | undefined try { @@ -229,19 +226,21 @@ export class IdentifyService implements Startable { } async _identify (connection: Connection, options: AbortOptions = {}): Promise { - const stream = await connection.newStream([this.identifyProtocolStr], options) - let source: Duplex = stream let timeoutController let signal = options.signal // create a timeout if no abort signal passed if (signal == null) { - timeoutController = new TimeoutController(this.init.timeout ?? IDENTIFY_TIMEOUT) + timeoutController = new TimeoutController(this.init.timeout) signal = timeoutController.signal } - // make stream abortable if AbortSignal passed - source = abortableDuplex(stream, signal) + const stream = await connection.newStream([this.identifyProtocolStr], { + signal + }) + + // make stream abortable + const source = abortableDuplex(stream, signal) try { const data = await pipe( @@ -370,7 +369,7 @@ export class IdentifyService implements Startable { */ async _handleIdentify (data: IncomingStreamData) { const { connection, stream } = data - const timeoutController = new TimeoutController(this.init.timeout ?? IDENTIFY_TIMEOUT) + const timeoutController = new TimeoutController(this.init.timeout) try { const publicKey = this.components.getPeerId().publicKey ?? new Uint8Array(0) @@ -421,7 +420,7 @@ export class IdentifyService implements Startable { */ async _handlePush (data: IncomingStreamData) { const { connection, stream } = data - const timeoutController = new TimeoutController(this.init.timeout ?? IDENTIFY_TIMEOUT) + const timeoutController = new TimeoutController(this.init.timeout) let message: Identify | undefined try { diff --git a/src/libp2p.ts b/src/libp2p.ts index 002075c2bd..d4ae584b88 100644 --- a/src/libp2p.ts +++ b/src/libp2p.ts @@ -124,7 +124,8 @@ export class Libp2pNode extends EventEmitter implements Libp2p { // Set up the Upgrader this.components.setUpgrader(new DefaultUpgrader(this.components, { connectionEncryption: (init.connectionEncryption ?? []).map(component => this.configureComponent(component)), - muxers: (init.streamMuxers ?? []).map(component => this.configureComponent(component)) + muxers: (init.streamMuxers ?? []).map(component => this.configureComponent(component)), + inboundUpgradeTimeout: init.connectionManager.inboundUpgradeTimeout })) // Create the Connection Manager diff --git a/src/ping/index.ts b/src/ping/index.ts index f755bd51bc..1fc1567a4e 100644 --- a/src/ping/index.ts +++ b/src/ping/index.ts @@ -11,8 +11,8 @@ import type { PeerId } from '@libp2p/interface-peer-id' import type { Startable } from '@libp2p/interfaces/startable' import type { Components } from '@libp2p/components' import type { AbortOptions } from '@libp2p/interfaces' -import type { Duplex } from 'it-stream-types' import { abortableDuplex } from 'abortable-iterator' +import { TimeoutController } from 'timeout-abort-controller' const log = logger('libp2p:ping') @@ -20,6 +20,11 @@ export interface PingServiceInit { protocolPrefix: string maxInboundStreams: number maxOutboundStreams: number + + /** + * How long we should wait for a ping response + */ + timeout: number } export class PingService implements Startable { @@ -73,18 +78,25 @@ export class PingService implements Startable { async ping (peer: PeerId, options: AbortOptions = {}): Promise { log('dialing %s to %p', this.protocol, peer) - const connection = await this.components.getConnectionManager().openConnection(peer, options) - const stream = await connection.newStream([this.protocol], options) const start = Date.now() const data = randomBytes(PING_LENGTH) + const connection = await this.components.getConnectionManager().openConnection(peer, options) + let timeoutController + let signal = options.signal - let source: Duplex = stream - - // make stream abortable if AbortSignal passed - if (options.signal != null) { - source = abortableDuplex(stream, options.signal) + // create a timeout if no abort signal passed + if (signal == null) { + timeoutController = new TimeoutController(this.init.timeout) + signal = timeoutController.signal } + const stream = await connection.newStream([this.protocol], { + signal + }) + + // make stream abortable + const source = abortableDuplex(stream, signal) + try { const result = await pipe( [data], @@ -99,6 +111,10 @@ export class PingService implements Startable { return end - start } finally { + if (timeoutController != null) { + timeoutController.clear() + } + stream.close() } } diff --git a/src/upgrader.ts b/src/upgrader.ts index 9cf8fc50c7..80cb742f17 100644 --- a/src/upgrader.ts +++ b/src/upgrader.ts @@ -18,6 +18,8 @@ import { Components, isInitializable } from '@libp2p/components' import type { AbortOptions } from '@libp2p/interfaces' import type { Registrar } from '@libp2p/interface-registrar' import { DEFAULT_MAX_INBOUND_STREAMS, DEFAULT_MAX_OUTBOUND_STREAMS } from './registrar.js' +import { TimeoutController } from 'timeout-abort-controller' +import { abortableDuplex } from 'abortable-iterator' const log = logger('libp2p:upgrader') @@ -43,6 +45,12 @@ export interface CryptoResult extends SecuredConnection { export interface UpgraderInit { connectionEncryption: ConnectionEncrypter[] muxers: StreamMuxerFactory[] + + /** + * An amount of ms by which an inbound connection upgrade + * must complete + */ + inboundUpgradeTimeout: number } function findIncomingStreamLimit (protocol: string, registrar: Registrar) { @@ -89,6 +97,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg private readonly components: Components private readonly connectionEncryption: Map private readonly muxers: Map + private readonly inboundUpgradeTimeout: number constructor (components: Components, init: UpgraderInit) { super() @@ -105,6 +114,8 @@ export class DefaultUpgrader extends EventEmitter implements Upg init.muxers.forEach(muxer => { this.muxers.set(muxer.protocol, muxer) }) + + this.inboundUpgradeTimeout = init.inboundUpgradeTimeout } /** @@ -120,82 +131,92 @@ export class DefaultUpgrader extends EventEmitter implements Upg let proxyPeer const metrics = this.components.getMetrics() - if (await this.components.getConnectionGater().denyInboundConnection(maConn)) { - throw errCode(new Error('The multiaddr connection is blocked by gater.acceptConnection'), codes.ERR_CONNECTION_INTERCEPTED) - } + const timeoutController = new TimeoutController(this.inboundUpgradeTimeout) - if (metrics != null) { - ({ setTarget: setPeer, proxy: proxyPeer } = mutableProxy()) - const idString = `${(Math.random() * 1e9).toString(36)}${Date.now()}` - setPeer({ toString: () => idString }) - maConn = metrics.trackStream({ stream: maConn, remotePeer: proxyPeer }) - } + try { + const abortableStream = abortableDuplex(maConn, timeoutController.signal) + maConn.source = abortableStream.source + maConn.sink = abortableStream.sink - log('starting the inbound connection upgrade') + if (await this.components.getConnectionGater().denyInboundConnection(maConn)) { + throw errCode(new Error('The multiaddr connection is blocked by gater.acceptConnection'), codes.ERR_CONNECTION_INTERCEPTED) + } - // Protect - let protectedConn = maConn - const protector = this.components.getConnectionProtector() + if (metrics != null) { + ({ setTarget: setPeer, proxy: proxyPeer } = mutableProxy()) + const idString = `${(Math.random() * 1e9).toString(36)}${Date.now()}` + setPeer({ toString: () => idString }) + maConn = metrics.trackStream({ stream: maConn, remotePeer: proxyPeer }) + } - if (protector != null) { - log('protecting the inbound connection') - protectedConn = await protector.protect(maConn) - } + log('starting the inbound connection upgrade') - try { - // Encrypt the connection - ({ - conn: encryptedConn, - remotePeer, - protocol: cryptoProtocol - } = await this._encryptInbound(protectedConn)) + // Protect + let protectedConn = maConn + const protector = this.components.getConnectionProtector() + + if (protector != null) { + log('protecting the inbound connection') + protectedConn = await protector.protect(maConn) + } + + try { + // Encrypt the connection + ({ + conn: encryptedConn, + remotePeer, + protocol: cryptoProtocol + } = await this._encryptInbound(protectedConn)) + + if (await this.components.getConnectionGater().denyInboundEncryptedConnection(remotePeer, { + ...protectedConn, + ...encryptedConn + })) { + throw errCode(new Error('The multiaddr connection is blocked by gater.acceptEncryptedConnection'), codes.ERR_CONNECTION_INTERCEPTED) + } + + // Multiplex the connection + if (this.muxers.size > 0) { + const multiplexed = await this._multiplexInbound({ + ...protectedConn, + ...encryptedConn + }, this.muxers) + muxerFactory = multiplexed.muxerFactory + upgradedConn = multiplexed.stream + } else { + upgradedConn = encryptedConn + } + } catch (err: any) { + log.error('Failed to upgrade inbound connection', err) + await maConn.close(err) + throw err + } - if (await this.components.getConnectionGater().denyInboundEncryptedConnection(remotePeer, { + if (await this.components.getConnectionGater().denyInboundUpgradedConnection(remotePeer, { ...protectedConn, ...encryptedConn })) { throw errCode(new Error('The multiaddr connection is blocked by gater.acceptEncryptedConnection'), codes.ERR_CONNECTION_INTERCEPTED) } - // Multiplex the connection - if (this.muxers.size > 0) { - const multiplexed = await this._multiplexInbound({ - ...protectedConn, - ...encryptedConn - }, this.muxers) - muxerFactory = multiplexed.muxerFactory - upgradedConn = multiplexed.stream - } else { - upgradedConn = encryptedConn + if (metrics != null) { + metrics.updatePlaceholder(proxyPeer, remotePeer) + setPeer(remotePeer) } - } catch (err: any) { - log.error('Failed to upgrade inbound connection', err) - await maConn.close(err) - throw err - } - if (await this.components.getConnectionGater().denyInboundUpgradedConnection(remotePeer, { - ...protectedConn, - ...encryptedConn - })) { - throw errCode(new Error('The multiaddr connection is blocked by gater.acceptEncryptedConnection'), codes.ERR_CONNECTION_INTERCEPTED) - } + log('Successfully upgraded inbound connection') - if (metrics != null) { - metrics.updatePlaceholder(proxyPeer, remotePeer) - setPeer(remotePeer) + return this._createConnection({ + cryptoProtocol, + direction: 'inbound', + maConn, + upgradedConn, + muxerFactory, + remotePeer + }) + } finally { + timeoutController.clear() } - - log('Successfully upgraded inbound connection') - - return this._createConnection({ - cryptoProtocol, - direction: 'inbound', - maConn, - upgradedConn, - muxerFactory, - remotePeer - }) } /** @@ -378,8 +399,16 @@ export class DefaultUpgrader extends EventEmitter implements Upg const muxedStream = muxer.newStream() const mss = new Dialer(muxedStream) const metrics = this.components.getMetrics() + let controller: TimeoutController | undefined try { + if (options.signal == null) { + log('No abort signal was passed while trying to negotiate protocols %s falling back to default timeout', protocols) + + controller = new TimeoutController(30000) + options.signal = controller.signal + } + let { stream, protocol } = await mss.select(protocols, options) if (metrics != null) { @@ -415,6 +444,10 @@ export class DefaultUpgrader extends EventEmitter implements Upg } throw errCode(err, codes.ERR_UNSUPPORTED_PROTOCOL) + } finally { + if (controller != null) { + controller.clear() + } } } diff --git a/test/connection-manager/index.node.ts b/test/connection-manager/index.node.ts index 6e5d693c37..69fc2b23db 100644 --- a/test/connection-manager/index.node.ts +++ b/test/connection-manager/index.node.ts @@ -54,7 +54,8 @@ describe('Connection Manager', () => { const connectionManager = new DefaultConnectionManager({ maxConnections: 1000, minConnections: 50, - autoDialInterval: 1000 + autoDialInterval: 1000, + inboundUpgradeTimeout: 1000 }) connectionManager.init(new Components({ upgrader, peerStore })) @@ -89,7 +90,8 @@ describe('Connection Manager', () => { const connectionManager = new DefaultConnectionManager({ maxConnections: 1000, minConnections: 50, - autoDialInterval: 1000 + autoDialInterval: 1000, + inboundUpgradeTimeout: 1000 }) connectionManager.init(new Components({ upgrader, peerStore })) diff --git a/test/connection-manager/index.spec.ts b/test/connection-manager/index.spec.ts index c4d9cc0a14..e1ad443643 100644 --- a/test/connection-manager/index.spec.ts +++ b/test/connection-manager/index.spec.ts @@ -10,6 +10,7 @@ import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/int import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { CustomEvent } from '@libp2p/interfaces/events' import { KEEP_ALIVE } from '@libp2p/interface-peer-store/tags' +import pWaitFor from 'p-wait-for' describe('Connection Manager', () => { let libp2p: Libp2pNode @@ -163,6 +164,10 @@ describe('Connection Manager', () => { await libp2p.stop() await libp2p.start() + await pWaitFor(() => connectionManagerOpenConnectionSpy.called, { + interval: 100 + }) + expect(connectionManagerOpenConnectionSpy.called).to.be.true('Did not attempt to connect to important peer') expect(connectionManagerOpenConnectionSpy.getCall(0).args[0].toString()).to.equal(peerId.toString(), 'Attempted to connect to the wrong peer') }) diff --git a/test/dialing/direct.node.ts b/test/dialing/direct.node.ts index 428aba065a..19091bf3f4 100644 --- a/test/dialing/direct.node.ts +++ b/test/dialing/direct.node.ts @@ -74,7 +74,8 @@ describe('Dialing (direct, TCP)', () => { localComponents.setConnectionManager(new DefaultConnectionManager({ maxConnections: 100, minConnections: 50, - autoDialInterval: 1000 + autoDialInterval: 1000, + inboundUpgradeTimeout: 1000 })) localTM = new DefaultTransportManager(localComponents) diff --git a/test/dialing/direct.spec.ts b/test/dialing/direct.spec.ts index 1af0e7c6a5..846800a92e 100644 --- a/test/dialing/direct.spec.ts +++ b/test/dialing/direct.spec.ts @@ -52,7 +52,8 @@ describe('Dialing (direct, WebSockets)', () => { localComponents.setConnectionManager(new DefaultConnectionManager({ maxConnections: 100, minConnections: 50, - autoDialInterval: 1000 + autoDialInterval: 1000, + inboundUpgradeTimeout: 1000 })) localTM = new DefaultTransportManager(localComponents) diff --git a/test/fetch/index.spec.ts b/test/fetch/index.spec.ts index 555782c2dc..8ef13dd316 100644 --- a/test/fetch/index.spec.ts +++ b/test/fetch/index.spec.ts @@ -19,7 +19,8 @@ import { MemoryDatastore } from 'datastore-core' const defaultInit: FetchServiceInit = { protocolPrefix: 'ipfs', maxInboundStreams: 1, - maxOutboundStreams: 1 + maxOutboundStreams: 1, + timeout: 1000 } async function createComponents (index: number) { @@ -34,7 +35,8 @@ async function createComponents (index: number) { connectionManager: new DefaultConnectionManager({ minConnections: 50, maxConnections: 1000, - autoDialInterval: 1000 + autoDialInterval: 1000, + inboundUpgradeTimeout: 1000 }) }) diff --git a/test/identify/index.spec.ts b/test/identify/index.spec.ts index 3d6623a0f0..b9aefdffa6 100644 --- a/test/identify/index.spec.ts +++ b/test/identify/index.spec.ts @@ -40,7 +40,8 @@ const defaultInit: IdentifyServiceInit = { maxInboundStreams: 1, maxOutboundStreams: 1, maxPushIncomingStreams: 1, - maxPushOutgoingStreams: 1 + maxPushOutgoingStreams: 1, + timeout: 1000 } const protocols = [MULTICODEC_IDENTIFY, MULTICODEC_IDENTIFY_PUSH] @@ -58,7 +59,8 @@ async function createComponents (index: number) { connectionManager: new DefaultConnectionManager({ minConnections: 50, maxConnections: 1000, - autoDialInterval: 1000 + autoDialInterval: 1000, + inboundUpgradeTimeout: 1000 }) }) components.setAddressManager(new DefaultAddressManager(components, { diff --git a/test/identify/push.spec.ts b/test/identify/push.spec.ts index 9dbf95c8ba..312a07d08a 100644 --- a/test/identify/push.spec.ts +++ b/test/identify/push.spec.ts @@ -35,7 +35,8 @@ const defaultInit: IdentifyServiceInit = { maxInboundStreams: 1, maxOutboundStreams: 1, maxPushIncomingStreams: 1, - maxPushOutgoingStreams: 1 + maxPushOutgoingStreams: 1, + timeout: 1000 } const protocols = [MULTICODEC_IDENTIFY, MULTICODEC_IDENTIFY_PUSH] @@ -53,7 +54,8 @@ async function createComponents (index: number) { connectionManager: new DefaultConnectionManager({ minConnections: 50, maxConnections: 1000, - autoDialInterval: 1000 + autoDialInterval: 1000, + inboundUpgradeTimeout: 1000 }) }) components.setAddressManager(new DefaultAddressManager(components, { diff --git a/test/ping/index.spec.ts b/test/ping/index.spec.ts index fe885df4bc..9e17d0b678 100644 --- a/test/ping/index.spec.ts +++ b/test/ping/index.spec.ts @@ -19,7 +19,8 @@ import { MemoryDatastore } from 'datastore-core' const defaultInit: PingServiceInit = { protocolPrefix: 'ipfs', maxInboundStreams: 1, - maxOutboundStreams: 1 + maxOutboundStreams: 1, + timeout: 1000 } async function createComponents (index: number) { @@ -34,7 +35,8 @@ async function createComponents (index: number) { connectionManager: new DefaultConnectionManager({ minConnections: 50, maxConnections: 1000, - autoDialInterval: 1000 + autoDialInterval: 1000, + inboundUpgradeTimeout: 1000 }) }) diff --git a/test/registrar/registrar.spec.ts b/test/registrar/registrar.spec.ts index 4848c64ae8..080ecef3e9 100644 --- a/test/registrar/registrar.spec.ts +++ b/test/registrar/registrar.spec.ts @@ -43,7 +43,8 @@ describe('registrar', () => { connectionManager: new DefaultConnectionManager({ minConnections: 50, maxConnections: 1000, - autoDialInterval: 1000 + autoDialInterval: 1000, + inboundUpgradeTimeout: 1000 }) }) registrar = new DefaultRegistrar(components) diff --git a/test/upgrading/upgrader.spec.ts b/test/upgrading/upgrader.spec.ts index 6951d39b30..e21ca28242 100644 --- a/test/upgrading/upgrader.spec.ts +++ b/test/upgrading/upgrader.spec.ts @@ -66,7 +66,8 @@ describe('Upgrader', () => { ], muxers: [ localMuxerFactory - ] + ], + inboundUpgradeTimeout: 1000 }) remoteComponents = new Components({ @@ -80,7 +81,8 @@ describe('Upgrader', () => { ], muxers: [ new Mplex() - ] + ], + inboundUpgradeTimeout: 1000 }) await localComponents.getRegistrar().handle('/echo/1.0.0', ({ stream }) => { @@ -137,13 +139,15 @@ describe('Upgrader', () => { connectionEncryption: [ new Plaintext() ], - muxers: [] + muxers: [], + inboundUpgradeTimeout: 1000 }) remoteUpgrader = new DefaultUpgrader(remoteComponents, { connectionEncryption: [ new Plaintext() ], - muxers: [] + muxers: [], + inboundUpgradeTimeout: 1000 }) const connections = await Promise.all([ @@ -214,13 +218,15 @@ describe('Upgrader', () => { connectionEncryption: [ new BoomCrypto() ], - muxers: [] + muxers: [], + inboundUpgradeTimeout: 1000 }) remoteUpgrader = new DefaultUpgrader(remoteComponents, { connectionEncryption: [ new BoomCrypto() ], - muxers: [] + muxers: [], + inboundUpgradeTimeout: 1000 }) // Wait for the results of each side of the connection @@ -266,7 +272,8 @@ describe('Upgrader', () => { ], muxers: [ new OtherMuxerFactory() - ] + ], + inboundUpgradeTimeout: 1000 }) remoteUpgrader = new DefaultUpgrader(remoteComponents, { connectionEncryption: [ @@ -274,7 +281,8 @@ describe('Upgrader', () => { ], muxers: [ new Mplex() - ] + ], + inboundUpgradeTimeout: 1000 }) // Wait for the results of each side of the connection From 6eaab2e3ee9c6f8bbffd1060c757c9526cba8ae0 Mon Sep 17 00:00:00 2001 From: Konosuke Kachi <18605580+gkkachi@users.noreply.github.com> Date: Sat, 16 Jul 2022 00:27:26 +0900 Subject: [PATCH 391/447] docs: update transport example (#1268) --- examples/transports/3.js | 4 ++-- examples/transports/README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/transports/3.js b/examples/transports/3.js index 0bc9fa7097..26abf36e98 100644 --- a/examples/transports/3.js +++ b/examples/transports/3.js @@ -43,7 +43,7 @@ function print ({ stream }) { ) } -;(async () => { +(async () => { const [node1, node2, node3] = await Promise.all([ createNode([new TCP()], '/ip4/0.0.0.0/tcp/0'), createNode([new TCP(), new WebSockets()], ['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws']), @@ -69,7 +69,7 @@ function print ({ stream }) { stream ) - // node 2 (TCP+WebSockets) dials to node 2 (WebSockets) + // node 2 (TCP+WebSockets) dials to node 3 (WebSockets) const stream2 = await node2.dialProtocol(node3.peerId, '/print') await pipe( [uint8ArrayFromString('node 2 dialed to node 3 successfully')], diff --git a/examples/transports/README.md b/examples/transports/README.md index ca951834b9..fb4b920525 100644 --- a/examples/transports/README.md +++ b/examples/transports/README.md @@ -121,7 +121,7 @@ function printAddrs (node, number) { Then add, ```js -;(async () => { +(async () => { const [node1, node2] = await Promise.all([ createNode(), createNode() @@ -231,7 +231,7 @@ await pipe( stream ) -// node 2 (TCP+WebSockets) dials to node 2 (WebSockets) +// node 2 (TCP+WebSockets) dials to node 3 (WebSockets) const stream2 = await node2.dialProtocol(node3.peerId, '/print') await pipe( ['node 2 dialed to node 3 successfully'], From 0bb1b802c8fc2f32eaef10efbc88005dce6c6020 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 15 Jul 2022 16:35:52 +0000 Subject: [PATCH 392/447] feat: programmatically set agentVersion for use in identify (#1296) If no `agentVersion` is provided for the Identify protocol, the default `AGENT_VERSION` will now be set to * `js-libp2p/ UserAgent=` when running in Node.js * `js-libp2p/ UserAgent=` when running in the browser (also when running in a webworker) Fixes #686 Supersedes #1240 Co-authored-by: Kevin Westphal Co-authored-by: Kevin <56823591+6d7a@users.noreply.github.com> --- package.json | 1 + scripts/update-version.js | 14 ++++++++++++++ src/config.ts | 10 ++++++++++ src/version.ts | 1 - test/identify/service.spec.ts | 24 ++++++++++++++++++++++++ 5 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 scripts/update-version.js diff --git a/package.json b/package.json index dbb8801308..f254345301 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "clean": "aegir clean", "lint": "aegir lint", "dep-check": "aegir dep-check", + "prepublishOnly": "node scripts/update-version.js", "build": "aegir build", "generate": "run-s generate:proto:*", "generate:proto:circuit": "protons ./src/circuit/pb/index.proto", diff --git a/scripts/update-version.js b/scripts/update-version.js new file mode 100644 index 0000000000..2620790ae4 --- /dev/null +++ b/scripts/update-version.js @@ -0,0 +1,14 @@ +import { readFile, writeFile } from 'fs/promises' + +const pkg = JSON.parse( + await readFile( + new URL('../package.json', import.meta.url) + ) +) + +await writeFile( + new URL('../src/version.ts', import.meta.url), + `export const version = '${pkg.version}' +export const name = '${pkg.name}' +` +) diff --git a/src/config.ts b/src/config.ts index c82b9d1d40..6e88a67456 100644 --- a/src/config.ts +++ b/src/config.ts @@ -10,6 +10,7 @@ import type { Libp2pInit } from './index.js' import { codes, messages } from './errors.js' import errCode from 'err-code' import type { RecursivePartial } from '@libp2p/interfaces' +import { isNode, isBrowser, isWebWorker, isElectronMain, isElectronRenderer, isReactNative } from 'wherearewe' const DefaultConfig: Partial = { addresses: { @@ -116,5 +117,14 @@ export function validateConfig (opts: RecursivePartial): Libp2pInit throw errCode(new Error(messages.ERR_PROTECTOR_REQUIRED), codes.ERR_PROTECTOR_REQUIRED) } + // Append user agent version to default AGENT_VERSION depending on the environment + if (resultingOptions.identify.host.agentVersion === AGENT_VERSION) { + if (isNode || isElectronMain) { + resultingOptions.identify.host.agentVersion += ` UserAgent=${globalThis.process.version}` + } else if (isBrowser || isWebWorker || isElectronRenderer || isReactNative) { + resultingOptions.identify.host.agentVersion += ` UserAgent=${globalThis.navigator.userAgent}` + } + } + return resultingOptions } diff --git a/src/version.ts b/src/version.ts index c77a04e17f..36a3d88e7c 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,3 +1,2 @@ - export const version = '0.0.0' export const name = 'libp2p' diff --git a/test/identify/service.spec.ts b/test/identify/service.spec.ts index e772bad3e7..152d00c24d 100644 --- a/test/identify/service.spec.ts +++ b/test/identify/service.spec.ts @@ -14,6 +14,7 @@ import { peerIdFromString } from '@libp2p/peer-id' import type { PeerId } from '@libp2p/interface-peer-id' import type { Libp2pNode } from '../../src/libp2p.js' import { pEvent } from 'p-event' +import { AGENT_VERSION } from '../../src/identify/consts.js' describe('libp2p.dialer.identifyService', () => { let peerId: PeerId @@ -147,6 +148,29 @@ describe('libp2p.dialer.identifyService', () => { await pWaitFor(() => connection.streams.length === 0) }) + it('should append UserAgent information to default AGENT_VERSION', async () => { + // Stub environment version for testing dynamic AGENT_VERSION + sinon.stub(process, 'version').value('vTEST') + + if (typeof globalThis.navigator !== 'undefined') { + sinon.stub(navigator, 'userAgent').value('vTEST') + } + + libp2p = await createLibp2pNode(createBaseOptions({ + peerId + })) + + await libp2p.start() + + if (libp2p.identifyService == null) { + throw new Error('Identity service was not configured') + } + + const storedAgentVersion = await libp2p.peerStore.metadataBook.getValue(peerId, 'AgentVersion') + + expect(AGENT_VERSION + ' UserAgent=vTEST').to.equal(uint8ArrayToString(storedAgentVersion ?? new Uint8Array())) + }) + it('should store host data and protocol version into metadataBook', async () => { const agentVersion = 'js-project/1.0.0' From ba56c6466232ad4aa5025e2db084c5c9ccd4e5d0 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 15 Jul 2022 16:36:31 +0000 Subject: [PATCH 393/447] fix: add timeout for circuit relay (#1294) Make sure we don't potentially wait forever during incoming circuit relay handshakes. Adds a timeout option to the hop config to control how long we will wait. --- src/circuit/circuit/hop.ts | 19 +++-- src/circuit/circuit/stop.ts | 10 ++- src/circuit/index.ts | 13 +--- src/circuit/transport.ts | 137 +++++++++++++++++++++--------------- src/config.ts | 3 +- src/fetch/index.ts | 17 +++-- src/identify/index.ts | 17 +++-- src/index.ts | 1 + src/libp2p.ts | 2 +- src/ping/index.ts | 18 +++-- test/relay/relay.node.ts | 34 +++++++++ 11 files changed, 170 insertions(+), 101 deletions(-) diff --git a/src/circuit/circuit/hop.ts b/src/circuit/circuit/hop.ts index d8bbe03ed6..a7fd38850e 100644 --- a/src/circuit/circuit/hop.ts +++ b/src/circuit/circuit/hop.ts @@ -12,6 +12,7 @@ import { peerIdFromBytes } from '@libp2p/peer-id' import type { Duplex } from 'it-stream-types' import type { Circuit } from '../transport.js' import type { ConnectionManager } from '@libp2p/interface-connection-manager' +import type { AbortOptions } from '@libp2p/interfaces' const log = logger('libp2p:circuit:hop') @@ -118,7 +119,7 @@ export async function handleHop (hopRequest: HopRequest) { ) } -export interface HopConfig { +export interface HopConfig extends AbortOptions { connection: Connection request: CircuitPB } @@ -130,11 +131,14 @@ export interface HopConfig { export async function hop (options: HopConfig): Promise> { const { connection, - request + request, + signal } = options // Create a new stream to the relay - const stream = await connection.newStream(RELAY_CODEC) + const stream = await connection.newStream(RELAY_CODEC, { + signal + }) // Send the HOP request const streamHandler = new StreamHandler({ stream }) streamHandler.write(request) @@ -156,7 +160,7 @@ export async function hop (options: HopConfig): Promise> { throw errCode(new Error(`HOP request failed with code "${response.code ?? 'unknown'}"`), Errors.ERR_HOP_REQUEST_FAILED) } -export interface CanHopOptions { +export interface CanHopOptions extends AbortOptions { connection: Connection } @@ -165,11 +169,14 @@ export interface CanHopOptions { */ export async function canHop (options: CanHopOptions) { const { - connection + connection, + signal } = options // Create a new stream to the relay - const stream = await connection.newStream(RELAY_CODEC) + const stream = await connection.newStream(RELAY_CODEC, { + signal + }) // Send the HOP request const streamHandler = new StreamHandler({ stream }) diff --git a/src/circuit/circuit/stop.ts b/src/circuit/circuit/stop.ts index 75c97f66f1..1ad8f8e0f1 100644 --- a/src/circuit/circuit/stop.ts +++ b/src/circuit/circuit/stop.ts @@ -5,6 +5,7 @@ import { StreamHandler } from './stream-handler.js' import { validateAddrs } from './utils.js' import type { Connection } from '@libp2p/interface-connection' import type { Duplex } from 'it-stream-types' +import type { AbortOptions } from '@libp2p/interfaces' const log = logger('libp2p:circuit:stop') @@ -42,7 +43,7 @@ export function handleStop (options: HandleStopOptions): Duplex | un return streamHandler.rest() } -export interface StopOptions { +export interface StopOptions extends AbortOptions { connection: Connection request: CircuitPB } @@ -53,10 +54,13 @@ export interface StopOptions { export async function stop (options: StopOptions) { const { connection, - request + request, + signal } = options - const stream = await connection.newStream([RELAY_CODEC]) + const stream = await connection.newStream(RELAY_CODEC, { + signal + }) log('starting stop request to %p', connection.remotePeer) const streamHandler = new StreamHandler({ stream }) diff --git a/src/circuit/index.ts b/src/circuit/index.ts index b8219b57e8..ed6aebbcf6 100644 --- a/src/circuit/index.ts +++ b/src/circuit/index.ts @@ -13,6 +13,7 @@ import { import type { AddressSorter } from '@libp2p/interface-peer-store' import type { Startable } from '@libp2p/interfaces/startable' import type { Components } from '@libp2p/components' +import type { RelayConfig } from '../index.js' const log = logger('libp2p:relay') @@ -22,11 +23,6 @@ export interface RelayAdvertiseConfig { ttl?: number } -export interface HopConfig { - enabled?: boolean - active?: boolean -} - export interface AutoRelayConfig { enabled?: boolean @@ -36,13 +32,8 @@ export interface AutoRelayConfig { maxListeners: number } -export interface RelayInit { +export interface RelayInit extends RelayConfig { addressSorter?: AddressSorter - maxListeners?: number - onError?: (error: Error, msg?: string) => void - hop: HopConfig - advertise: RelayAdvertiseConfig - autoRelay: AutoRelayConfig } export class Relay implements Startable { diff --git a/src/circuit/transport.ts b/src/circuit/transport.ts index a4d4894cc3..295ef81410 100644 --- a/src/circuit/transport.ts +++ b/src/circuit/transport.ts @@ -17,12 +17,20 @@ import type { AbortOptions } from '@libp2p/interfaces' import type { IncomingStreamData } from '@libp2p/interface-registrar' import type { Listener, Transport, CreateListenerOptions, ConnectionHandler } from '@libp2p/interface-transport' import type { Connection } from '@libp2p/interface-connection' +import type { RelayConfig } from '../index.js' +import { abortableDuplex } from 'abortable-iterator' +import { TimeoutController } from 'timeout-abort-controller' const log = logger('libp2p:circuit') export class Circuit implements Transport, Initializable { private handler?: ConnectionHandler private components: Components = new Components() + private readonly _init: RelayConfig + + constructor (init: RelayConfig) { + this._init = init + } init (components: Components): void { this.components = components @@ -54,49 +62,20 @@ export class Circuit implements Transport, Initializable { async _onProtocol (data: IncomingStreamData) { const { connection, stream } = data - const streamHandler = new StreamHandler({ stream }) - const request = await streamHandler.read() - - if (request == null) { - log('request was invalid, could not read from stream') - streamHandler.write({ - type: CircuitPB.Type.STATUS, - code: CircuitPB.Status.MALFORMED_MESSAGE - }) - streamHandler.close() - return - } + const controller = new TimeoutController(this._init.hop.timeout) - let virtualConnection + try { + const source = abortableDuplex(stream, controller.signal) + const streamHandler = new StreamHandler({ + stream: { + ...stream, + ...source + } + }) + const request = await streamHandler.read() - switch (request.type) { - case CircuitPB.Type.CAN_HOP: { - log('received CAN_HOP request from %p', connection.remotePeer) - await handleCanHop({ circuit: this, connection, streamHandler }) - break - } - case CircuitPB.Type.HOP: { - log('received HOP request from %p', connection.remotePeer) - virtualConnection = await handleHop({ - connection, - request, - streamHandler, - circuit: this, - connectionManager: this.components.getConnectionManager() - }) - break - } - case CircuitPB.Type.STOP: { - log('received STOP request from %p', connection.remotePeer) - virtualConnection = await handleStop({ - connection, - request, - streamHandler - }) - break - } - default: { - log('Request of type %s not supported', request.type) + if (request == null) { + log('request was invalid, could not read from stream') streamHandler.write({ type: CircuitPB.Type.STATUS, code: CircuitPB.Status.MALFORMED_MESSAGE @@ -104,27 +83,68 @@ export class Circuit implements Transport, Initializable { streamHandler.close() return } - } - if (virtualConnection != null) { - // @ts-expect-error dst peer will not be undefined - const remoteAddr = new Multiaddr(request.dstPeer.addrs[0]) - // @ts-expect-error dst peer will not be undefined - const localAddr = new Multiaddr(request.srcPeer.addrs[0]) - const maConn = streamToMaConnection({ - stream: virtualConnection, - remoteAddr, - localAddr - }) - const type = request.type === CircuitPB.Type.HOP ? 'relay' : 'inbound' - log('new %s connection %s', type, maConn.remoteAddr) + let virtualConnection - const conn = await this.components.getUpgrader().upgradeInbound(maConn) - log('%s connection %s upgraded', type, maConn.remoteAddr) + switch (request.type) { + case CircuitPB.Type.CAN_HOP: { + log('received CAN_HOP request from %p', connection.remotePeer) + await handleCanHop({ circuit: this, connection, streamHandler }) + break + } + case CircuitPB.Type.HOP: { + log('received HOP request from %p', connection.remotePeer) + virtualConnection = await handleHop({ + connection, + request, + streamHandler, + circuit: this, + connectionManager: this.components.getConnectionManager() + }) + break + } + case CircuitPB.Type.STOP: { + log('received STOP request from %p', connection.remotePeer) + virtualConnection = await handleStop({ + connection, + request, + streamHandler + }) + break + } + default: { + log('Request of type %s not supported', request.type) + streamHandler.write({ + type: CircuitPB.Type.STATUS, + code: CircuitPB.Status.MALFORMED_MESSAGE + }) + streamHandler.close() + return + } + } + + if (virtualConnection != null) { + // @ts-expect-error dst peer will not be undefined + const remoteAddr = new Multiaddr(request.dstPeer.addrs[0]) + // @ts-expect-error dst peer will not be undefined + const localAddr = new Multiaddr(request.srcPeer.addrs[0]) + const maConn = streamToMaConnection({ + stream: virtualConnection, + remoteAddr, + localAddr + }) + const type = request.type === CircuitPB.Type.HOP ? 'relay' : 'inbound' + log('new %s connection %s', type, maConn.remoteAddr) + + const conn = await this.components.getUpgrader().upgradeInbound(maConn) + log('%s connection %s upgraded', type, maConn.remoteAddr) - if (this.handler != null) { - this.handler(conn) + if (this.handler != null) { + this.handler(conn) + } } + } finally { + controller.clear() } } @@ -160,6 +180,7 @@ export class Circuit implements Transport, Initializable { try { const virtualConnection = await hop({ + ...options, connection: relayConnection, request: { type: CircuitPB.Type.HOP, diff --git a/src/config.ts b/src/config.ts index 6e88a67456..a3acf51a2c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -69,7 +69,8 @@ const DefaultConfig: Partial = { }, hop: { enabled: false, - active: false + active: false, + timeout: 30000 }, autoRelay: { enabled: false, diff --git a/src/fetch/index.ts b/src/fetch/index.ts index 9add8e8f46..327d5049df 100644 --- a/src/fetch/index.ts +++ b/src/fetch/index.ts @@ -93,6 +93,7 @@ export class FetchService implements Startable { const connection = await this.components.getConnectionManager().openConnection(peer, options) let timeoutController let signal = options.signal + let stream: Stream | undefined // create a timeout if no abort signal passed if (signal == null) { @@ -100,14 +101,14 @@ export class FetchService implements Startable { signal = timeoutController.signal } - const stream = await connection.newStream([this.protocol], { - signal - }) + try { + stream = await connection.newStream([this.protocol], { + signal + }) - // make stream abortable - const source = abortableDuplex(stream, signal) + // make stream abortable + const source = abortableDuplex(stream, signal) - try { const result = await pipe( [FetchRequest.encode({ identifier: key })], lp.encode(), @@ -146,7 +147,9 @@ export class FetchService implements Startable { timeoutController.clear() } - stream.close() + if (stream != null) { + stream.close() + } } } diff --git a/src/identify/index.ts b/src/identify/index.ts index 8a871f1982..268ab057ab 100644 --- a/src/identify/index.ts +++ b/src/identify/index.ts @@ -228,6 +228,7 @@ export class IdentifyService implements Startable { async _identify (connection: Connection, options: AbortOptions = {}): Promise { let timeoutController let signal = options.signal + let stream: Stream | undefined // create a timeout if no abort signal passed if (signal == null) { @@ -235,14 +236,14 @@ export class IdentifyService implements Startable { signal = timeoutController.signal } - const stream = await connection.newStream([this.identifyProtocolStr], { - signal - }) + try { + stream = await connection.newStream([this.identifyProtocolStr], { + signal + }) - // make stream abortable - const source = abortableDuplex(stream, signal) + // make stream abortable + const source = abortableDuplex(stream, signal) - try { const data = await pipe( [], source, @@ -266,7 +267,9 @@ export class IdentifyService implements Startable { timeoutController.clear() } - stream.close() + if (stream != null) { + stream.close() + } } } diff --git a/src/index.ts b/src/index.ts index b7aa62878e..d5c3830709 100644 --- a/src/index.ts +++ b/src/index.ts @@ -50,6 +50,7 @@ export interface MetricsConfig { export interface HopConfig { enabled?: boolean active?: boolean + timeout: number } export interface RelayConfig { diff --git a/src/libp2p.ts b/src/libp2p.ts index d4ae584b88..0a5a435df5 100644 --- a/src/libp2p.ts +++ b/src/libp2p.ts @@ -218,7 +218,7 @@ export class Libp2pNode extends EventEmitter implements Libp2p { }))) if (init.relay.enabled) { - this.components.getTransportManager().add(this.configureComponent(new Circuit())) + this.components.getTransportManager().add(this.configureComponent(new Circuit(init.relay))) this.configureComponent(new Relay(this.components, { addressSorter: init.connectionManager.addressSorter, diff --git a/src/ping/index.ts b/src/ping/index.ts index 1fc1567a4e..98c249093a 100644 --- a/src/ping/index.ts +++ b/src/ping/index.ts @@ -13,6 +13,7 @@ import type { Components } from '@libp2p/components' import type { AbortOptions } from '@libp2p/interfaces' import { abortableDuplex } from 'abortable-iterator' import { TimeoutController } from 'timeout-abort-controller' +import type { Stream } from '@libp2p/interface-connection' const log = logger('libp2p:ping') @@ -83,6 +84,7 @@ export class PingService implements Startable { const connection = await this.components.getConnectionManager().openConnection(peer, options) let timeoutController let signal = options.signal + let stream: Stream | undefined // create a timeout if no abort signal passed if (signal == null) { @@ -90,14 +92,14 @@ export class PingService implements Startable { signal = timeoutController.signal } - const stream = await connection.newStream([this.protocol], { - signal - }) + try { + stream = await connection.newStream([this.protocol], { + signal + }) - // make stream abortable - const source = abortableDuplex(stream, signal) + // make stream abortable + const source = abortableDuplex(stream, signal) - try { const result = await pipe( [data], source, @@ -115,7 +117,9 @@ export class PingService implements Startable { timeoutController.clear() } - stream.close() + if (stream != null) { + stream.close() + } } } } diff --git a/test/relay/relay.node.ts b/test/relay/relay.node.ts index 60e1bf5f60..4249f01b96 100644 --- a/test/relay/relay.node.ts +++ b/test/relay/relay.node.ts @@ -13,6 +13,7 @@ import { RELAY_CODEC } from '../../src/circuit/multicodec.js' import { StreamHandler } from '../../src/circuit/circuit/stream-handler.js' import { CircuitRelay } from '../../src/circuit/pb/index.js' import { createNodeOptions, createRelayOptions } from './utils.js' +import delay from 'delay' describe('Dialing (via relay, TCP)', () => { let srcLibp2p: Libp2pNode @@ -170,4 +171,37 @@ describe('Dialing (via relay, TCP)', () => { expect(dstToRelayConn).to.have.lengthOf(1) expect(dstToRelayConn).to.have.nested.property('[0].stat.status', 'OPEN') }) + + it('should time out when establishing a relay connection', async () => { + await relayLibp2p.stop() + relayLibp2p = await createNode({ + config: createRelayOptions({ + relay: { + autoRelay: { + enabled: false + }, + hop: { + // very short timeout + timeout: 10 + } + } + }) + }) + + const relayAddr = relayLibp2p.components.getTransportManager().getAddrs()[0] + const dialAddr = relayAddr.encapsulate(`/p2p/${relayLibp2p.peerId.toString()}`) + + const connection = await srcLibp2p.dial(dialAddr) + const stream = await connection.newStream('/libp2p/circuit/relay/0.1.0') + + await stream.sink(async function * () { + // delay for longer than the timeout + await delay(1000) + yield Uint8Array.from([0]) + }()) + + // because we timed out, the remote should have reset the stream + await expect(all(stream.source)).to.eventually.be.rejected + .with.property('code', 'ERR_MPLEX_STREAM_RESET') + }) }) From 627b8bf87c775762dd6a9de69b77852e48ebcf26 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Sun, 17 Jul 2022 08:25:21 +0000 Subject: [PATCH 394/447] fix: MaxListenersExceeded warning (#1297) Where we create signals that are passed down the stack, increase the max listeners to prevent warnings in the console. --- src/circuit/transport.ts | 6 +++++ src/connection-manager/dialer/auto-dialer.ts | 6 +++++ src/connection-manager/index.ts | 10 +++++++++ src/fetch/index.ts | 6 +++++ src/identify/index.ts | 23 +++++++++++++++++++- src/ping/index.ts | 6 +++++ src/upgrader.ts | 11 ++++++++++ 7 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/circuit/transport.ts b/src/circuit/transport.ts index 295ef81410..5c87bd2e3a 100644 --- a/src/circuit/transport.ts +++ b/src/circuit/transport.ts @@ -20,6 +20,7 @@ import type { Connection } from '@libp2p/interface-connection' import type { RelayConfig } from '../index.js' import { abortableDuplex } from 'abortable-iterator' import { TimeoutController } from 'timeout-abort-controller' +import { setMaxListeners } from 'events' const log = logger('libp2p:circuit') @@ -64,6 +65,11 @@ export class Circuit implements Transport, Initializable { const { connection, stream } = data const controller = new TimeoutController(this._init.hop.timeout) + try { + // fails on node < 15.4 + setMaxListeners?.(Infinity, controller.signal) + } catch {} + try { const source = abortableDuplex(stream, controller.signal) const streamHandler = new StreamHandler({ diff --git a/src/connection-manager/dialer/auto-dialer.ts b/src/connection-manager/dialer/auto-dialer.ts index ab63ec2888..0c572647ca 100644 --- a/src/connection-manager/dialer/auto-dialer.ts +++ b/src/connection-manager/dialer/auto-dialer.ts @@ -2,6 +2,7 @@ import type { PeerInfo } from '@libp2p/interface-peer-info' import { logger } from '@libp2p/logger' import type { Components } from '@libp2p/components' import { TimeoutController } from 'timeout-abort-controller' +import { setMaxListeners } from 'events' const log = logger('libp2p:dialer:auto-dialer') @@ -44,6 +45,11 @@ export class AutoDialer { const controller = new TimeoutController(this.dialTimeout) + try { + // fails on node < 15.4 + setMaxListeners?.(Infinity, controller.signal) + } catch {} + void this.components.getConnectionManager().openConnection(peer.id, { signal: controller.signal }) diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index ff63583977..54acae3b99 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -331,6 +331,11 @@ export class DefaultConnectionManager extends EventEmitter { await this.openConnection(peer, { @@ -510,6 +515,11 @@ export class DefaultConnectionManager extends EventEmitter { - const timeoutController = new TimeoutController(this.init.timeout) let stream: Stream | undefined + const timeoutController = new TimeoutController(this.init.timeout) + + try { + // fails on node < 15.4 + setMaxListeners?.(Infinity, timeoutController.signal) + } catch {} try { stream = await connection.newStream([this.identifyPushProtocolStr], { @@ -234,6 +240,11 @@ export class IdentifyService implements Startable { if (signal == null) { timeoutController = new TimeoutController(this.init.timeout) signal = timeoutController.signal + + try { + // fails on node < 15.4 + setMaxListeners?.(Infinity, timeoutController.signal) + } catch {} } try { @@ -374,6 +385,11 @@ export class IdentifyService implements Startable { const { connection, stream } = data const timeoutController = new TimeoutController(this.init.timeout) + try { + // fails on node < 15.4 + setMaxListeners?.(Infinity, timeoutController.signal) + } catch {} + try { const publicKey = this.components.getPeerId().publicKey ?? new Uint8Array(0) const peerData = await this.components.getPeerStore().get(this.components.getPeerId()) @@ -425,6 +441,11 @@ export class IdentifyService implements Startable { const { connection, stream } = data const timeoutController = new TimeoutController(this.init.timeout) + try { + // fails on node < 15.4 + setMaxListeners?.(Infinity, timeoutController.signal) + } catch {} + let message: Identify | undefined try { // make stream abortable diff --git a/src/ping/index.ts b/src/ping/index.ts index 98c249093a..8eb83fb121 100644 --- a/src/ping/index.ts +++ b/src/ping/index.ts @@ -14,6 +14,7 @@ import type { AbortOptions } from '@libp2p/interfaces' import { abortableDuplex } from 'abortable-iterator' import { TimeoutController } from 'timeout-abort-controller' import type { Stream } from '@libp2p/interface-connection' +import { setMaxListeners } from 'events' const log = logger('libp2p:ping') @@ -90,6 +91,11 @@ export class PingService implements Startable { if (signal == null) { timeoutController = new TimeoutController(this.init.timeout) signal = timeoutController.signal + + try { + // fails on node < 15.4 + setMaxListeners?.(Infinity, timeoutController.signal) + } catch {} } try { diff --git a/src/upgrader.ts b/src/upgrader.ts index 80cb742f17..e659d3ae7c 100644 --- a/src/upgrader.ts +++ b/src/upgrader.ts @@ -20,6 +20,7 @@ import type { Registrar } from '@libp2p/interface-registrar' import { DEFAULT_MAX_INBOUND_STREAMS, DEFAULT_MAX_OUTBOUND_STREAMS } from './registrar.js' import { TimeoutController } from 'timeout-abort-controller' import { abortableDuplex } from 'abortable-iterator' +import { setMaxListeners } from 'events' const log = logger('libp2p:upgrader') @@ -133,6 +134,11 @@ export class DefaultUpgrader extends EventEmitter implements Upg const timeoutController = new TimeoutController(this.inboundUpgradeTimeout) + try { + // fails on node < 15.4 + setMaxListeners?.(Infinity, timeoutController.signal) + } catch {} + try { const abortableStream = abortableDuplex(maConn, timeoutController.signal) maConn.source = abortableStream.source @@ -407,6 +413,11 @@ export class DefaultUpgrader extends EventEmitter implements Upg controller = new TimeoutController(30000) options.signal = controller.signal + + try { + // fails on node < 15.4 + setMaxListeners?.(Infinity, controller.signal) + } catch {} } let { stream, protocol } = await mss.select(protocols, options) From 54450d434202ed6a9dc2352774e1e2047f81074f Mon Sep 17 00:00:00 2001 From: Roshan Singh <42250206+kalikho@users.noreply.github.com> Date: Thu, 21 Jul 2022 18:16:39 +0530 Subject: [PATCH 395/447] docs: removed talks section (#1300) Removed "Talks" section as links are broken. --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index e48170726d..c6a4ace284 100644 --- a/README.md +++ b/README.md @@ -66,8 +66,6 @@ We are in the process of writing better documentation, blog posts, tutorials and - [docs.libp2p.io](https://docs.libp2p.io) - [Specification (WIP)](https://github.com/libp2p/specs) - [Discussion Forums](https://discuss.libp2p.io) -- Talks - - [`libp2p <3 ethereum` at DEVCON2](https://ethereumfoundation.org/devcon/?session=libp2p) [📼 video](https://www.youtube.com/watch?v=HxueJbeMVG4) [slides](https://ethereumfoundation.org/devcon/wp-content/uploads/2016/10/libp2p-HEART-devp2p-IPFS-PLUS-Ethereum-networking.pdf) [📼 demo-1](https://ethereumfoundation.org/devcon/wp-content/uploads/2016/10/libp2p_demo1-1.mp4) [📼 demo-2](https://ethereumfoundation.org/devcon/wp-content/uploads/2016/10/libp2p_demo2-1.mp4) - Articles - [The overview of libp2p](https://github.com/libp2p/libp2p#description) From 3c0fb13babe295c8e5284345080bd4434f39efa7 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 22 Jul 2022 13:57:01 +0100 Subject: [PATCH 396/447] fix: close streams when protocol limits are reached (#1301) - If a stream is opened that exceeds inbound/outbound limits, reset that stream (if it is incoming) or abort and throw (if it is outgoing) - Make the error message more helpful (say which protocol has breached the limit) - Increase the default stream limits so we don't trigger this by accident when a remote dials us with a protocol we don't support --- examples/delegated-routing/package.json | 6 +++--- examples/libp2p-in-the-browser/package.json | 6 +++--- examples/webrtc-direct/package.json | 4 ++-- package.json | 6 +++--- src/registrar.ts | 4 ++-- src/upgrader.ts | 9 +++++++-- test/identify/service.spec.ts | 10 +++++++++- test/upgrading/upgrader.spec.ts | 2 +- 8 files changed, 30 insertions(+), 17 deletions(-) diff --git a/examples/delegated-routing/package.json b/examples/delegated-routing/package.json index 3892af938f..4da13155b5 100644 --- a/examples/delegated-routing/package.json +++ b/examples/delegated-routing/package.json @@ -3,14 +3,14 @@ "version": "0.1.0", "private": true, "dependencies": { - "@chainsafe/libp2p-noise": "^6.2.0", + "@chainsafe/libp2p-noise": "^7.0.1", "ipfs-core": "^0.14.1", "libp2p": "../../", "@libp2p/delegated-content-routing": "^2.0.1", "@libp2p/delegated-peer-routing": "^2.0.1", "@libp2p/kad-dht": "^3.0.0", - "@libp2p/mplex": "^3.0.0", - "@libp2p/webrtc-star": "^2.0.1", + "@libp2p/mplex": "^4.0.1", + "@libp2p/webrtc-star": "^3.0.0", "@libp2p/websockets": "^3.0.0", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json index 4736108cae..a8fdd3ab0b 100644 --- a/examples/libp2p-in-the-browser/package.json +++ b/examples/libp2p-in-the-browser/package.json @@ -9,10 +9,10 @@ }, "license": "ISC", "dependencies": { - "@chainsafe/libp2p-noise": "^6.2.0", + "@chainsafe/libp2p-noise": "^7.0.1", "@libp2p/bootstrap": "^2.0.0", - "@libp2p/mplex": "^3.0.0", - "@libp2p/webrtc-star": "^2.0.1", + "@libp2p/mplex": "^4.0.1", + "@libp2p/webrtc-star": "^3.0.0", "@libp2p/websockets": "^3.0.0", "libp2p": "../../" }, diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index 57d33ee0a5..6c09a59be7 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -10,9 +10,9 @@ "license": "ISC", "dependencies": { "@libp2p/webrtc-direct": "^2.0.0", - "@chainsafe/libp2p-noise": "^6.2.0", + "@chainsafe/libp2p-noise": "^7.0.1", "@libp2p/bootstrap": "^2.0.0", - "@libp2p/mplex": "^3.0.0", + "@libp2p/mplex": "^4.0.1", "libp2p": "../../", "wrtc": "^0.4.7" }, diff --git a/package.json b/package.json index f254345301..6d06b3cc06 100644 --- a/package.json +++ b/package.json @@ -171,8 +171,8 @@ "@libp2p/bootstrap": "^2.0.0", "@libp2p/daemon-client": "^2.0.0", "@libp2p/daemon-server": "^2.0.0", - "@libp2p/delegated-content-routing": "^2.0.0", - "@libp2p/delegated-peer-routing": "^2.0.0", + "@libp2p/delegated-content-routing": "^2.0.1", + "@libp2p/delegated-peer-routing": "^2.0.1", "@libp2p/floodsub": "^3.0.0", "@libp2p/interface-compliance-tests": "^3.0.1", "@libp2p/interface-connection-encrypter-compliance-tests": "^1.0.0", @@ -180,7 +180,7 @@ "@libp2p/interop": "^2.0.0", "@libp2p/kad-dht": "^3.0.0", "@libp2p/mdns": "^3.0.0", - "@libp2p/mplex": "^4.0.0", + "@libp2p/mplex": "^4.0.1", "@libp2p/pubsub": "^3.0.1", "@libp2p/tcp": "^3.0.0", "@libp2p/topology": "^3.0.0", diff --git a/src/registrar.ts b/src/registrar.ts index d9304d2574..edf871eedf 100644 --- a/src/registrar.ts +++ b/src/registrar.ts @@ -10,8 +10,8 @@ import type { Components } from '@libp2p/components' const log = logger('libp2p:registrar') -export const DEFAULT_MAX_INBOUND_STREAMS = 1 -export const DEFAULT_MAX_OUTBOUND_STREAMS = 1 +export const DEFAULT_MAX_INBOUND_STREAMS = 32 +export const DEFAULT_MAX_OUTBOUND_STREAMS = 64 /** * Responsible for notifying registered protocols of events in the network. diff --git a/src/upgrader.ts b/src/upgrader.ts index e659d3ae7c..1c5919fe1a 100644 --- a/src/upgrader.ts +++ b/src/upgrader.ts @@ -370,7 +370,9 @@ export class DefaultUpgrader extends EventEmitter implements Upg const streamCount = countStreams(protocol, 'inbound', connection) if (streamCount === incomingLimit) { - throw errCode(new Error('Too many incoming protocol streams'), codes.ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS) + muxedStream.abort(errCode(new Error(`Too many inbound protocol streams for protocol "${protocol}" - limit ${incomingLimit}`), codes.ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS)) + + return } muxedStream.stat.protocol = protocol @@ -430,7 +432,10 @@ export class DefaultUpgrader extends EventEmitter implements Upg const streamCount = countStreams(protocol, 'outbound', connection) if (streamCount === outgoingLimit) { - throw errCode(new Error('Too many outgoing protocol streams'), codes.ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS) + const err = errCode(new Error(`Too many outbound protocol streams for protocol "${protocol}" - limit ${outgoingLimit}`), codes.ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS) + muxedStream.abort(err) + + throw err } muxedStream.stat.protocol = protocol diff --git a/test/identify/service.spec.ts b/test/identify/service.spec.ts index 152d00c24d..f63541b070 100644 --- a/test/identify/service.spec.ts +++ b/test/identify/service.spec.ts @@ -128,11 +128,19 @@ describe('libp2p.dialer.identifyService', () => { await identityServiceIdentifySpy.firstCall.returnValue sinon.stub(libp2p, 'isStarted').returns(true) + // Cause supported protocols to change await libp2p.handle('/echo/2.0.0', () => {}) + + // Wait for push to complete + await pWaitFor(() => identityServicePushSpy.callCount === 1) + await identityServicePushSpy.firstCall.returnValue + + // Cause supported protocols to change back await libp2p.unhandle('/echo/2.0.0') - // the protocol change event listener in the identity service is async + // Wait for push to complete a second time await pWaitFor(() => identityServicePushSpy.callCount === 2) + await identityServicePushSpy.secondCall.returnValue // Verify the remote peer is notified of both changes expect(identityServicePushSpy.callCount).to.equal(2) diff --git a/test/upgrading/upgrader.spec.ts b/test/upgrading/upgrader.spec.ts index e21ca28242..d16996af56 100644 --- a/test/upgrading/upgrader.spec.ts +++ b/test/upgrading/upgrader.spec.ts @@ -654,7 +654,7 @@ describe('libp2p.upgrader', () => { expect(streamCount).to.equal(1) await expect(localToRemote.newStream(protocol)).to.eventually.be.rejected() - .with.property('code', 'ERR_UNDER_READ') + .with.property('code', 'ERR_MPLEX_STREAM_RESET') }) it('should limit the number of outgoing streams that can be opened using a protocol', async () => { From 05e8e7ead96d494bdd7dfa5d6430155670066767 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Mon, 25 Jul 2022 13:37:31 +0100 Subject: [PATCH 397/447] fix: remove mplex prefix from muxer errors (#1304) Muxer errors are now standard across implementations so do not depend on the `"MPLEX_"` prefix --- examples/delegated-routing/package.json | 2 +- examples/libp2p-in-the-browser/package.json | 2 +- examples/webrtc-direct/package.json | 2 +- package.json | 2 +- test/relay/relay.node.ts | 2 +- test/upgrading/upgrader.spec.ts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/delegated-routing/package.json b/examples/delegated-routing/package.json index 4da13155b5..04ff5e32e3 100644 --- a/examples/delegated-routing/package.json +++ b/examples/delegated-routing/package.json @@ -9,7 +9,7 @@ "@libp2p/delegated-content-routing": "^2.0.1", "@libp2p/delegated-peer-routing": "^2.0.1", "@libp2p/kad-dht": "^3.0.0", - "@libp2p/mplex": "^4.0.1", + "@libp2p/mplex": "^4.0.2", "@libp2p/webrtc-star": "^3.0.0", "@libp2p/websockets": "^3.0.0", "react": "^17.0.2", diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json index a8fdd3ab0b..57e4fe1a68 100644 --- a/examples/libp2p-in-the-browser/package.json +++ b/examples/libp2p-in-the-browser/package.json @@ -11,7 +11,7 @@ "dependencies": { "@chainsafe/libp2p-noise": "^7.0.1", "@libp2p/bootstrap": "^2.0.0", - "@libp2p/mplex": "^4.0.1", + "@libp2p/mplex": "^4.0.2", "@libp2p/webrtc-star": "^3.0.0", "@libp2p/websockets": "^3.0.0", "libp2p": "../../" diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index 6c09a59be7..f355efd3c5 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -12,7 +12,7 @@ "@libp2p/webrtc-direct": "^2.0.0", "@chainsafe/libp2p-noise": "^7.0.1", "@libp2p/bootstrap": "^2.0.0", - "@libp2p/mplex": "^4.0.1", + "@libp2p/mplex": "^4.0.2", "libp2p": "../../", "wrtc": "^0.4.7" }, diff --git a/package.json b/package.json index 6d06b3cc06..93188b34de 100644 --- a/package.json +++ b/package.json @@ -180,7 +180,7 @@ "@libp2p/interop": "^2.0.0", "@libp2p/kad-dht": "^3.0.0", "@libp2p/mdns": "^3.0.0", - "@libp2p/mplex": "^4.0.1", + "@libp2p/mplex": "^4.0.2", "@libp2p/pubsub": "^3.0.1", "@libp2p/tcp": "^3.0.0", "@libp2p/topology": "^3.0.0", diff --git a/test/relay/relay.node.ts b/test/relay/relay.node.ts index 4249f01b96..c99bf80319 100644 --- a/test/relay/relay.node.ts +++ b/test/relay/relay.node.ts @@ -202,6 +202,6 @@ describe('Dialing (via relay, TCP)', () => { // because we timed out, the remote should have reset the stream await expect(all(stream.source)).to.eventually.be.rejected - .with.property('code', 'ERR_MPLEX_STREAM_RESET') + .with.property('code', 'ERR_STREAM_RESET') }) }) diff --git a/test/upgrading/upgrader.spec.ts b/test/upgrading/upgrader.spec.ts index d16996af56..9a8bf95e74 100644 --- a/test/upgrading/upgrader.spec.ts +++ b/test/upgrading/upgrader.spec.ts @@ -654,7 +654,7 @@ describe('libp2p.upgrader', () => { expect(streamCount).to.equal(1) await expect(localToRemote.newStream(protocol)).to.eventually.be.rejected() - .with.property('code', 'ERR_MPLEX_STREAM_RESET') + .with.property('code', 'ERR_STREAM_RESET') }) it('should limit the number of outgoing streams that can be opened using a protocol', async () => { From 564f4b8aa77dffc7c3744aa97c5cb217f42c56bc Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 3 Aug 2022 14:15:35 +0100 Subject: [PATCH 398/447] deps: update it-length-prefix, uint8arraylist etc (#1317) In order to support no-copy operations in streams, update all deps to support streaming Uint8ArrayLists. --- examples/auto-relay/test.js | 2 +- examples/chat/src/stream.js | 2 +- examples/chat/test.js | 2 +- examples/discovery-mechanisms/test-2.js | 2 +- examples/discovery-mechanisms/test-3.js | 2 +- examples/echo/test.js | 2 +- examples/libp2p-in-the-browser/README.md | 8 ++++---- examples/libp2p-in-the-browser/index.html | 2 +- examples/libp2p-in-the-browser/test.js | 2 +- examples/package.json | 10 +++++----- examples/pubsub/message-filtering/test.js | 2 +- examples/pubsub/test-1.js | 2 +- examples/test.js | 6 ++++-- examples/utils.js | 7 +++++-- examples/webrtc-direct/index.html | 2 +- examples/webrtc-direct/package.json | 4 ++-- examples/webrtc-direct/test.js | 2 +- package.json | 16 ++++++++-------- src/circuit/circuit/stream-handler.ts | 5 +++-- src/circuit/pb/index.ts | 9 +++++---- src/fetch/pb/proto.ts | 9 +++++---- src/identify/index.ts | 2 +- src/identify/pb/message.ts | 5 +++-- src/insecure/index.ts | 2 +- src/insecure/pb/proto.ts | 9 +++++---- test/configuration/utils.ts | 9 +++++---- test/relay/auto-relay.node.ts | 2 +- 27 files changed, 69 insertions(+), 58 deletions(-) diff --git a/examples/auto-relay/test.js b/examples/auto-relay/test.js index 4c7541b962..646dbce2b3 100644 --- a/examples/auto-relay/test.js +++ b/examples/auto-relay/test.js @@ -1,5 +1,5 @@ import path from 'path' -import execa from 'execa' +import { execa } from 'execa' import pDefer from 'p-defer' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { fileURLToPath } from 'url' diff --git a/examples/chat/src/stream.js b/examples/chat/src/stream.js index 391ac9868d..9263c128aa 100644 --- a/examples/chat/src/stream.js +++ b/examples/chat/src/stream.js @@ -28,7 +28,7 @@ export function streamToConsole(stream) { // Decode length-prefixed data lp.decode(), // Turn buffers into strings - (source) => map(source, (buf) => uint8ArrayToString(buf)), + (source) => map(source, (buf) => uint8ArrayToString(buf.subarray())), // Sink function async function (source) { // For each chunk of data diff --git a/examples/chat/test.js b/examples/chat/test.js index dd75cb91b7..179bf3ead6 100644 --- a/examples/chat/test.js +++ b/examples/chat/test.js @@ -1,5 +1,5 @@ import path from 'path' -import execa from 'execa' +import { execa } from 'execa' import pDefer from 'p-defer' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { fileURLToPath } from 'url' diff --git a/examples/discovery-mechanisms/test-2.js b/examples/discovery-mechanisms/test-2.js index b40da4a332..9cef3bfd24 100644 --- a/examples/discovery-mechanisms/test-2.js +++ b/examples/discovery-mechanisms/test-2.js @@ -1,5 +1,5 @@ import path from 'path' -import execa from 'execa' +import { execa } from 'execa' import pWaitFor from 'p-wait-for' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { fileURLToPath } from 'url' diff --git a/examples/discovery-mechanisms/test-3.js b/examples/discovery-mechanisms/test-3.js index 375ef6f8ab..24a033110d 100644 --- a/examples/discovery-mechanisms/test-3.js +++ b/examples/discovery-mechanisms/test-3.js @@ -1,5 +1,5 @@ import path from 'path' -import execa from 'execa' +import { execa } from 'execa' import pWaitFor from 'p-wait-for' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { fileURLToPath } from 'url' diff --git a/examples/echo/test.js b/examples/echo/test.js index 927b8e986c..2fbae5c111 100644 --- a/examples/echo/test.js +++ b/examples/echo/test.js @@ -1,5 +1,5 @@ import path from 'path' -import execa from 'execa' +import { execa } from 'execa' import pDefer from 'p-defer' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { fileURLToPath } from 'url' diff --git a/examples/libp2p-in-the-browser/README.md b/examples/libp2p-in-the-browser/README.md index 9c5513f2d6..d2185b2d49 100644 --- a/examples/libp2p-in-the-browser/README.md +++ b/examples/libp2p-in-the-browser/README.md @@ -1,6 +1,6 @@ # libp2p in the browser -This example leverages the [Parcel.js bundler](https://parceljs.org/) to compile and serve the libp2p code in the browser. Parcel uses [Babel](https://babeljs.io/) to handle transpilation of the code. You can use other bundlers such as Webpack or Browserify, but we will not be covering them here. +This example leverages the [vite bundler](https://vitejs.dev/) to compile and serve the libp2p code in the browser. You can use other bundlers such as Webpack, but we will not be covering them here. ## Setup @@ -18,7 +18,7 @@ npm install ## Running the examples -Start by running the Parcel server: +Start by running the vite server: ``` npm start @@ -30,7 +30,7 @@ The output should look something like this: $ npm start > libp2p-in-browser@1.0.0 start -> parcel index.html +> vite index.html Server running at http://localhost:1234 ✨ Built in 1000ms. @@ -40,7 +40,7 @@ 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. +**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 vite will automatically rebuild and reload the browser tabs. ## Going to production? diff --git a/examples/libp2p-in-the-browser/index.html b/examples/libp2p-in-the-browser/index.html index 1b1f48da60..2a1fbe7820 100644 --- a/examples/libp2p-in-the-browser/index.html +++ b/examples/libp2p-in-the-browser/index.html @@ -4,7 +4,7 @@ - js-libp2p parcel.js browser example + js-libp2p vite browser example diff --git a/examples/libp2p-in-the-browser/test.js b/examples/libp2p-in-the-browser/test.js index 0b3a16a6fe..4097ad8544 100644 --- a/examples/libp2p-in-the-browser/test.js +++ b/examples/libp2p-in-the-browser/test.js @@ -1,4 +1,4 @@ -import execa from 'execa' +import { execa } from 'execa' import { chromium } from 'playwright' import path from 'path' import { fileURLToPath } from 'url' diff --git a/examples/package.json b/examples/package.json index a699ca30c0..6e86102b1e 100644 --- a/examples/package.json +++ b/examples/package.json @@ -9,13 +9,13 @@ }, "license": "MIT", "dependencies": { - "@libp2p/pubsub-peer-discovery": "^6.0.0", - "@libp2p/floodsub": "^3.0.0", + "@libp2p/pubsub-peer-discovery": "^6.0.1", + "@libp2p/floodsub": "^3.0.3", "@nodeutils/defaults-deep": "^1.1.0", - "execa": "^2.1.0", - "fs-extra": "^8.1.0", + "execa": "^6.1.0", + "fs-extra": "^10.1.0", "libp2p": "../", - "p-defer": "^3.0.0", + "p-defer": "^4.0.0", "uint8arrays": "^3.0.0", "which": "^2.0.1" }, diff --git a/examples/pubsub/message-filtering/test.js b/examples/pubsub/message-filtering/test.js index 4cb9fbaa15..97736f4985 100644 --- a/examples/pubsub/message-filtering/test.js +++ b/examples/pubsub/message-filtering/test.js @@ -1,5 +1,5 @@ import path from 'path' -import execa from 'execa' +import { execa } from 'execa' import pDefer from 'p-defer' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { fileURLToPath } from 'url' diff --git a/examples/pubsub/test-1.js b/examples/pubsub/test-1.js index c818a573d9..e863307154 100644 --- a/examples/pubsub/test-1.js +++ b/examples/pubsub/test-1.js @@ -1,5 +1,5 @@ import path from 'path' -import execa from 'execa' +import { execa } from 'execa' import pDefer from 'p-defer' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { fileURLToPath } from 'url' diff --git a/examples/test.js b/examples/test.js index 5de9963449..e6d625c8be 100644 --- a/examples/test.js +++ b/examples/test.js @@ -3,7 +3,7 @@ process.env.CI = true // needed for some "clever" build tools import fs from 'fs-extra' import path from 'path' -import execa from 'execa' +import { execa } from 'execa' import { fileURLToPath } from 'url' const __dirname = path.dirname(fileURLToPath(import.meta.url)) @@ -36,7 +36,8 @@ async function installDeps (dir) { return } - const proc = execa.command('npm install', { + const proc = execa('npm', ['install'], { + all: true, cwd: dir }) proc.all.on('data', (data) => { @@ -71,6 +72,7 @@ async function build (dir) { } const proc = execa('npm', ['run', build], { + all: true, cwd: dir }) proc.all.on('data', (data) => { diff --git a/examples/utils.js b/examples/utils.js index 914d91e9bb..e6118cc5bb 100644 --- a/examples/utils.js +++ b/examples/utils.js @@ -1,4 +1,4 @@ -import execa from 'execa' +import { execa } from 'execa' import fs from 'fs-extra' import which from 'which' @@ -26,7 +26,10 @@ export async function waitForOutput (expectedOutput, command, args = [], opts = command = 'node' } - const proc = execa(command, args, opts) + const proc = execa(command, args, { + ...opts, + all: true + }) let output = '' let time = 600000 diff --git a/examples/webrtc-direct/index.html b/examples/webrtc-direct/index.html index 3fcf9c35d7..0da84a2f5f 100644 --- a/examples/webrtc-direct/index.html +++ b/examples/webrtc-direct/index.html @@ -2,7 +2,7 @@ - js-libp2p parcel.js browser example + js-libp2p vite browser example diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index f355efd3c5..664488dd28 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -10,9 +10,9 @@ "license": "ISC", "dependencies": { "@libp2p/webrtc-direct": "^2.0.0", - "@chainsafe/libp2p-noise": "^7.0.1", + "@chainsafe/libp2p-noise": "^7.0.3", "@libp2p/bootstrap": "^2.0.0", - "@libp2p/mplex": "^4.0.2", + "@libp2p/mplex": "^4.0.3", "libp2p": "../../", "wrtc": "^0.4.7" }, diff --git a/examples/webrtc-direct/test.js b/examples/webrtc-direct/test.js index 513a21e928..23181b879f 100644 --- a/examples/webrtc-direct/test.js +++ b/examples/webrtc-direct/test.js @@ -1,5 +1,5 @@ import path from 'path' -import execa from 'execa' +import { execa } from 'execa' import pDefer from 'p-defer' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { chromium } from 'playwright' diff --git a/package.json b/package.json index 93188b34de..a8071c1ec5 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,6 @@ "generate:proto:fetch": "protons ./src/fetch/pb/proto.proto", "generate:proto:identify": "protons ./src/identify/pb/message.proto", "generate:proto:plaintext": "protons ./src/insecure/pb/proto.proto", - "generate:proto:tags": "protons ./src/connection-manager/tags/tags.proto", "test": "aegir test", "test:node": "aegir test -t node -f \"./dist/test/**/*.{node,spec}.js\" --cov", "test:chrome": "aegir test -t browser -f \"./dist/test/**/*.spec.js\" --cov", @@ -113,7 +112,7 @@ "@libp2p/interface-peer-info": "^1.0.1", "@libp2p/interface-peer-routing": "^1.0.0", "@libp2p/interface-peer-store": "^1.2.0", - "@libp2p/interface-pubsub": "^1.0.3", + "@libp2p/interface-pubsub": "^2.0.0", "@libp2p/interface-registrar": "^2.0.0", "@libp2p/interface-stream-muxer": "^2.0.1", "@libp2p/interface-transport": "^1.0.0", @@ -123,7 +122,7 @@ "@libp2p/peer-collections": "^2.0.0", "@libp2p/peer-id": "^1.1.10", "@libp2p/peer-id-factory": "^1.0.9", - "@libp2p/peer-record": "^3.0.0", + "@libp2p/peer-record": "^4.0.0", "@libp2p/peer-store": "^3.0.0", "@libp2p/tracked-map": "^2.0.1", "@libp2p/utils": "^3.0.0", @@ -142,7 +141,7 @@ "it-first": "^1.0.6", "it-foreach": "^0.1.1", "it-handshake": "^4.0.0", - "it-length-prefixed": "^7.0.1", + "it-length-prefixed": "^8.0.2", "it-map": "^1.0.6", "it-merge": "^1.0.3", "it-pair": "^2.0.2", @@ -157,17 +156,18 @@ "p-retry": "^5.0.0", "p-settle": "^5.0.0", "private-ip": "^2.3.3", - "protons-runtime": "^1.0.4", + "protons-runtime": "^2.0.2", "retimer": "^3.0.0", "sanitize-filename": "^1.6.3", "set-delayed-interval": "^1.0.0", "timeout-abort-controller": "^3.0.0", + "uint8arraylist": "^2.0.0", "uint8arrays": "^3.0.0", "wherearewe": "^1.0.0", "xsalsa20": "^1.1.0" }, "devDependencies": { - "@chainsafe/libp2p-noise": "^7.0.1", + "@chainsafe/libp2p-noise": "^7.0.2", "@libp2p/bootstrap": "^2.0.0", "@libp2p/daemon-client": "^2.0.0", "@libp2p/daemon-server": "^2.0.0", @@ -204,8 +204,8 @@ "p-defer": "^4.0.0", "p-event": "^5.0.1", "p-times": "^4.0.0", - "p-wait-for": "^4.1.0", - "protons": "^3.0.4", + "p-wait-for": "^5.0.0", + "protons": "^4.0.1", "rimraf": "^3.0.2", "sinon": "^14.0.0", "ts-sinon": "^2.0.2" diff --git a/src/circuit/circuit/stream-handler.ts b/src/circuit/circuit/stream-handler.ts index 5dcb36ebf3..0935d5d689 100644 --- a/src/circuit/circuit/stream-handler.ts +++ b/src/circuit/circuit/stream-handler.ts @@ -4,6 +4,7 @@ import { Handshake, handshake } from 'it-handshake' import { CircuitRelay } from '../pb/index.js' import type { Stream } from '@libp2p/interface-connection' import type { Source } from 'it-stream-types' +import type { Uint8ArrayList } from 'uint8arraylist' const log = logger('libp2p:circuit:stream-handler') @@ -22,7 +23,7 @@ export interface StreamHandlerOptions { export class StreamHandler { private readonly stream: Stream private readonly shake: Handshake - private readonly decoder: Source + private readonly decoder: Source constructor (options: StreamHandlerOptions) { const { stream, maxLength = 4096 } = options @@ -40,7 +41,7 @@ export class StreamHandler { const msg = await this.decoder.next() if (msg.value != null) { - const value = CircuitRelay.decode(msg.value.slice()) + const value = CircuitRelay.decode(msg.value) log('read message type', value.type) return value } diff --git a/src/circuit/pb/index.ts b/src/circuit/pb/index.ts index 71ee1b9d29..ec47246aab 100644 --- a/src/circuit/pb/index.ts +++ b/src/circuit/pb/index.ts @@ -3,6 +3,7 @@ import { enumeration, encodeMessage, decodeMessage, message, bytes } from 'protons-runtime' import type { Codec } from 'protons-runtime' +import type { Uint8ArrayList } from 'uint8arraylist' export interface CircuitRelay { type?: CircuitRelay.Type @@ -89,11 +90,11 @@ export namespace CircuitRelay { }) } - export const encode = (obj: Peer): Uint8Array => { + export const encode = (obj: Peer): Uint8ArrayList => { return encodeMessage(obj, Peer.codec()) } - export const decode = (buf: Uint8Array): Peer => { + export const decode = (buf: Uint8Array | Uint8ArrayList): Peer => { return decodeMessage(buf, Peer.codec()) } } @@ -107,11 +108,11 @@ export namespace CircuitRelay { }) } - export const encode = (obj: CircuitRelay): Uint8Array => { + export const encode = (obj: CircuitRelay): Uint8ArrayList => { return encodeMessage(obj, CircuitRelay.codec()) } - export const decode = (buf: Uint8Array): CircuitRelay => { + export const decode = (buf: Uint8Array | Uint8ArrayList): CircuitRelay => { return decodeMessage(buf, CircuitRelay.codec()) } } diff --git a/src/fetch/pb/proto.ts b/src/fetch/pb/proto.ts index 919f94a73f..608559358b 100644 --- a/src/fetch/pb/proto.ts +++ b/src/fetch/pb/proto.ts @@ -3,6 +3,7 @@ import { encodeMessage, decodeMessage, message, string, enumeration, bytes } from 'protons-runtime' import type { Codec } from 'protons-runtime' +import type { Uint8ArrayList } from 'uint8arraylist' export interface FetchRequest { identifier: string @@ -15,11 +16,11 @@ export namespace FetchRequest { }) } - export const encode = (obj: FetchRequest): Uint8Array => { + export const encode = (obj: FetchRequest): Uint8ArrayList => { return encodeMessage(obj, FetchRequest.codec()) } - export const decode = (buf: Uint8Array): FetchRequest => { + export const decode = (buf: Uint8Array | Uint8ArrayList): FetchRequest => { return decodeMessage(buf, FetchRequest.codec()) } } @@ -55,11 +56,11 @@ export namespace FetchResponse { }) } - export const encode = (obj: FetchResponse): Uint8Array => { + export const encode = (obj: FetchResponse): Uint8ArrayList => { return encodeMessage(obj, FetchResponse.codec()) } - export const decode = (buf: Uint8Array): FetchResponse => { + export const decode = (buf: Uint8Array | Uint8ArrayList): FetchResponse => { return decodeMessage(buf, FetchResponse.codec()) } } diff --git a/src/identify/index.ts b/src/identify/index.ts index f868a631ac..ba82aa8178 100644 --- a/src/identify/index.ts +++ b/src/identify/index.ts @@ -404,7 +404,7 @@ export class IdentifyService implements Startable { const envelope = await RecordEnvelope.seal(peerRecord, this.components.getPeerId()) await this.components.getPeerStore().addressBook.consumePeerRecord(envelope) - signedPeerRecord = envelope.marshal() + signedPeerRecord = envelope.marshal().subarray() } const message = Identify.encode({ diff --git a/src/identify/pb/message.ts b/src/identify/pb/message.ts index 04aad2a8b9..1f9072d677 100644 --- a/src/identify/pb/message.ts +++ b/src/identify/pb/message.ts @@ -3,6 +3,7 @@ import { encodeMessage, decodeMessage, message, string, bytes } from 'protons-runtime' import type { Codec } from 'protons-runtime' +import type { Uint8ArrayList } from 'uint8arraylist' export interface Identify { protocolVersion?: string @@ -27,11 +28,11 @@ export namespace Identify { }) } - export const encode = (obj: Identify): Uint8Array => { + export const encode = (obj: Identify): Uint8ArrayList => { return encodeMessage(obj, Identify.codec()) } - export const decode = (buf: Uint8Array): Identify => { + export const decode = (buf: Uint8Array | Uint8ArrayList): Identify => { return decodeMessage(buf, Identify.codec()) } } diff --git a/src/insecure/index.ts b/src/insecure/index.ts index 446104e0ca..3ffd6618be 100644 --- a/src/insecure/index.ts +++ b/src/insecure/index.ts @@ -39,7 +39,7 @@ async function encrypt (localId: PeerId, conn: Duplex, remoteId?: Pe Type: type, Data: localId.publicKey ?? new Uint8Array(0) } - }).slice() + }).subarray() ) log('write pubkey exchange to peer %p', remoteId) diff --git a/src/insecure/pb/proto.ts b/src/insecure/pb/proto.ts index 24b0020a7a..ec84391110 100644 --- a/src/insecure/pb/proto.ts +++ b/src/insecure/pb/proto.ts @@ -3,6 +3,7 @@ import { encodeMessage, decodeMessage, message, bytes, enumeration } from 'protons-runtime' import type { Codec } from 'protons-runtime' +import type { Uint8ArrayList } from 'uint8arraylist' export interface Exchange { id?: Uint8Array @@ -17,11 +18,11 @@ export namespace Exchange { }) } - export const encode = (obj: Exchange): Uint8Array => { + export const encode = (obj: Exchange): Uint8ArrayList => { return encodeMessage(obj, Exchange.codec()) } - export const decode = (buf: Uint8Array): Exchange => { + export const decode = (buf: Uint8Array | Uint8ArrayList): Exchange => { return decodeMessage(buf, Exchange.codec()) } } @@ -58,11 +59,11 @@ export namespace PublicKey { }) } - export const encode = (obj: PublicKey): Uint8Array => { + export const encode = (obj: PublicKey): Uint8ArrayList => { return encodeMessage(obj, PublicKey.codec()) } - export const decode = (buf: Uint8Array): PublicKey => { + export const decode = (buf: Uint8Array | Uint8ArrayList): PublicKey => { return decodeMessage(buf, PublicKey.codec()) } } diff --git a/test/configuration/utils.ts b/test/configuration/utils.ts index a34385c9e3..bc587e5cd9 100644 --- a/test/configuration/utils.ts +++ b/test/configuration/utils.ts @@ -10,6 +10,7 @@ import type { Libp2pInit, Libp2pOptions } from '../../src/index.js' import type { PeerId } from '@libp2p/interface-peer-id' import * as cborg from 'cborg' import { peerIdFromString } from '@libp2p/peer-id' +import { Uint8ArrayList } from 'uint8arraylist' const relayAddr = MULTIADDRS_WEBSOCKETS[0] @@ -32,16 +33,16 @@ class MockPubSub extends PubSubBaseProtocol { return cborg.decode(bytes) } - encodeRpc (rpc: PubSubRPC): Uint8Array { - return cborg.encode(rpc) + encodeRpc (rpc: PubSubRPC): Uint8ArrayList { + return new Uint8ArrayList(cborg.encode(rpc)) } decodeMessage (bytes: Uint8Array): PubSubRPCMessage { return cborg.decode(bytes) } - encodeMessage (rpc: PubSubRPCMessage): Uint8Array { - return cborg.encode(rpc) + encodeMessage (rpc: PubSubRPCMessage): Uint8ArrayList { + return new Uint8ArrayList(cborg.encode(rpc)) } async publishMessage (from: PeerId, message: Message): Promise { diff --git a/test/relay/auto-relay.node.ts b/test/relay/auto-relay.node.ts index 1363144e85..6dd8e0f484 100644 --- a/test/relay/auto-relay.node.ts +++ b/test/relay/auto-relay.node.ts @@ -16,7 +16,7 @@ import type Sinon from 'sinon' import { createRelayOptions, createNodeOptions } from './utils.js' import { protocols } from '@multiformats/multiaddr' -async function usingAsRelay (node: Libp2pNode, relay: Libp2pNode, opts?: PWaitForOptions) { +async function usingAsRelay (node: Libp2pNode, relay: Libp2pNode, opts?: PWaitForOptions) { // Wait for peer to be used as a relay await pWaitFor(() => { for (const addr of node.getMultiaddrs()) { From f439d9b589a0a6544b61aca3736e920943ce38b5 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 11 Aug 2022 13:21:04 +0100 Subject: [PATCH 399/447] deps!: update all deps to support no-copy operations (#1335) Updates all deps needed to support passing lists of byte arrays where they have been created from multiple input buffers. When reading multiplexed data, all messages arrive in length-prefixed buffers, which means the first few bytes tell the consumer how many bytes long next chunk will be. One length prefixed chunk can be delivered in several payloads from the underlying network transport. The first payload can also include the length prefix and some or all of the data, so we stitch these together in a `Uint8ArrayList` to avoid having to concatenate `Uint8Array`s together. Previously once we'd received enough bytes to satisfy the length prefix we'd concatenate the bytes together, but this is a potentially expensive operation where transports have small message sizes so instead just pass the `Uint8ArrayList` to the consumer and let them decide wether to concatenate or not as some consumers will be smart enough to operate on lists of `Uint8Array`s instead of always requiring a contiguous block of memory. BREAKING CHANGE: Streams are now `Duplex` --- .aegir.js | 4 +- examples/connection-encryption/1.js | 2 +- examples/delegated-routing/package.json | 6 +- examples/echo/src/dialer.js | 2 +- examples/libp2p-in-the-browser/package.json | 4 +- examples/pnet/index.js | 2 +- examples/protocol-and-stream-muxing/1.js | 2 +- examples/protocol-and-stream-muxing/2.js | 2 +- examples/protocol-and-stream-muxing/3.js | 4 +- examples/transports/2.js | 5 + examples/transports/3.js | 2 +- examples/transports/4.js | 2 +- examples/webrtc-direct/package.json | 4 +- package.json | 74 +++++----- src/circuit/README.md | 4 +- src/circuit/circuit/hop.ts | 8 +- src/circuit/circuit/stop.ts | 3 +- src/circuit/circuit/stream-handler.ts | 4 +- src/circuit/pb/index.ts | 149 +++++++++++++++++--- src/circuit/transport.ts | 6 +- src/fetch/pb/proto.ts | 121 ++++++++++++++-- src/identify/index.ts | 7 +- src/identify/pb/message.ts | 123 ++++++++++++++-- src/insecure/index.ts | 9 +- src/insecure/pb/proto.ts | 124 ++++++++++++++-- src/metrics/index.ts | 11 +- src/ping/index.ts | 2 +- src/pnet/index.ts | 2 + src/upgrader.ts | 34 ++--- test/configuration/utils.ts | 9 +- test/core/consume-peer-record.spec.ts | 4 +- test/core/encryption.spec.ts | 4 +- test/core/get-public-key.spec.ts | 4 +- test/core/listening.node.ts | 4 +- test/dialing/direct.node.ts | 22 +-- test/dialing/direct.spec.ts | 20 +-- test/fetch/fetch.node.ts | 4 +- test/metrics/index.node.ts | 3 +- test/transports/transport-manager.spec.ts | 8 +- test/upgrading/upgrader.spec.ts | 22 +-- 40 files changed, 626 insertions(+), 200 deletions(-) diff --git a/.aegir.js b/.aegir.js index 66bccedf2f..c0a745f8e1 100644 --- a/.aegir.js +++ b/.aegir.js @@ -1,6 +1,6 @@ import { WebSockets } from '@libp2p/websockets' import { Mplex } from '@libp2p/mplex' -import { NOISE } from '@chainsafe/libp2p-noise' +import { Noise } from '@chainsafe/libp2p-noise' import { pipe } from 'it-pipe' import { createFromJSON } from '@libp2p/peer-id-factory' @@ -31,7 +31,7 @@ export default { new Mplex() ], connectionEncryption: [ - NOISE, + new Noise(), new Plaintext() ], relay: { diff --git a/examples/connection-encryption/1.js b/examples/connection-encryption/1.js index 0e774fcb99..4f03ff6d57 100644 --- a/examples/connection-encryption/1.js +++ b/examples/connection-encryption/1.js @@ -34,7 +34,7 @@ const createNode = async () => { stream, async function (source) { for await (const msg of source) { - console.log(uint8ArrayToString(msg)) + console.log(uint8ArrayToString(msg.subarray())) } } ) diff --git a/examples/delegated-routing/package.json b/examples/delegated-routing/package.json index 04ff5e32e3..8c7d430290 100644 --- a/examples/delegated-routing/package.json +++ b/examples/delegated-routing/package.json @@ -3,13 +3,13 @@ "version": "0.1.0", "private": true, "dependencies": { - "@chainsafe/libp2p-noise": "^7.0.1", - "ipfs-core": "^0.14.1", + "@chainsafe/libp2p-noise": "^8.0.0", + "ipfs-core": "^0.15.4", "libp2p": "../../", "@libp2p/delegated-content-routing": "^2.0.1", "@libp2p/delegated-peer-routing": "^2.0.1", "@libp2p/kad-dht": "^3.0.0", - "@libp2p/mplex": "^4.0.2", + "@libp2p/mplex": "^5.0.0", "@libp2p/webrtc-star": "^3.0.0", "@libp2p/websockets": "^3.0.0", "react": "^17.0.2", diff --git a/examples/echo/src/dialer.js b/examples/echo/src/dialer.js index 435a9ea220..0982e69efe 100644 --- a/examples/echo/src/dialer.js +++ b/examples/echo/src/dialer.js @@ -51,7 +51,7 @@ async function run() { // For each chunk of data for await (const data of source) { // Output the data - console.log('received echo:', uint8ArrayToString(data)) + console.log('received echo:', uint8ArrayToString(data.subarray())) } } ) diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json index 57e4fe1a68..f53ae096a1 100644 --- a/examples/libp2p-in-the-browser/package.json +++ b/examples/libp2p-in-the-browser/package.json @@ -9,9 +9,9 @@ }, "license": "ISC", "dependencies": { - "@chainsafe/libp2p-noise": "^7.0.1", + "@chainsafe/libp2p-noise": "^8.0.0", "@libp2p/bootstrap": "^2.0.0", - "@libp2p/mplex": "^4.0.2", + "@libp2p/mplex": "^5.0.0", "@libp2p/webrtc-star": "^3.0.0", "@libp2p/websockets": "^3.0.0", "libp2p": "../../" diff --git a/examples/pnet/index.js b/examples/pnet/index.js index fa8268df84..6d3adf0a1d 100644 --- a/examples/pnet/index.js +++ b/examples/pnet/index.js @@ -37,7 +37,7 @@ generateKey(otherSwarmKey) stream, async function (source) { for await (const msg of source) { - console.log(uint8ArrayToString(msg)) + console.log(uint8ArrayToString(msg.subarray())) } } ) diff --git a/examples/protocol-and-stream-muxing/1.js b/examples/protocol-and-stream-muxing/1.js index f1ab2f20df..024aaaab80 100644 --- a/examples/protocol-and-stream-muxing/1.js +++ b/examples/protocol-and-stream-muxing/1.js @@ -36,7 +36,7 @@ const createNode = async () => { stream, async function (source) { for await (const msg of source) { - console.log(uint8ArrayToString(msg)) + console.log(uint8ArrayToString(msg.subarray())) } } ) diff --git a/examples/protocol-and-stream-muxing/2.js b/examples/protocol-and-stream-muxing/2.js index 2605938d20..0b7a332bf6 100644 --- a/examples/protocol-and-stream-muxing/2.js +++ b/examples/protocol-and-stream-muxing/2.js @@ -35,7 +35,7 @@ const createNode = async () => { stream, async function (source) { for await (const msg of source) { - console.log(`from: ${protocol}, msg: ${uint8ArrayToString(msg)}`) + console.log(`from: ${protocol}, msg: ${uint8ArrayToString(msg.subarray())}`) } } ).finally(() => { diff --git a/examples/protocol-and-stream-muxing/3.js b/examples/protocol-and-stream-muxing/3.js index af63bdea8e..84de6b1b25 100644 --- a/examples/protocol-and-stream-muxing/3.js +++ b/examples/protocol-and-stream-muxing/3.js @@ -37,7 +37,7 @@ const createNode = async () => { stream, async function (source) { for await (const msg of source) { - console.log(uint8ArrayToString(msg)) + console.log(uint8ArrayToString(msg.subarray())) } } ) @@ -48,7 +48,7 @@ const createNode = async () => { stream, async function (source) { for await (const msg of source) { - console.log(uint8ArrayToString(msg)) + console.log(uint8ArrayToString(msg.subarray())) } } ) diff --git a/examples/transports/2.js b/examples/transports/2.js index 9dee88780c..2808c82049 100644 --- a/examples/transports/2.js +++ b/examples/transports/2.js @@ -42,6 +42,11 @@ function printAddrs (node, number) { node2.handle('/print', async ({ stream }) => { const result = await pipe( stream, + async function * (source) { + for await (const list of source) { + yield list.subarray() + } + }, toBuffer ) console.log(uint8ArrayToString(result)) diff --git a/examples/transports/3.js b/examples/transports/3.js index 26abf36e98..980570c7ef 100644 --- a/examples/transports/3.js +++ b/examples/transports/3.js @@ -37,7 +37,7 @@ function print ({ stream }) { stream, async function (source) { for await (const msg of source) { - console.log(uint8ArrayToString(msg)) + console.log(uint8ArrayToString(msg.subarray())) } } ) diff --git a/examples/transports/4.js b/examples/transports/4.js index 0e13c569a2..ad35181e75 100644 --- a/examples/transports/4.js +++ b/examples/transports/4.js @@ -57,7 +57,7 @@ function print ({ stream }) { stream, async function (source) { for await (const msg of source) { - console.log(uint8ArrayToString(msg)) + console.log(uint8ArrayToString(msg.subarray())) } } ) diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index 664488dd28..d5be295f4a 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -10,9 +10,9 @@ "license": "ISC", "dependencies": { "@libp2p/webrtc-direct": "^2.0.0", - "@chainsafe/libp2p-noise": "^7.0.3", + "@chainsafe/libp2p-noise": "^8.0.0", "@libp2p/bootstrap": "^2.0.0", - "@libp2p/mplex": "^4.0.3", + "@libp2p/mplex": "^5.0.0", "libp2p": "../../", "wrtc": "^0.4.7" }, diff --git a/package.json b/package.json index a8071c1ec5..1e31ab23b3 100644 --- a/package.json +++ b/package.json @@ -98,36 +98,36 @@ }, "dependencies": { "@achingbrain/nat-port-mapper": "^1.0.3", - "@libp2p/components": "^2.0.1", - "@libp2p/connection": "^4.0.0", - "@libp2p/crypto": "^1.0.0", - "@libp2p/interface-address-manager": "^1.0.1", - "@libp2p/interface-connection": "^2.0.0", - "@libp2p/interface-connection-encrypter": "^1.0.2", - "@libp2p/interface-content-routing": "^1.0.1", - "@libp2p/interface-dht": "^1.0.0", - "@libp2p/interface-metrics": "^2.0.0", - "@libp2p/interface-peer-discovery": "^1.0.0", - "@libp2p/interface-peer-id": "^1.0.2", - "@libp2p/interface-peer-info": "^1.0.1", - "@libp2p/interface-peer-routing": "^1.0.0", - "@libp2p/interface-peer-store": "^1.2.0", - "@libp2p/interface-pubsub": "^2.0.0", - "@libp2p/interface-registrar": "^2.0.0", - "@libp2p/interface-stream-muxer": "^2.0.1", - "@libp2p/interface-transport": "^1.0.0", - "@libp2p/interfaces": "^3.0.2", + "@libp2p/components": "^2.0.3", + "@libp2p/connection": "^4.0.1", + "@libp2p/crypto": "^1.0.3", + "@libp2p/interface-address-manager": "^1.0.2", + "@libp2p/interface-connection": "^3.0.1", + "@libp2p/interface-connection-encrypter": "^2.0.1", + "@libp2p/interface-content-routing": "^1.0.2", + "@libp2p/interface-dht": "^1.0.1", + "@libp2p/interface-metrics": "^3.0.0", + "@libp2p/interface-peer-discovery": "^1.0.1", + "@libp2p/interface-peer-id": "^1.0.4", + "@libp2p/interface-peer-info": "^1.0.2", + "@libp2p/interface-peer-routing": "^1.0.1", + "@libp2p/interface-peer-store": "^1.2.1", + "@libp2p/interface-pubsub": "^2.0.1", + "@libp2p/interface-registrar": "^2.0.3", + "@libp2p/interface-stream-muxer": "^2.0.2", + "@libp2p/interface-transport": "^1.0.3", + "@libp2p/interfaces": "^3.0.3", "@libp2p/logger": "^2.0.0", - "@libp2p/multistream-select": "^2.0.1", + "@libp2p/multistream-select": "^3.0.0", "@libp2p/peer-collections": "^2.0.0", - "@libp2p/peer-id": "^1.1.10", - "@libp2p/peer-id-factory": "^1.0.9", - "@libp2p/peer-record": "^4.0.0", - "@libp2p/peer-store": "^3.0.0", + "@libp2p/peer-id": "^1.1.15", + "@libp2p/peer-id-factory": "^1.0.18", + "@libp2p/peer-record": "^4.0.1", + "@libp2p/peer-store": "^3.1.2", "@libp2p/tracked-map": "^2.0.1", - "@libp2p/utils": "^3.0.0", + "@libp2p/utils": "^3.0.1", "@multiformats/mafmt": "^11.0.2", - "@multiformats/multiaddr": "^10.1.8", + "@multiformats/multiaddr": "^10.3.3", "abortable-iterator": "^4.0.2", "any-signal": "^3.0.0", "datastore-core": "^7.0.0", @@ -140,7 +140,7 @@ "it-filter": "^1.0.3", "it-first": "^1.0.6", "it-foreach": "^0.1.1", - "it-handshake": "^4.0.0", + "it-handshake": "^4.1.2", "it-length-prefixed": "^8.0.2", "it-map": "^1.0.6", "it-merge": "^1.0.3", @@ -156,31 +156,31 @@ "p-retry": "^5.0.0", "p-settle": "^5.0.0", "private-ip": "^2.3.3", - "protons-runtime": "^2.0.2", + "protons-runtime": "^3.0.1", "retimer": "^3.0.0", "sanitize-filename": "^1.6.3", "set-delayed-interval": "^1.0.0", "timeout-abort-controller": "^3.0.0", - "uint8arraylist": "^2.0.0", + "uint8arraylist": "^2.3.2", "uint8arrays": "^3.0.0", "wherearewe": "^1.0.0", "xsalsa20": "^1.1.0" }, "devDependencies": { - "@chainsafe/libp2p-noise": "^7.0.2", + "@chainsafe/libp2p-noise": "^8.0.0", "@libp2p/bootstrap": "^2.0.0", - "@libp2p/daemon-client": "^2.0.0", - "@libp2p/daemon-server": "^2.0.0", + "@libp2p/daemon-client": "^2.0.4", + "@libp2p/daemon-server": "^2.0.4", "@libp2p/delegated-content-routing": "^2.0.1", "@libp2p/delegated-peer-routing": "^2.0.1", "@libp2p/floodsub": "^3.0.0", "@libp2p/interface-compliance-tests": "^3.0.1", - "@libp2p/interface-connection-encrypter-compliance-tests": "^1.0.0", - "@libp2p/interface-mocks": "^3.0.1", + "@libp2p/interface-connection-encrypter-compliance-tests": "^2.0.1", + "@libp2p/interface-mocks": "^4.0.1", "@libp2p/interop": "^2.0.0", - "@libp2p/kad-dht": "^3.0.0", + "@libp2p/kad-dht": "^3.0.1", "@libp2p/mdns": "^3.0.0", - "@libp2p/mplex": "^4.0.2", + "@libp2p/mplex": "^5.0.0", "@libp2p/pubsub": "^3.0.1", "@libp2p/tcp": "^3.0.0", "@libp2p/topology": "^3.0.0", @@ -205,7 +205,7 @@ "p-event": "^5.0.1", "p-times": "^4.0.0", "p-wait-for": "^5.0.0", - "protons": "^4.0.1", + "protons": "^5.0.0", "rimraf": "^3.0.2", "sinon": "^14.0.0", "ts-sinon": "^2.0.2" diff --git a/src/circuit/README.md b/src/circuit/README.md index cbf1dd1dc4..330df42a12 100644 --- a/src/circuit/README.md +++ b/src/circuit/README.md @@ -41,7 +41,7 @@ import { Multiaddr } from '@multiformats/multiaddr' import Libp2p from 'libp2p' import { TCP } from '@libp2p/tcp' import { Mplex } from '@libp2p/mplex' -import { NOISE } from '@chainsafe/libp2p-noise' +import { Noise } from '@chainsafe/libp2p-noise' const relayAddr = ... @@ -56,7 +56,7 @@ const node = await createLibp2p({ new Mplex() ], connectionEncryption: [ - NOISE + new Noise() ] }, config: { diff --git a/src/circuit/circuit/hop.ts b/src/circuit/circuit/hop.ts index a7fd38850e..1ec63e0caf 100644 --- a/src/circuit/circuit/hop.ts +++ b/src/circuit/circuit/hop.ts @@ -13,6 +13,7 @@ import type { Duplex } from 'it-stream-types' import type { Circuit } from '../transport.js' import type { ConnectionManager } from '@libp2p/interface-connection-manager' import type { AbortOptions } from '@libp2p/interfaces' +import type { Uint8ArrayList } from 'uint8arraylist' const log = logger('libp2p:circuit:hop') @@ -24,7 +25,7 @@ export interface HopRequest { connectionManager: ConnectionManager } -export async function handleHop (hopRequest: HopRequest) { +export async function handleHop (hopRequest: HopRequest): Promise { const { connection, request, @@ -84,7 +85,7 @@ export async function handleHop (hopRequest: HopRequest) { srcPeer: request.srcPeer } - let destinationStream: Duplex + let destinationStream: Duplex try { log('performing STOP request') const result = await stop({ @@ -128,7 +129,7 @@ export interface HopConfig extends AbortOptions { * Performs a HOP request to a relay peer, to request a connection to another * peer. A new, virtual, connection will be created between the two via the relay. */ -export async function hop (options: HopConfig): Promise> { +export async function hop (options: HopConfig): Promise> { const { connection, request, @@ -151,6 +152,7 @@ export async function hop (options: HopConfig): Promise> { if (response.code === CircuitPB.Status.SUCCESS) { log('hop request was successful') + return streamHandler.rest() } diff --git a/src/circuit/circuit/stop.ts b/src/circuit/circuit/stop.ts index 1ad8f8e0f1..2e27d010fe 100644 --- a/src/circuit/circuit/stop.ts +++ b/src/circuit/circuit/stop.ts @@ -6,6 +6,7 @@ import { validateAddrs } from './utils.js' import type { Connection } from '@libp2p/interface-connection' import type { Duplex } from 'it-stream-types' import type { AbortOptions } from '@libp2p/interfaces' +import type { Uint8ArrayList } from 'uint8arraylist' const log = logger('libp2p:circuit:stop') @@ -18,7 +19,7 @@ export interface HandleStopOptions { /** * Handles incoming STOP requests */ -export function handleStop (options: HandleStopOptions): Duplex | undefined { +export function handleStop (options: HandleStopOptions): Duplex | undefined { const { connection, request, diff --git a/src/circuit/circuit/stream-handler.ts b/src/circuit/circuit/stream-handler.ts index 0935d5d689..0638733f4e 100644 --- a/src/circuit/circuit/stream-handler.ts +++ b/src/circuit/circuit/stream-handler.ts @@ -22,7 +22,7 @@ export interface StreamHandlerOptions { export class StreamHandler { private readonly stream: Stream - private readonly shake: Handshake + private readonly shake: Handshake private readonly decoder: Source constructor (options: StreamHandlerOptions) { @@ -56,7 +56,7 @@ export class StreamHandler { */ write (msg: CircuitRelay) { log('write message type %s', msg.type) - this.shake.write(lp.encode.single(CircuitRelay.encode(msg)).slice()) + this.shake.write(lp.encode.single(CircuitRelay.encode(msg))) } /** diff --git a/src/circuit/pb/index.ts b/src/circuit/pb/index.ts index ec47246aab..44b0127cec 100644 --- a/src/circuit/pb/index.ts +++ b/src/circuit/pb/index.ts @@ -1,9 +1,9 @@ /* eslint-disable import/export */ /* eslint-disable @typescript-eslint/no-namespace */ -import { enumeration, encodeMessage, decodeMessage, message, bytes } from 'protons-runtime' -import type { Codec } from 'protons-runtime' +import { enumeration, encodeMessage, decodeMessage, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' +import type { Codec } from 'protons-runtime' export interface CircuitRelay { type?: CircuitRelay.Type @@ -53,7 +53,7 @@ export namespace CircuitRelay { export namespace Status { export const codec = () => { - return enumeration(__StatusValues) + return enumeration(__StatusValues) } } @@ -73,7 +73,7 @@ export namespace CircuitRelay { export namespace Type { export const codec = () => { - return enumeration(__TypeValues) + return enumeration(__TypeValues) } } @@ -83,14 +83,74 @@ export namespace CircuitRelay { } export namespace Peer { + let _codec: Codec + export const codec = (): Codec => { - return message({ - 1: { name: 'id', codec: bytes }, - 2: { name: 'addrs', codec: bytes, repeats: true } - }) + if (_codec == null) { + _codec = message((obj, writer, opts = {}) => { + if (opts.lengthDelimited !== false) { + writer.fork() + } + + if (obj.id != null) { + writer.uint32(10) + writer.bytes(obj.id) + } else { + throw new Error('Protocol error: required field "id" was not found in object') + } + + if (obj.addrs != null) { + for (const value of obj.addrs) { + writer.uint32(18) + writer.bytes(value) + } + } else { + throw new Error('Protocol error: required field "addrs" was not found in object') + } + + if (opts.lengthDelimited !== false) { + writer.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.id = reader.bytes() + break + case 2: + obj.addrs = obj.addrs ?? [] + obj.addrs.push(reader.bytes()) + break + default: + reader.skipType(tag & 7) + break + } + } + + obj.addrs = obj.addrs ?? [] + + if (obj.id == null) { + throw new Error('Protocol error: value for required field "id" was not found in protobuf') + } + + if (obj.addrs == null) { + throw new Error('Protocol error: value for required field "addrs" was not found in protobuf') + } + + return obj + }) + } + + return _codec } - export const encode = (obj: Peer): Uint8ArrayList => { + export const encode = (obj: Peer): Uint8Array => { return encodeMessage(obj, Peer.codec()) } @@ -99,16 +159,73 @@ export namespace CircuitRelay { } } + let _codec: Codec + export const codec = (): Codec => { - return message({ - 1: { name: 'type', codec: CircuitRelay.Type.codec(), optional: true }, - 2: { name: 'srcPeer', codec: CircuitRelay.Peer.codec(), optional: true }, - 3: { name: 'dstPeer', codec: CircuitRelay.Peer.codec(), optional: true }, - 4: { name: 'code', codec: CircuitRelay.Status.codec(), optional: true } - }) + if (_codec == null) { + _codec = message((obj, writer, opts = {}) => { + if (opts.lengthDelimited !== false) { + writer.fork() + } + + if (obj.type != null) { + writer.uint32(8) + CircuitRelay.Type.codec().encode(obj.type, writer) + } + + if (obj.srcPeer != null) { + writer.uint32(18) + CircuitRelay.Peer.codec().encode(obj.srcPeer, writer) + } + + if (obj.dstPeer != null) { + writer.uint32(26) + CircuitRelay.Peer.codec().encode(obj.dstPeer, writer) + } + + if (obj.code != null) { + writer.uint32(32) + CircuitRelay.Status.codec().encode(obj.code, writer) + } + + if (opts.lengthDelimited !== false) { + writer.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.type = CircuitRelay.Type.codec().decode(reader) + break + case 2: + obj.srcPeer = CircuitRelay.Peer.codec().decode(reader, reader.uint32()) + break + case 3: + obj.dstPeer = CircuitRelay.Peer.codec().decode(reader, reader.uint32()) + break + case 4: + obj.code = CircuitRelay.Status.codec().decode(reader) + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec } - export const encode = (obj: CircuitRelay): Uint8ArrayList => { + export const encode = (obj: CircuitRelay): Uint8Array => { return encodeMessage(obj, CircuitRelay.codec()) } diff --git a/src/circuit/transport.ts b/src/circuit/transport.ts index 5c87bd2e3a..eaa0f4a988 100644 --- a/src/circuit/transport.ts +++ b/src/circuit/transport.ts @@ -21,6 +21,8 @@ import type { RelayConfig } from '../index.js' import { abortableDuplex } from 'abortable-iterator' import { TimeoutController } from 'timeout-abort-controller' import { setMaxListeners } from 'events' +import type { Uint8ArrayList } from 'uint8arraylist' +import type { Duplex } from 'it-stream-types' const log = logger('libp2p:circuit') @@ -90,7 +92,7 @@ export class Circuit implements Transport, Initializable { return } - let virtualConnection + let virtualConnection: Duplex | undefined switch (request.type) { case CircuitPB.Type.CAN_HOP: { @@ -100,7 +102,7 @@ export class Circuit implements Transport, Initializable { } case CircuitPB.Type.HOP: { log('received HOP request from %p', connection.remotePeer) - virtualConnection = await handleHop({ + await handleHop({ connection, request, streamHandler, diff --git a/src/fetch/pb/proto.ts b/src/fetch/pb/proto.ts index 608559358b..d4997ea4fc 100644 --- a/src/fetch/pb/proto.ts +++ b/src/fetch/pb/proto.ts @@ -1,22 +1,64 @@ /* eslint-disable import/export */ /* eslint-disable @typescript-eslint/no-namespace */ -import { encodeMessage, decodeMessage, message, string, enumeration, bytes } from 'protons-runtime' -import type { Codec } from 'protons-runtime' +import { encodeMessage, decodeMessage, message, enumeration } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' +import type { Codec } from 'protons-runtime' export interface FetchRequest { identifier: string } export namespace FetchRequest { + let _codec: Codec + export const codec = (): Codec => { - return message({ - 1: { name: 'identifier', codec: string } - }) + if (_codec == null) { + _codec = message((obj, writer, opts = {}) => { + if (opts.lengthDelimited !== false) { + writer.fork() + } + + if (obj.identifier != null) { + writer.uint32(10) + writer.string(obj.identifier) + } else { + throw new Error('Protocol error: required field "identifier" was not found in object') + } + + if (opts.lengthDelimited !== false) { + writer.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.identifier = reader.string() + break + default: + reader.skipType(tag & 7) + break + } + } + + if (obj.identifier == null) { + throw new Error('Protocol error: value for required field "identifier" was not found in protobuf') + } + + return obj + }) + } + + return _codec } - export const encode = (obj: FetchRequest): Uint8ArrayList => { + export const encode = (obj: FetchRequest): Uint8Array => { return encodeMessage(obj, FetchRequest.codec()) } @@ -45,18 +87,73 @@ export namespace FetchResponse { export namespace StatusCode { export const codec = () => { - return enumeration(__StatusCodeValues) + return enumeration(__StatusCodeValues) } } + let _codec: Codec + export const codec = (): Codec => { - return message({ - 1: { name: 'status', codec: FetchResponse.StatusCode.codec() }, - 2: { name: 'data', codec: bytes } - }) + if (_codec == null) { + _codec = message((obj, writer, opts = {}) => { + if (opts.lengthDelimited !== false) { + writer.fork() + } + + if (obj.status != null) { + writer.uint32(8) + FetchResponse.StatusCode.codec().encode(obj.status, writer) + } else { + throw new Error('Protocol error: required field "status" was not found in object') + } + + if (obj.data != null) { + writer.uint32(18) + writer.bytes(obj.data) + } else { + throw new Error('Protocol error: required field "data" was not found in object') + } + + if (opts.lengthDelimited !== false) { + writer.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.status = FetchResponse.StatusCode.codec().decode(reader) + break + case 2: + obj.data = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + if (obj.status == null) { + throw new Error('Protocol error: value for required field "status" was not found in protobuf') + } + + if (obj.data == null) { + throw new Error('Protocol error: value for required field "data" was not found in protobuf') + } + + return obj + }) + } + + return _codec } - export const encode = (obj: FetchResponse): Uint8ArrayList => { + export const encode = (obj: FetchResponse): Uint8Array => { return encodeMessage(obj, FetchResponse.codec()) } diff --git a/src/identify/index.ts b/src/identify/index.ts index ba82aa8178..a0f051423b 100644 --- a/src/identify/index.ts +++ b/src/identify/index.ts @@ -26,7 +26,6 @@ import type { Components } from '@libp2p/components' import { TimeoutController } from 'timeout-abort-controller' import type { AbortOptions } from '@libp2p/interfaces' import { abortableDuplex } from 'abortable-iterator' -import type { Duplex } from 'it-stream-types' import { setMaxListeners } from 'events' const log = logger('libp2p:identify') @@ -179,7 +178,7 @@ export class IdentifyService implements Startable { }) // make stream abortable - const source: Duplex = abortableDuplex(stream, timeoutController.signal) + const source = abortableDuplex(stream, timeoutController.signal) await pipe( [Identify.encode({ @@ -418,7 +417,7 @@ export class IdentifyService implements Startable { }) // make stream abortable - const source: Duplex = abortableDuplex(stream, timeoutController.signal) + const source = abortableDuplex(stream, timeoutController.signal) await pipe( [message], @@ -449,7 +448,7 @@ export class IdentifyService implements Startable { let message: Identify | undefined try { // make stream abortable - const source: Duplex = abortableDuplex(stream, timeoutController.signal) + const source = abortableDuplex(stream, timeoutController.signal) const data = await pipe( [], diff --git a/src/identify/pb/message.ts b/src/identify/pb/message.ts index 1f9072d677..882b5b67a2 100644 --- a/src/identify/pb/message.ts +++ b/src/identify/pb/message.ts @@ -1,9 +1,9 @@ /* eslint-disable import/export */ /* eslint-disable @typescript-eslint/no-namespace */ -import { encodeMessage, decodeMessage, message, string, bytes } from 'protons-runtime' -import type { Codec } from 'protons-runtime' +import { encodeMessage, decodeMessage, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' +import type { Codec } from 'protons-runtime' export interface Identify { protocolVersion?: string @@ -16,19 +16,118 @@ export interface Identify { } export namespace Identify { + let _codec: Codec + export const codec = (): Codec => { - return message({ - 5: { name: 'protocolVersion', codec: string, optional: true }, - 6: { name: 'agentVersion', codec: string, optional: true }, - 1: { name: 'publicKey', codec: bytes, optional: true }, - 2: { name: 'listenAddrs', codec: bytes, repeats: true }, - 4: { name: 'observedAddr', codec: bytes, optional: true }, - 3: { name: 'protocols', codec: string, repeats: true }, - 8: { name: 'signedPeerRecord', codec: bytes, optional: true } - }) + if (_codec == null) { + _codec = message((obj, writer, opts = {}) => { + if (opts.lengthDelimited !== false) { + writer.fork() + } + + if (obj.protocolVersion != null) { + writer.uint32(42) + writer.string(obj.protocolVersion) + } + + if (obj.agentVersion != null) { + writer.uint32(50) + writer.string(obj.agentVersion) + } + + if (obj.publicKey != null) { + writer.uint32(10) + writer.bytes(obj.publicKey) + } + + if (obj.listenAddrs != null) { + for (const value of obj.listenAddrs) { + writer.uint32(18) + writer.bytes(value) + } + } else { + throw new Error('Protocol error: required field "listenAddrs" was not found in object') + } + + if (obj.observedAddr != null) { + writer.uint32(34) + writer.bytes(obj.observedAddr) + } + + if (obj.protocols != null) { + for (const value of obj.protocols) { + writer.uint32(26) + writer.string(value) + } + } else { + throw new Error('Protocol error: required field "protocols" was not found in object') + } + + if (obj.signedPeerRecord != null) { + writer.uint32(66) + writer.bytes(obj.signedPeerRecord) + } + + if (opts.lengthDelimited !== false) { + writer.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 5: + obj.protocolVersion = reader.string() + break + case 6: + obj.agentVersion = reader.string() + break + case 1: + obj.publicKey = reader.bytes() + break + case 2: + obj.listenAddrs = obj.listenAddrs ?? [] + obj.listenAddrs.push(reader.bytes()) + break + case 4: + obj.observedAddr = reader.bytes() + break + case 3: + obj.protocols = obj.protocols ?? [] + obj.protocols.push(reader.string()) + break + case 8: + obj.signedPeerRecord = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + obj.listenAddrs = obj.listenAddrs ?? [] + obj.protocols = obj.protocols ?? [] + + if (obj.listenAddrs == null) { + throw new Error('Protocol error: value for required field "listenAddrs" was not found in protobuf') + } + + if (obj.protocols == null) { + throw new Error('Protocol error: value for required field "protocols" was not found in protobuf') + } + + return obj + }) + } + + return _codec } - export const encode = (obj: Identify): Uint8ArrayList => { + export const encode = (obj: Identify): Uint8Array => { return encodeMessage(obj, Identify.codec()) } diff --git a/src/insecure/index.ts b/src/insecure/index.ts index 3ffd6618be..d773415666 100644 --- a/src/insecure/index.ts +++ b/src/insecure/index.ts @@ -7,6 +7,7 @@ import type { PeerId } from '@libp2p/interface-peer-id' import { peerIdFromBytes, peerIdFromKeys } from '@libp2p/peer-id' import type { ConnectionEncrypter, SecuredConnection } from '@libp2p/interface-connection-encrypter' import type { Duplex } from 'it-stream-types' +import map from 'it-map' const log = logger('libp2p:plaintext') const PROTOCOL = '/plaintext/2.0.0' @@ -47,7 +48,7 @@ async function encrypt (localId: PeerId, conn: Duplex, remoteId?: Pe // Get the Exchange message // @ts-expect-error needs to be generator const response = (await lp.decode.fromReader(shake.reader).next()).value - const id = Exchange.decode(response.slice()) + const id = Exchange.decode(response) log('read pubkey exchange from peer %p', remoteId) let peerId @@ -81,8 +82,12 @@ async function encrypt (localId: PeerId, conn: Duplex, remoteId?: Pe log('plaintext key exchange completed successfully with peer %p', peerId) shake.rest() + return { - conn: shake.stream, + conn: { + sink: shake.stream.sink, + source: map(shake.stream.source, (buf) => buf.subarray()) + }, remotePeer: peerId, remoteEarlyData: new Uint8Array() } diff --git a/src/insecure/pb/proto.ts b/src/insecure/pb/proto.ts index ec84391110..1ff2468f5c 100644 --- a/src/insecure/pb/proto.ts +++ b/src/insecure/pb/proto.ts @@ -1,9 +1,9 @@ /* eslint-disable import/export */ /* eslint-disable @typescript-eslint/no-namespace */ -import { encodeMessage, decodeMessage, message, bytes, enumeration } from 'protons-runtime' -import type { Codec } from 'protons-runtime' +import { encodeMessage, decodeMessage, message, enumeration } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' +import type { Codec } from 'protons-runtime' export interface Exchange { id?: Uint8Array @@ -11,14 +11,57 @@ export interface Exchange { } export namespace Exchange { + let _codec: Codec + export const codec = (): Codec => { - return message({ - 1: { name: 'id', codec: bytes, optional: true }, - 2: { name: 'pubkey', codec: PublicKey.codec(), optional: true } - }) + if (_codec == null) { + _codec = message((obj, writer, opts = {}) => { + if (opts.lengthDelimited !== false) { + writer.fork() + } + + if (obj.id != null) { + writer.uint32(10) + writer.bytes(obj.id) + } + + if (obj.pubkey != null) { + writer.uint32(18) + PublicKey.codec().encode(obj.pubkey, writer) + } + + if (opts.lengthDelimited !== false) { + writer.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.id = reader.bytes() + break + case 2: + obj.pubkey = PublicKey.codec().decode(reader, reader.uint32()) + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec } - export const encode = (obj: Exchange): Uint8ArrayList => { + export const encode = (obj: Exchange): Uint8Array => { return encodeMessage(obj, Exchange.codec()) } @@ -43,7 +86,7 @@ enum __KeyTypeValues { export namespace KeyType { export const codec = () => { - return enumeration(__KeyTypeValues) + return enumeration(__KeyTypeValues) } } export interface PublicKey { @@ -52,14 +95,69 @@ export interface PublicKey { } export namespace PublicKey { + let _codec: Codec + export const codec = (): Codec => { - return message({ - 1: { name: 'Type', codec: KeyType.codec() }, - 2: { name: 'Data', codec: bytes } - }) + if (_codec == null) { + _codec = message((obj, writer, opts = {}) => { + if (opts.lengthDelimited !== false) { + writer.fork() + } + + if (obj.Type != null) { + writer.uint32(8) + KeyType.codec().encode(obj.Type, writer) + } else { + throw new Error('Protocol error: required field "Type" was not found in object') + } + + if (obj.Data != null) { + writer.uint32(18) + writer.bytes(obj.Data) + } else { + throw new Error('Protocol error: required field "Data" was not found in object') + } + + if (opts.lengthDelimited !== false) { + writer.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.Type = KeyType.codec().decode(reader) + break + case 2: + obj.Data = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + if (obj.Type == null) { + throw new Error('Protocol error: value for required field "Type" was not found in protobuf') + } + + if (obj.Data == null) { + throw new Error('Protocol error: value for required field "Data" was not found in protobuf') + } + + return obj + }) + } + + return _codec } - export const encode = (obj: PublicKey): Uint8ArrayList => { + export const encode = (obj: PublicKey): Uint8Array => { return encodeMessage(obj, PublicKey.codec()) } diff --git a/src/metrics/index.ts b/src/metrics/index.ts index 6c4429bb2f..80f1c5fa1d 100644 --- a/src/metrics/index.ts +++ b/src/metrics/index.ts @@ -6,7 +6,6 @@ import { DefaultStats, StatsInit } from './stats.js' import type { ComponentMetricsUpdate, Metrics, Stats, TrackedMetric, TrackStreamOptions } from '@libp2p/interface-metrics' import type { PeerId } from '@libp2p/interface-peer-id' import type { Startable } from '@libp2p/interfaces/startable' -import type { Duplex } from 'it-stream-types' const initialCounters: ['dataReceived', 'dataSent'] = [ 'dataReceived', @@ -263,11 +262,11 @@ export class DefaultMetrics implements Metrics, Startable { * When the `PeerId` is known, `Metrics.updatePlaceholder` should be called * with the placeholder string returned from here, and the known `PeerId`. */ - trackStream > (opts: TrackStreamOptions): T { + trackStream (opts: TrackStreamOptions): void { const { stream, remotePeer, protocol } = opts if (!this.running) { - return stream + return } const source = stream.source @@ -275,7 +274,7 @@ export class DefaultMetrics implements Metrics, Startable { remotePeer, protocol, direction: 'in', - dataLength: chunk.length + dataLength: chunk.byteLength })) const sink = stream.sink @@ -287,14 +286,12 @@ export class DefaultMetrics implements Metrics, Startable { remotePeer, protocol, direction: 'out', - dataLength: chunk.length + dataLength: chunk.byteLength }) }), sink ) } - - return stream } } diff --git a/src/ping/index.ts b/src/ping/index.ts index 8eb83fb121..e5f7386509 100644 --- a/src/ping/index.ts +++ b/src/ping/index.ts @@ -113,7 +113,7 @@ export class PingService implements Startable { ) const end = Date.now() - if (result == null || !uint8ArrayEquals(data, result)) { + if (result == null || !uint8ArrayEquals(data, result.subarray())) { throw errCode(new Error('Received wrong ping ack'), codes.ERR_WRONG_PING_ACK) } diff --git a/src/pnet/index.ts b/src/pnet/index.ts index 2a14d52dfb..e1333083d7 100644 --- a/src/pnet/index.ts +++ b/src/pnet/index.ts @@ -13,6 +13,7 @@ import { import { handshake } from 'it-handshake' import { NONCE_LENGTH } from './key-generator.js' import type { ConnectionProtector, MultiaddrConnection } from '@libp2p/interface-connection' +import map from 'it-map' const log = logger('libp2p:pnet') @@ -83,6 +84,7 @@ export class PreSharedKeyConnectionProtector implements ConnectionProtector { // Encrypt all outbound traffic createBoxStream(localNonce, this.psk), shake.stream, + (source) => map(source, (buf) => buf.subarray()), // Decrypt all inbound traffic createUnboxStream(remoteNonce, this.psk), external diff --git a/src/upgrader.ts b/src/upgrader.ts index 1c5919fe1a..8d314bb135 100644 --- a/src/upgrader.ts +++ b/src/upgrader.ts @@ -1,6 +1,6 @@ import { logger } from '@libp2p/logger' import errCode from 'err-code' -import { Dialer, Listener } from '@libp2p/multistream-select' +import * as mss from '@libp2p/multistream-select' import { pipe } from 'it-pipe' // @ts-expect-error mutable-proxy does not export types import mutableProxy from 'mutable-proxy' @@ -152,7 +152,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg ({ setTarget: setPeer, proxy: proxyPeer } = mutableProxy()) const idString = `${(Math.random() * 1e9).toString(36)}${Date.now()}` setPeer({ toString: () => idString }) - maConn = metrics.trackStream({ stream: maConn, remotePeer: proxyPeer }) + metrics.trackStream({ stream: maConn, remotePeer: proxyPeer }) } log('starting the inbound connection upgrade') @@ -253,7 +253,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg ({ setTarget: setPeer, proxy: proxyPeer } = mutableProxy()) const idString = `${(Math.random() * 1e9).toString(36)}${Date.now()}` setPeer({ toB58String: () => idString }) - maConn = metrics.trackStream({ stream: maConn, remotePeer: proxyPeer }) + metrics.trackStream({ stream: maConn, remotePeer: proxyPeer }) } log('Starting the outbound connection upgrade') @@ -351,9 +351,8 @@ export class DefaultUpgrader extends EventEmitter implements Upg void Promise.resolve() .then(async () => { - const mss = new Listener(muxedStream) const protocols = this.components.getRegistrar().getProtocols() - const { stream, protocol } = await mss.handle(protocols) + const { stream, protocol } = await mss.handle(muxedStream, protocols) log('%s: incoming stream opened on %s', direction, protocol) const metrics = this.components.getMetrics() @@ -405,7 +404,6 @@ export class DefaultUpgrader extends EventEmitter implements Upg log('%s: starting new stream on %s', direction, protocols) const muxedStream = muxer.newStream() - const mss = new Dialer(muxedStream) const metrics = this.components.getMetrics() let controller: TimeoutController | undefined @@ -422,10 +420,10 @@ export class DefaultUpgrader extends EventEmitter implements Upg } catch {} } - let { stream, protocol } = await mss.select(protocols, options) + const { stream, protocol } = await mss.select(muxedStream, protocols, options) if (metrics != null) { - stream = metrics.trackStream({ stream, remotePeer, protocol }) + metrics.trackStream({ stream, remotePeer, protocol }) } const outgoingLimit = findOutgoingStreamLimit(protocol, this.components.getRegistrar()) @@ -545,12 +543,13 @@ export class DefaultUpgrader extends EventEmitter implements Upg * Attempts to encrypt the incoming `connection` with the provided `cryptos` */ async _encryptInbound (connection: Duplex): Promise { - const mss = new Listener(connection) const protocols = Array.from(this.connectionEncryption.keys()) log('handling inbound crypto protocol selection', protocols) try { - const { stream, protocol } = await mss.handle(protocols) + const { stream, protocol } = await mss.handle(connection, protocols, { + writeBytes: true + }) const encrypter = this.connectionEncryption.get(protocol) if (encrypter == null) { @@ -573,12 +572,13 @@ export class DefaultUpgrader extends EventEmitter implements Upg * The first `ConnectionEncrypter` module to succeed will be used */ async _encryptOutbound (connection: MultiaddrConnection, remotePeerId: PeerId): Promise { - const mss = new Dialer(connection) const protocols = Array.from(this.connectionEncryption.keys()) log('selecting outbound crypto protocol', protocols) try { - const { stream, protocol } = await mss.select(protocols) + const { stream, protocol } = await mss.select(connection, protocols, { + writeBytes: true + }) const encrypter = this.connectionEncryption.get(protocol) if (encrypter == null) { @@ -601,11 +601,12 @@ export class DefaultUpgrader extends EventEmitter implements Upg * muxer will be used for all future streams on the connection. */ async _multiplexOutbound (connection: MultiaddrConnection, muxers: Map): Promise<{ stream: Duplex, muxerFactory?: StreamMuxerFactory}> { - const dialer = new Dialer(connection) const protocols = Array.from(muxers.keys()) log('outbound selecting muxer %s', protocols) try { - const { stream, protocol } = await dialer.select(protocols) + const { stream, protocol } = await mss.select(connection, protocols, { + writeBytes: true + }) log('%s selected as muxer protocol', protocol) const muxerFactory = muxers.get(protocol) return { stream, muxerFactory } @@ -620,11 +621,12 @@ export class DefaultUpgrader extends EventEmitter implements Upg * selected muxer will be used for all future streams on the connection. */ async _multiplexInbound (connection: MultiaddrConnection, muxers: Map): Promise<{ stream: Duplex, muxerFactory?: StreamMuxerFactory}> { - const listener = new Listener(connection) const protocols = Array.from(muxers.keys()) log('inbound handling muxers %s', protocols) try { - const { stream, protocol } = await listener.handle(protocols) + const { stream, protocol } = await mss.handle(connection, protocols, { + writeBytes: true + }) const muxerFactory = muxers.get(protocol) return { stream, muxerFactory } } catch (err: any) { diff --git a/test/configuration/utils.ts b/test/configuration/utils.ts index bc587e5cd9..a34385c9e3 100644 --- a/test/configuration/utils.ts +++ b/test/configuration/utils.ts @@ -10,7 +10,6 @@ import type { Libp2pInit, Libp2pOptions } from '../../src/index.js' import type { PeerId } from '@libp2p/interface-peer-id' import * as cborg from 'cborg' import { peerIdFromString } from '@libp2p/peer-id' -import { Uint8ArrayList } from 'uint8arraylist' const relayAddr = MULTIADDRS_WEBSOCKETS[0] @@ -33,16 +32,16 @@ class MockPubSub extends PubSubBaseProtocol { return cborg.decode(bytes) } - encodeRpc (rpc: PubSubRPC): Uint8ArrayList { - return new Uint8ArrayList(cborg.encode(rpc)) + encodeRpc (rpc: PubSubRPC): Uint8Array { + return cborg.encode(rpc) } decodeMessage (bytes: Uint8Array): PubSubRPCMessage { return cborg.decode(bytes) } - encodeMessage (rpc: PubSubRPCMessage): Uint8ArrayList { - return new Uint8ArrayList(cborg.encode(rpc)) + encodeMessage (rpc: PubSubRPCMessage): Uint8Array { + return cborg.encode(rpc) } async publishMessage (from: PeerId, message: Message): Promise { diff --git a/test/core/consume-peer-record.spec.ts b/test/core/consume-peer-record.spec.ts index 70dad598dd..0f7a126ce0 100644 --- a/test/core/consume-peer-record.spec.ts +++ b/test/core/consume-peer-record.spec.ts @@ -1,7 +1,7 @@ /* eslint-env mocha */ import { WebSockets } from '@libp2p/websockets' -import { NOISE } from '@chainsafe/libp2p-noise' +import { Plaintext } from '../../src/insecure/index.js' import { createPeerId } from '../utils/creators/peer.js' import { Multiaddr } from '@multiformats/multiaddr' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' @@ -18,7 +18,7 @@ describe('Consume peer record', () => { new WebSockets() ], connectionEncryption: [ - NOISE + new Plaintext() ] } libp2p = await createLibp2pNode(config) diff --git a/test/core/encryption.spec.ts b/test/core/encryption.spec.ts index 54d4d4c8c2..e8f52b80f8 100644 --- a/test/core/encryption.spec.ts +++ b/test/core/encryption.spec.ts @@ -2,7 +2,7 @@ import { expect } from 'aegir/chai' import { WebSockets } from '@libp2p/websockets' -import { NOISE } from '@chainsafe/libp2p-noise' +import { Plaintext } from '../../src/insecure/index.js' import { createLibp2p, Libp2pOptions } from '../../src/index.js' import { codes as ErrorCodes } from '../../src/errors.js' import { createPeerId } from '../utils/creators/peer.js' @@ -46,7 +46,7 @@ describe('Connection encryption configuration', () => { new WebSockets() ], connectionEncryption: [ - NOISE + new Plaintext() ] } await createLibp2p(config) diff --git a/test/core/get-public-key.spec.ts b/test/core/get-public-key.spec.ts index c72f695dd4..041d9c4971 100644 --- a/test/core/get-public-key.spec.ts +++ b/test/core/get-public-key.spec.ts @@ -2,7 +2,7 @@ import { expect } from 'aegir/chai' import { WebSockets } from '@libp2p/websockets' -import { NOISE } from '@chainsafe/libp2p-noise' +import { Plaintext } from '../../src/insecure/index.js' import { createPeerId } from '../utils/creators/peer.js' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' import type { Libp2pOptions } from '../../src/index.js' @@ -20,7 +20,7 @@ describe('getPublicKey', () => { new WebSockets() ], connectionEncryption: [ - NOISE + new Plaintext() ], dht: new KadDHT() } diff --git a/test/core/listening.node.ts b/test/core/listening.node.ts index fdb7e66246..41eeddf84b 100644 --- a/test/core/listening.node.ts +++ b/test/core/listening.node.ts @@ -2,7 +2,7 @@ import { expect } from 'aegir/chai' import { TCP } from '@libp2p/tcp' -import { NOISE } from '@chainsafe/libp2p-noise' +import { Plaintext } from '../../src/insecure/index.js' import { createPeerId } from '../utils/creators/peer.js' import type { PeerId } from '@libp2p/interface-peer-id' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' @@ -31,7 +31,7 @@ describe('Listening', () => { new TCP() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) diff --git a/test/dialing/direct.node.ts b/test/dialing/direct.node.ts index 19091bf3f4..d4965adba0 100644 --- a/test/dialing/direct.node.ts +++ b/test/dialing/direct.node.ts @@ -4,7 +4,7 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' import { TCP } from '@libp2p/tcp' import { Mplex } from '@libp2p/mplex' -import { NOISE } from '@chainsafe/libp2p-noise' +import { Plaintext } from '../../src/insecure/index.js' import { Multiaddr } from '@multiformats/multiaddr' import delay from 'delay' @@ -245,7 +245,7 @@ describe('libp2p.dialer (direct, TCP)', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) await remoteLibp2p.handle('/echo/1.0.0', ({ stream }) => { @@ -278,7 +278,7 @@ describe('libp2p.dialer (direct, TCP)', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) @@ -298,7 +298,7 @@ describe('libp2p.dialer (direct, TCP)', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) @@ -325,7 +325,7 @@ describe('libp2p.dialer (direct, TCP)', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) @@ -354,7 +354,7 @@ describe('libp2p.dialer (direct, TCP)', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) @@ -418,7 +418,7 @@ describe('libp2p.dialer (direct, TCP)', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) @@ -444,7 +444,7 @@ describe('libp2p.dialer (direct, TCP)', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) @@ -467,7 +467,7 @@ describe('libp2p.dialer (direct, TCP)', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ], connectionProtector: new PreSharedKeyConnectionProtector({ psk: swarmKeyBuffer @@ -505,7 +505,7 @@ describe('libp2p.dialer (direct, TCP)', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) @@ -541,7 +541,7 @@ describe('libp2p.dialer (direct, TCP)', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) diff --git a/test/dialing/direct.spec.ts b/test/dialing/direct.spec.ts index 846800a92e..f20ad79c49 100644 --- a/test/dialing/direct.spec.ts +++ b/test/dialing/direct.spec.ts @@ -7,7 +7,7 @@ import delay from 'delay' import { WebSockets } from '@libp2p/websockets' import * as filters from '@libp2p/websockets/filters' import { Mplex } from '@libp2p/mplex' -import { NOISE } from '@chainsafe/libp2p-noise' +import { Plaintext } from '../../src/insecure/index.js' import { Multiaddr } from '@multiformats/multiaddr' import { AbortError } from '@libp2p/interfaces/errors' import { MemoryDatastore } from 'datastore-core/memory' @@ -360,7 +360,7 @@ describe('libp2p.dialer (direct, WebSockets)', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) @@ -385,7 +385,7 @@ describe('libp2p.dialer (direct, WebSockets)', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ], connectionManager: { maxParallelDials: 10, @@ -416,7 +416,7 @@ describe('libp2p.dialer (direct, WebSockets)', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) @@ -450,7 +450,7 @@ describe('libp2p.dialer (direct, WebSockets)', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) @@ -490,7 +490,7 @@ describe('libp2p.dialer (direct, WebSockets)', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) @@ -518,7 +518,7 @@ describe('libp2p.dialer (direct, WebSockets)', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) @@ -539,7 +539,7 @@ describe('libp2p.dialer (direct, WebSockets)', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) @@ -576,7 +576,7 @@ describe('libp2p.dialer (direct, WebSockets)', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) @@ -602,7 +602,7 @@ describe('libp2p.dialer (direct, WebSockets)', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) diff --git a/test/fetch/fetch.node.ts b/test/fetch/fetch.node.ts index 32a089d96a..82a27d2aee 100644 --- a/test/fetch/fetch.node.ts +++ b/test/fetch/fetch.node.ts @@ -4,7 +4,7 @@ import { expect } from 'aegir/chai' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' import { TCP } from '@libp2p/tcp' import { Mplex } from '@libp2p/mplex' -import { NOISE } from '@chainsafe/libp2p-noise' +import { Plaintext } from '../../src/insecure/index.js' import { createPeerId } from '../utils/creators/peer.js' import { codes } from '../../src/errors.js' import type { PeerId } from '@libp2p/interface-peer-id' @@ -22,7 +22,7 @@ async function createNode (peerId: PeerId) { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) } diff --git a/test/metrics/index.node.ts b/test/metrics/index.node.ts index 89296b1e7b..380989eb24 100644 --- a/test/metrics/index.node.ts +++ b/test/metrics/index.node.ts @@ -13,6 +13,7 @@ import type { Libp2pOptions } from '../../src/index.js' import type { DefaultMetrics } from '../../src/metrics/index.js' import pWaitFor from 'p-wait-for' import drain from 'it-drain' +import map from 'it-map' describe('libp2p.metrics', () => { let libp2p: Libp2pNode @@ -97,7 +98,7 @@ describe('libp2p.metrics', () => { const result = await pipe( [bytes], stream, - async (source) => await toBuffer(source) + async (source) => await toBuffer(map(source, (list) => list.subarray())) ) // Flush the call stack diff --git a/test/transports/transport-manager.spec.ts b/test/transports/transport-manager.spec.ts index 8ca53aa96e..348ef695d6 100644 --- a/test/transports/transport-manager.spec.ts +++ b/test/transports/transport-manager.spec.ts @@ -5,7 +5,7 @@ import sinon from 'sinon' import { Multiaddr } from '@multiformats/multiaddr' import { WebSockets } from '@libp2p/websockets' import * as filters from '@libp2p/websockets/filters' -import { NOISE } from '@chainsafe/libp2p-noise' +import { Plaintext } from '../../src/insecure/index.js' import { DefaultAddressManager } from '../../src/address-manager/index.js' import { DefaultTransportManager, FaultTolerance } from '../../src/transport-manager.js' import { mockUpgrader } from '@libp2p/interface-mocks' @@ -109,7 +109,7 @@ describe('libp2p.transportManager (dial only)', () => { listen: ['/ip4/127.0.0.1/tcp/0'] }, transports: [new WebSockets()], - connectionEncryption: [NOISE] + connectionEncryption: [new Plaintext()] }) await expect(libp2p.start()).to.eventually.be.rejected @@ -129,7 +129,7 @@ describe('libp2p.transportManager (dial only)', () => { new WebSockets() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) @@ -149,7 +149,7 @@ describe('libp2p.transportManager (dial only)', () => { new WebSockets() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) diff --git a/test/upgrading/upgrader.spec.ts b/test/upgrading/upgrader.spec.ts index 9a8bf95e74..48ca372e2c 100644 --- a/test/upgrading/upgrader.spec.ts +++ b/test/upgrading/upgrader.spec.ts @@ -8,7 +8,6 @@ import { pipe } from 'it-pipe' import all from 'it-all' import pSettle from 'p-settle' import { WebSockets } from '@libp2p/websockets' -import { NOISE } from '@chainsafe/libp2p-noise' import { PreSharedKeyConnectionProtector } from '../../src/pnet/index.js' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import swarmKey from '../fixtures/swarm.key.js' @@ -30,6 +29,7 @@ import { pEvent } from 'p-event' import { TimeoutController } from 'timeout-abort-controller' import delay from 'delay' import drain from 'it-drain' +import { Uint8ArrayList } from 'uint8arraylist' const addrs = [ new Multiaddr('/ip4/127.0.0.1/tcp/0'), @@ -409,7 +409,7 @@ describe('Upgrader', () => { source: (async function * () { // longer than the timeout await delay(1000) - yield new Uint8Array() + yield new Uint8ArrayList() }()), sink: drain }) @@ -479,7 +479,7 @@ describe('libp2p.upgrader', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ], connectionProtector: new PreSharedKeyConnectionProtector({ psk: uint8ArrayFromString(swarmKey) @@ -501,7 +501,7 @@ describe('libp2p.upgrader', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) await libp2p.start() @@ -517,7 +517,7 @@ describe('libp2p.upgrader', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) await remoteLibp2p.start() @@ -548,7 +548,7 @@ describe('libp2p.upgrader', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) await libp2p.start() @@ -562,7 +562,7 @@ describe('libp2p.upgrader', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) await remoteLibp2p.start() @@ -607,7 +607,7 @@ describe('libp2p.upgrader', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) await libp2p.start() @@ -621,7 +621,7 @@ describe('libp2p.upgrader', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) await remoteLibp2p.start() @@ -669,7 +669,7 @@ describe('libp2p.upgrader', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) await libp2p.start() @@ -683,7 +683,7 @@ describe('libp2p.upgrader', () => { new Mplex() ], connectionEncryption: [ - NOISE + new Plaintext() ] }) await remoteLibp2p.start() From 8880eefa8ffeff1203cdf5053a17dbf45f43cc3d Mon Sep 17 00:00:00 2001 From: Cayman Date: Thu, 11 Aug 2022 11:48:16 -0400 Subject: [PATCH 400/447] fix: add successful stream peer to protobook (#1341) * fix: add successful stream peer to protobook If a protocol stream has been successfully negotiated and is to be psased to the application, the peerstore should ensure that the peer is registerd with that protocol. * fix: upgrader test fix --- src/upgrader.ts | 8 ++++++++ test/upgrading/upgrader.spec.ts | 10 ++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/upgrader.ts b/src/upgrader.ts index 8d314bb135..bc0a8d960f 100644 --- a/src/upgrader.ts +++ b/src/upgrader.ts @@ -376,6 +376,10 @@ export class DefaultUpgrader extends EventEmitter implements Upg muxedStream.stat.protocol = protocol + // If a protocol stream has been successfully negotiated and is to be passed to the application, + // the peerstore should ensure that the peer is registered with that protocol + this.components.getPeerStore().protoBook.add(remotePeer, [protocol]).catch(err => log.error(err)) + connection.addStream(muxedStream) this._onStream({ connection, stream: { ...muxedStream, ...stream }, protocol }) }) @@ -438,6 +442,10 @@ export class DefaultUpgrader extends EventEmitter implements Upg muxedStream.stat.protocol = protocol + // If a protocol stream has been successfully negotiated and is to be passed to the application, + // the peerstore should ensure that the peer is registered with that protocol + this.components.getPeerStore().protoBook.add(remotePeer, [protocol]).catch(err => log.error(err)) + return { ...muxedStream, ...stream, diff --git a/test/upgrading/upgrader.spec.ts b/test/upgrading/upgrader.spec.ts index 48ca372e2c..24027209c1 100644 --- a/test/upgrading/upgrader.spec.ts +++ b/test/upgrading/upgrader.spec.ts @@ -30,6 +30,8 @@ import { TimeoutController } from 'timeout-abort-controller' import delay from 'delay' import drain from 'it-drain' import { Uint8ArrayList } from 'uint8arraylist' +import { PersistentPeerStore } from '@libp2p/peer-store' +import { MemoryDatastore } from 'datastore-core' const addrs = [ new Multiaddr('/ip4/127.0.0.1/tcp/0'), @@ -57,7 +59,9 @@ describe('Upgrader', () => { localComponents = new Components({ peerId: localPeer, connectionGater: mockConnectionGater(), - registrar: mockRegistrar() + registrar: mockRegistrar(), + peerStore: new PersistentPeerStore(), + datastore: new MemoryDatastore() }) localMuxerFactory = new Mplex() localUpgrader = new DefaultUpgrader(localComponents, { @@ -73,7 +77,9 @@ describe('Upgrader', () => { remoteComponents = new Components({ peerId: remotePeer, connectionGater: mockConnectionGater(), - registrar: mockRegistrar() + registrar: mockRegistrar(), + peerStore: new PersistentPeerStore(), + datastore: new MemoryDatastore() }) remoteUpgrader = new DefaultUpgrader(remoteComponents, { connectionEncryption: [ From 6630cb19b9cf015bd73c18b42eb43c702eeac4b4 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 12 Aug 2022 13:29:34 +0100 Subject: [PATCH 401/447] deps: update interface-datastore and datastore-core (#1347) --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 1e31ab23b3..aff7fc19b9 100644 --- a/package.json +++ b/package.json @@ -117,24 +117,24 @@ "@libp2p/interface-stream-muxer": "^2.0.2", "@libp2p/interface-transport": "^1.0.3", "@libp2p/interfaces": "^3.0.3", - "@libp2p/logger": "^2.0.0", + "@libp2p/logger": "^2.0.1", "@libp2p/multistream-select": "^3.0.0", "@libp2p/peer-collections": "^2.0.0", "@libp2p/peer-id": "^1.1.15", "@libp2p/peer-id-factory": "^1.0.18", - "@libp2p/peer-record": "^4.0.1", - "@libp2p/peer-store": "^3.1.2", + "@libp2p/peer-record": "^4.0.2", + "@libp2p/peer-store": "^3.1.3", "@libp2p/tracked-map": "^2.0.1", "@libp2p/utils": "^3.0.1", "@multiformats/mafmt": "^11.0.2", "@multiformats/multiaddr": "^10.3.3", "abortable-iterator": "^4.0.2", "any-signal": "^3.0.0", - "datastore-core": "^7.0.0", + "datastore-core": "^8.0.1", "err-code": "^3.0.1", "events": "^3.3.0", "hashlru": "^2.3.0", - "interface-datastore": "^6.1.0", + "interface-datastore": "^7.0.0", "it-all": "^1.0.6", "it-drain": "^1.0.5", "it-filter": "^1.0.3", @@ -178,7 +178,7 @@ "@libp2p/interface-connection-encrypter-compliance-tests": "^2.0.1", "@libp2p/interface-mocks": "^4.0.1", "@libp2p/interop": "^2.0.0", - "@libp2p/kad-dht": "^3.0.1", + "@libp2p/kad-dht": "^3.0.4", "@libp2p/mdns": "^3.0.0", "@libp2p/mplex": "^5.0.0", "@libp2p/pubsub": "^3.0.1", From 2262f8192451e9accafcff03bb985d489c2cd466 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Sat, 13 Aug 2022 18:27:12 +0100 Subject: [PATCH 402/447] deps: update wherearewe@2.0.0 (#1350) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index aff7fc19b9..c9d79fccae 100644 --- a/package.json +++ b/package.json @@ -163,7 +163,7 @@ "timeout-abort-controller": "^3.0.0", "uint8arraylist": "^2.3.2", "uint8arrays": "^3.0.0", - "wherearewe": "^1.0.0", + "wherearewe": "^2.0.0", "xsalsa20": "^1.1.0" }, "devDependencies": { From 886759b7fb3c14f243d4e74b1714930424bb7453 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Sun, 14 Aug 2022 11:14:18 +0100 Subject: [PATCH 403/447] fix: catch errors when reconnecting old peers (#1352) Since we don't wait for successful reconnection to existing peers, the peerstore can be closed while we're accessing it since we might be shut down right after we've started up, e.g. during test runs so just log any reconnect errors instead of throwing. --- src/connection-manager/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index 54acae3b99..4b6f001a9c 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -347,6 +347,9 @@ export class DefaultConnectionManager extends EventEmitter { + log.error(err) + }) .finally(() => { this.connectOnStartupController?.clear() }) From 509e56a60359f98ec435f8519c6a499641cce212 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Mon, 15 Aug 2022 09:20:05 +0100 Subject: [PATCH 404/447] fix: prepend connection addr to circuit relay address (#1355) Otherwise the reported remote addr is not valid --- src/circuit/transport.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/circuit/transport.ts b/src/circuit/transport.ts index eaa0f4a988..4fe69480e6 100644 --- a/src/circuit/transport.ts +++ b/src/circuit/transport.ts @@ -132,10 +132,10 @@ export class Circuit implements Transport, Initializable { } if (virtualConnection != null) { - // @ts-expect-error dst peer will not be undefined - const remoteAddr = new Multiaddr(request.dstPeer.addrs[0]) - // @ts-expect-error dst peer will not be undefined - const localAddr = new Multiaddr(request.srcPeer.addrs[0]) + const remoteAddr = connection.remoteAddr + .encapsulate('/p2p-circuit') + .encapsulate(new Multiaddr(request.dstPeer?.addrs[0])) + const localAddr = new Multiaddr(request.srcPeer?.addrs[0]) const maConn = streamToMaConnection({ stream: virtualConnection, remoteAddr, From 41990421bfb06fa96c1ed04d440a712cb5fc051b Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 17 Aug 2022 20:27:57 +0100 Subject: [PATCH 405/447] deps: update node-forge to 1.3.1 to slience automated warnings (#1358) Fixes #1351 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c9d79fccae..3b73fc4abe 100644 --- a/package.json +++ b/package.json @@ -151,7 +151,7 @@ "merge-options": "^3.0.4", "multiformats": "^9.6.3", "mutable-proxy": "^1.0.0", - "node-forge": "^1.2.1", + "node-forge": "^1.3.1", "p-fifo": "^1.0.0", "p-retry": "^5.0.0", "p-settle": "^5.0.0", From 29c803a63e5aacda1e5b87527b15bf27b31ea5a6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 17 Aug 2022 20:39:29 +0100 Subject: [PATCH 406/447] chore: release 0.38.0 (#1253) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 37 +++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index deb10ccb77..4c1f9fae3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,43 @@ +## [0.38.0](https://www.github.com/libp2p/js-libp2p/compare/v0.37.3...v0.38.0) (2022-08-17) + + +### ⚠ BREAKING CHANGES + +* Streams are now `Duplex` +* `connectionManager.peerValue` has been removed, use `peerStore.tagPeer` instead +* limit protocol streams per-connection (#1255) +* uses new single-issue libp2p interface modules + +### Features + +* limit protocol streams per-connection ([#1255](https://www.github.com/libp2p/js-libp2p/issues/1255)) ([de30c2c](https://www.github.com/libp2p/js-libp2p/commit/de30c2cec79d1e9d758cbcddc11d315b17843343)) +* programmatically set agentVersion for use in identify ([#1296](https://www.github.com/libp2p/js-libp2p/issues/1296)) ([0bb1b80](https://www.github.com/libp2p/js-libp2p/commit/0bb1b802c8fc2f32eaef10efbc88005dce6c6020)), closes [#686](https://www.github.com/libp2p/js-libp2p/issues/686) [#1240](https://www.github.com/libp2p/js-libp2p/issues/1240) +* update libp2p interfaces ([#1252](https://www.github.com/libp2p/js-libp2p/issues/1252)) ([d4dd664](https://www.github.com/libp2p/js-libp2p/commit/d4dd664071476e3d22f53e02e7d66099f3265f6c)) +* use tag values to choose which connections to close ([#1276](https://www.github.com/libp2p/js-libp2p/issues/1276)) ([b1b2b21](https://www.github.com/libp2p/js-libp2p/commit/b1b2b216daf12caccd67503dfd7b296b191c5b83)) + + +### Bug Fixes + +* add successful stream peer to protobook ([#1341](https://www.github.com/libp2p/js-libp2p/issues/1341)) ([8880eef](https://www.github.com/libp2p/js-libp2p/commit/8880eefa8ffeff1203cdf5053a17dbf45f43cc3d)) +* add timeout for circuit relay ([#1294](https://www.github.com/libp2p/js-libp2p/issues/1294)) ([ba56c64](https://www.github.com/libp2p/js-libp2p/commit/ba56c6466232ad4aa5025e2db084c5c9ccd4e5d0)) +* add timeout for incoming connections and build-in protocols ([#1292](https://www.github.com/libp2p/js-libp2p/issues/1292)) ([750ed9c](https://www.github.com/libp2p/js-libp2p/commit/750ed9c35f095aa6e136a801ccd792f2190f38a1)) +* catch errors when reconnecting old peers ([#1352](https://www.github.com/libp2p/js-libp2p/issues/1352)) ([886759b](https://www.github.com/libp2p/js-libp2p/commit/886759b7fb3c14f243d4e74b1714930424bb7453)) +* close streams when protocol limits are reached ([#1301](https://www.github.com/libp2p/js-libp2p/issues/1301)) ([3c0fb13](https://www.github.com/libp2p/js-libp2p/commit/3c0fb13babe295c8e5284345080bd4434f39efa7)) +* MaxListenersExceeded warning ([#1297](https://www.github.com/libp2p/js-libp2p/issues/1297)) ([627b8bf](https://www.github.com/libp2p/js-libp2p/commit/627b8bf87c775762dd6a9de69b77852e48ebcf26)) +* prepend connection addr to circuit relay address ([#1355](https://www.github.com/libp2p/js-libp2p/issues/1355)) ([509e56a](https://www.github.com/libp2p/js-libp2p/commit/509e56a60359f98ec435f8519c6a499641cce212)) +* remove mplex prefix from muxer errors ([#1304](https://www.github.com/libp2p/js-libp2p/issues/1304)) ([05e8e7e](https://www.github.com/libp2p/js-libp2p/commit/05e8e7ead96d494bdd7dfa5d6430155670066767)) +* specify max stream args separately ([#1254](https://www.github.com/libp2p/js-libp2p/issues/1254)) ([5371729](https://www.github.com/libp2p/js-libp2p/commit/53717296468ef17fdc3e0dda9d5908b15d2772a1)) +* update muxer behavior ([#1289](https://www.github.com/libp2p/js-libp2p/issues/1289)) ([b1b9139](https://www.github.com/libp2p/js-libp2p/commit/b1b91398e27d0b8852a74a87f0d8ccc5f34340b4)) +* use keep-alive tag to reconnect to peers on startup ([#1278](https://www.github.com/libp2p/js-libp2p/issues/1278)) ([2836acc](https://www.github.com/libp2p/js-libp2p/commit/2836acc90f8eafd2106539a80ac7d3b307c0bd02)) + + +### deps + +* update all deps to support no-copy operations ([#1335](https://www.github.com/libp2p/js-libp2p/issues/1335)) ([f439d9b](https://www.github.com/libp2p/js-libp2p/commit/f439d9b589a0a6544b61aca3736e920943ce38b5)) + ### [0.37.3](https://www.github.com/libp2p/js-libp2p/compare/v0.37.2...v0.37.3) (2022-06-08) diff --git a/package.json b/package.json index 3b73fc4abe..2a1477e3b7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.37.3", + "version": "0.38.0", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p#readme", From 1f38ab7ac8380c9501b252d076bb356662978882 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 17 Aug 2022 21:34:57 +0100 Subject: [PATCH 407/447] fix!: load self key into keychain on startup if not present (#1357) To prevent triggering keychain attack prevention on startup, refactor the `KeyChain` class to load the current PeerId as the `'self'` key on startup. Fixes #1315 BREAKING CHANGE: the `loadKeychain` method has been removed as it is no longer necessary --- doc/API.md | 30 ------------------------------ doc/CONFIGURATION.md | 2 -- src/index.ts | 6 ------ src/keychain/index.ts | 30 +++++++++++++++++++++++++++--- src/libp2p.ts | 16 ---------------- test/configuration/utils.ts | 2 -- test/keychain/keychain.spec.ts | 6 ------ 7 files changed, 27 insertions(+), 65 deletions(-) diff --git a/doc/API.md b/doc/API.md index a4ae0830dd..402a6b37b2 100644 --- a/doc/API.md +++ b/doc/API.md @@ -185,36 +185,6 @@ Required keys in the `options` object: ## Libp2p Instance Methods -### loadKeychain - -Load keychain keys from the datastore, importing the private key as 'self', if needed. - -`libp2p.loadKeychain()` - -#### Returns - -| Type | Description | -|------|-------------| -| `Promise` | Promise resolves when the keychain is ready | - -#### Example - -```js -import { createLibp2p } from 'libp2p' - -// ... - -const libp2p = await createLibp2p({ - // ... - keychain: { - pass: '0123456789pass1234567890' - } -}) - -// load keychain -await libp2p.loadKeychain() -``` - ### start Starts the libp2p node. diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 0740154aa4..fc3cab847c 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -494,8 +494,6 @@ const node = await createLibp2p({ datastore: dsInstant, } }) - -await node.loadKeychain() ``` #### Configuring Dialing diff --git a/src/index.ts b/src/index.ts index d5c3830709..0ba5ad7ba6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -140,12 +140,6 @@ export interface Libp2p extends Startable, EventEmitter { pubsub: PubSub dht: DualDHT - /** - * Load keychain keys from the datastore. - * Imports the private key as 'self', if needed. - */ - loadKeychain: () => Promise - /** * Get a deduplicated list of peer advertising multiaddrs by concatenating * the listen addresses used by transports with any configured diff --git a/src/keychain/index.ts b/src/keychain/index.ts index 67543d491a..df04486452 100644 --- a/src/keychain/index.ts +++ b/src/keychain/index.ts @@ -13,6 +13,7 @@ import { generateKeyPair, importKey, unmarshalPrivateKey } from '@libp2p/crypto/ import type { PeerId } from '@libp2p/interface-peer-id' import type { Components } from '@libp2p/components' import { pbkdf2, randomBytes } from '@libp2p/crypto' +import type { Startable } from '@libp2p/interfaces/dist/src/startable' const log = logger('libp2p:keychain') @@ -110,9 +111,10 @@ function DsInfoName (name: string) { * - '/pkcs8/*key-name*', contains the PKCS #8 for the key * */ -export class KeyChain { +export class KeyChain implements Startable { private readonly components: Components private init: KeyChainInit + private started: boolean /** * Creates a new instance of a key chain @@ -145,6 +147,25 @@ export class KeyChain { : '' privates.set(this, { dek }) + this.started = false + } + + isStarted () { + return this.started + } + + async start () { + const dsname = DsInfoName('self') + + if (!(await this.components.getDatastore().has(dsname))) { + await this.importPeer('self', this.components.getPeerId()) + } + + this.started = true + } + + stop () { + this.started = false } /** @@ -474,8 +495,11 @@ export class KeyChain { if (!validateKeyName(name)) { throw errCode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME) } - if (peer == null || peer.privateKey == null) { - throw errCode(new Error('Peer.privKey is required'), codes.ERR_MISSING_PRIVATE_KEY) + if (peer == null) { + throw errCode(new Error('PeerId is required'), codes.ERR_MISSING_PRIVATE_KEY) + } + if (peer.privateKey == null) { + throw errCode(new Error('PeerId.privKey is required'), codes.ERR_MISSING_PRIVATE_KEY) } const privateKey = await unmarshalPrivateKey(peer.privateKey) diff --git a/src/libp2p.ts b/src/libp2p.ts index 0a5a435df5..c08b9e2148 100644 --- a/src/libp2p.ts +++ b/src/libp2p.ts @@ -352,22 +352,6 @@ export class Libp2pNode extends EventEmitter implements Libp2p { log('libp2p has stopped') } - /** - * Load keychain keys from the datastore. - * Imports the private key as 'self', if needed. - */ - async loadKeychain () { - if (this.keychain == null) { - return - } - - try { - await this.keychain.findKeyByName('self') - } catch (err: any) { - await this.keychain.importPeer('self', this.peerId) - } - } - isStarted () { return this.started } diff --git a/test/configuration/utils.ts b/test/configuration/utils.ts index a34385c9e3..cfec40786a 100644 --- a/test/configuration/utils.ts +++ b/test/configuration/utils.ts @@ -9,12 +9,10 @@ import type { Message, PublishResult, PubSubInit, PubSubRPC, PubSubRPCMessage } import type { Libp2pInit, Libp2pOptions } from '../../src/index.js' import type { PeerId } from '@libp2p/interface-peer-id' import * as cborg from 'cborg' -import { peerIdFromString } from '@libp2p/peer-id' const relayAddr = MULTIADDRS_WEBSOCKETS[0] export const baseOptions: Partial = { - peerId: peerIdFromString('12D3KooWJKCJW8Y26pRFNv78TCMGLNTfyN8oKaFswMRYXTzSbSst'), transports: [new WebSockets()], streamMuxers: [new Mplex()], connectionEncryption: [new Plaintext()] diff --git a/test/keychain/keychain.spec.ts b/test/keychain/keychain.spec.ts index dd742d485f..d93fe7e11f 100644 --- a/test/keychain/keychain.spec.ts +++ b/test/keychain/keychain.spec.ts @@ -512,8 +512,6 @@ describe('libp2p.keychain', () => { } }) - await libp2p.loadKeychain() - const kInfo = await libp2p.keychain.createKey('keyName', 'Ed25519') expect(kInfo).to.exist() }) @@ -526,8 +524,6 @@ describe('libp2p.keychain', () => { } }) - await libp2p.loadKeychain() - const kInfo = await libp2p.keychain.createKey('keyName', 'Ed25519') expect(kInfo).to.exist() }) @@ -543,7 +539,6 @@ describe('libp2p.keychain', () => { } } }) - await libp2p.loadKeychain() const kInfo = await libp2p.keychain.createKey('keyName', 'Ed25519') expect(kInfo).to.exist() @@ -558,7 +553,6 @@ describe('libp2p.keychain', () => { } }) - await libp2p2.loadKeychain() const key = await libp2p2.keychain.findKeyByName('keyName') expect(key).to.exist() From 0e7096d527d9f4d39c7bedbcdaf1a504226c620d Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 30 Aug 2022 10:33:39 +0200 Subject: [PATCH 408/447] docs: update message filtering example (#1362) Updates the example to use the new pubsub `addEventListener`-style API along with the README. Also updates the test to actually test that the relevant messages were received. Fixes https://github.com/libp2p/js-libp2p/issues/1288 --- examples/pubsub/message-filtering/1.js | 53 ++++++++++++++------- examples/pubsub/message-filtering/README.md | 37 +++++++------- examples/pubsub/message-filtering/test.js | 53 +++++++-------------- 3 files changed, 74 insertions(+), 69 deletions(-) diff --git a/examples/pubsub/message-filtering/1.js b/examples/pubsub/message-filtering/1.js index f83ecc98a5..86e43ec16a 100644 --- a/examples/pubsub/message-filtering/1.js +++ b/examples/pubsub/message-filtering/1.js @@ -40,24 +40,42 @@ const createNode = async () => { await node2.dial(node3.peerId) //subscribe - node1.pubsub.addEventListener(topic, (evt) => { + node1.pubsub.addEventListener('message', (evt) => { + if (evt.detail.topic !== topic) { + return + } + // Will not receive own published messages by default console.log(`node1 received: ${uint8ArrayToString(evt.detail.data)}`) }) node1.pubsub.subscribe(topic) - node2.pubsub.addEventListener(topic, (evt) => { + node2.pubsub.addEventListener('message', (evt) => { + if (evt.detail.topic !== topic) { + return + } + console.log(`node2 received: ${uint8ArrayToString(evt.detail.data)}`) }) + node2.pubsub.subscribe(topic) + + node3.pubsub.addEventListener('message', (evt) => { + if (evt.detail.topic !== topic) { + return + } - node3.pubsub.addEventListener(topic, (evt) => { console.log(`node3 received: ${uint8ArrayToString(evt.detail.data)}`) }) + node3.pubsub.subscribe(topic) + + // wait for subscriptions to propagate + await delay(1000) const validateFruit = (msgTopic, msg) => { const fruit = uint8ArrayToString(msg.data) const validFruit = ['banana', 'apple', 'orange'] + // car is not a fruit ! if (!validFruit.includes(fruit)) { throw new Error('no valid fruit received') } @@ -68,18 +86,19 @@ const createNode = async () => { node2.pubsub.topicValidators.set(topic, validateFruit) node3.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, uint8ArrayFromString(myFruits[count])).catch(err => { - console.info(err) - }) - count++ - if (count == myFruits.length) { - count = 0 - } - }, 5000) + // node1 publishes "fruits" + for (const fruit of ['banana', 'apple', 'car', 'orange']) { + console.log('############## fruit ' + fruit + ' ##############') + await node1.pubsub.publish(topic, uint8ArrayFromString(fruit)) + } + + // wait a few seconds for messages to be received + await delay(5000) + console.log('############## all messages sent ##############') })() + +async function delay (ms) { + await new Promise((resolve) => { + setTimeout(() => resolve(), ms) + }) +} \ No newline at end of file diff --git a/examples/pubsub/message-filtering/README.md b/examples/pubsub/message-filtering/README.md index 99cec8aa7d..11aad74207 100644 --- a/examples/pubsub/message-filtering/README.md +++ b/examples/pubsub/message-filtering/README.md @@ -48,18 +48,30 @@ Now we' can subscribe to the fruit topic and log incoming messages. ```JavaScript const topic = 'fruit' -node1.pubsub.on(topic, (msg) => { +node1.pubsub.addEventListener('message', (msg) => { + if (msg.detail.topic !== topic) { + return + } + console.log(`node1 received: ${uint8ArrayToString(msg.data)}`) }) await node1.pubsub.subscribe(topic) -node2.pubsub.on(topic, (msg) => { +node2.pubsub.addEventListener('message', (msg) => { + if (msg.detail.topic !== topic) { + return + } + console.log(`node2 received: ${uint8ArrayToString(msg.data)}`) }) await node2.pubsub.subscribe(topic) -node3.pubsub.on(topic, (msg) => { - console.log(`node3 received: ${uint8ArrayToString(msg.data)}`) +node3.pubsub.addEventListener('message', (msg) => { + if (msg.detail.topic !== topic) { + return + } + +console.log(`node3 received: ${uint8ArrayToString(msg.data)}`) }) await node3.pubsub.subscribe(topic) ``` @@ -83,19 +95,10 @@ node3.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, new TextEncoder().encode(myFruits[count])).catch(err => { - console.error(err) - }) - count++ - if (count == myFruits.length) { - count = 0 - } -}, 5000) +for (const fruit of ['banana', 'apple', 'car', 'orange']) { + console.log('############## fruit ' + fruit + ' ##############') + await node1.pubsub.publish(topic, uint8ArrayFromString(fruit)) +} ``` Result diff --git a/examples/pubsub/message-filtering/test.js b/examples/pubsub/message-filtering/test.js index 97736f4985..e7d202df2a 100644 --- a/examples/pubsub/message-filtering/test.js +++ b/examples/pubsub/message-filtering/test.js @@ -6,29 +6,11 @@ import { fileURLToPath } from 'url' const __dirname = path.dirname(fileURLToPath(import.meta.url)) -const stdout = [ - { - topic: 'banana', - messageCount: 2 - }, - { - topic: 'apple', - messageCount: 2 - }, - { - topic: 'car', - messageCount: 0 - }, - { - topic: 'orange', - messageCount: 2 - }, -] +// holds messages received by peers +const messages = {} export async function test () { const defer = pDefer() - let topicCount = 0 - let topicMessageCount = 0 process.stdout.write('message-filtering/1.js\n') @@ -38,26 +20,27 @@ export async function test () { }) proc.all.on('data', async (data) => { - // End - if (topicCount === stdout.length) { - defer.resolve() - proc.all.removeAllListeners('data') - } - process.stdout.write(data) const line = uint8ArrayToString(data) - if (stdout[topicCount] && line.includes(stdout[topicCount].topic)) { - // Validate previous number of messages - if (topicCount > 0 && topicMessageCount > stdout[topicCount - 1].messageCount) { - defer.reject() - throw new Error(`topic ${stdout[topicCount - 1].topic} had ${topicMessageCount} messages instead of ${stdout[topicCount - 1].messageCount}`) + // End + if (line.includes('all messages sent')) { + if (messages.car > 0) { + defer.reject(new Error('Message validation failed - peers failed to filter car messages')) + } + + for (const fruit of ['banana', 'apple', 'orange']) { + if (messages[fruit] !== 2) { + defer.reject(new Error(`Not enough ${fruit} messages - received ${messages[fruit] ?? 0}, expected 2`)) + } } - topicCount++ - topicMessageCount = 0 - } else { - topicMessageCount++ + defer.resolve() + } + + if (line.includes('received:')) { + const fruit = line.split('received:')[1].trim() + messages[fruit] = (messages[fruit] ?? 0) + 1 } }) From fc2224a1e8c96dc11d8c506510cac322c8816df8 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Mon, 5 Sep 2022 12:21:31 +0200 Subject: [PATCH 409/447] deps: update tcp (#1366) --- examples/peer-and-content-routing/2.js | 2 +- package.json | 2 +- src/circuit/auto-relay.ts | 6 ++++++ src/connection-manager/dialer/index.ts | 5 ++++- test/relay/auto-relay.node.ts | 13 ++++++++++--- 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/examples/peer-and-content-routing/2.js b/examples/peer-and-content-routing/2.js index 5df2346613..d9245c48ff 100644 --- a/examples/peer-and-content-routing/2.js +++ b/examples/peer-and-content-routing/2.js @@ -40,7 +40,7 @@ const createNode = async () => { ]) // Wait for onConnect handlers in the DHT - await delay(100) + await delay(1000) const cid = CID.parse('QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL') await node1.contentRouting.provide(cid) diff --git a/package.json b/package.json index 2a1477e3b7..9a78c1ee1f 100644 --- a/package.json +++ b/package.json @@ -182,7 +182,7 @@ "@libp2p/mdns": "^3.0.0", "@libp2p/mplex": "^5.0.0", "@libp2p/pubsub": "^3.0.1", - "@libp2p/tcp": "^3.0.0", + "@libp2p/tcp": "^3.0.5", "@libp2p/topology": "^3.0.0", "@libp2p/webrtc-star": "^3.0.0", "@libp2p/websockets": "^3.0.0", diff --git a/src/circuit/auto-relay.ts b/src/circuit/auto-relay.ts index a6218a0334..16e1e39086 100644 --- a/src/circuit/auto-relay.ts +++ b/src/circuit/auto-relay.ts @@ -260,6 +260,12 @@ export class AutoRelay { } const peerId = provider.id + + if (peerId.equals(this.components.getPeerId())) { + // Skip the provider if it's us as dialing will fail + continue + } + await this.components.getPeerStore().addressBook.add(peerId, provider.multiaddrs) await this._tryToListenOnRelay(peerId) diff --git a/src/connection-manager/dialer/index.ts b/src/connection-manager/dialer/index.ts index bcdc15a41b..f40c735be7 100644 --- a/src/connection-manager/dialer/index.ts +++ b/src/connection-manager/dialer/index.ts @@ -284,7 +284,10 @@ export class Dialer implements Startable, Initializable { throw errCode(new Error('already aborted'), codes.ERR_ALREADY_ABORTED) } - return await this.components.getTransportManager().dial(addr, options) + return await this.components.getTransportManager().dial(addr, options).catch(err => { + log.error('dial to %s failed', addr, err) + throw err + }) } const dialRequest = new DialRequest({ diff --git a/test/relay/auto-relay.node.ts b/test/relay/auto-relay.node.ts index 6dd8e0f484..77b0cf7eab 100644 --- a/test/relay/auto-relay.node.ts +++ b/test/relay/auto-relay.node.ts @@ -205,8 +205,10 @@ describe('auto-relay', () => { })).to.eventually.be.rejected() // Disconnect from peer used for relay + const disconnectPromise = pEvent(relayLibp2p1.connectionManager, 'peer:disconnect', { timeout: 500 }) await relayLibp2p2.stop() - await pEvent(relayLibp2p1.connectionManager, 'peer:disconnect', { timeout: 500 }) + const event = await disconnectPromise + expect(event.detail.remotePeer.toString()).to.equal(relayLibp2p2.peerId.toString()) // Should not be using the relay any more await expect(usingAsRelay(relayLibp2p1, relayLibp2p2, { @@ -399,6 +401,7 @@ describe('auto-relay', () => { nock('http://0.0.0.0:60197') .post('/api/v0/dht/findprovs') .query(true) + .twice() .reply(200, `{"Extra":"","ID":"${provider}","Responses":[{"Addrs":${JSON.stringify(multiaddrs)},"ID":"${provider}"}],"Type":4}\n`, [ 'Content-Type', 'application/json', 'X-Chunked-Output', '1' @@ -432,10 +435,14 @@ describe('auto-relay', () => { await local.hangUp(relayAddr) // Should try to find relay service providers - await pWaitFor(() => contentRoutingFindProvidersSpy.callCount === 1) + await pWaitFor(() => contentRoutingFindProvidersSpy.callCount === 1, { + timeout: 1000 + }) // Wait for peer added as listen relay - await pWaitFor(() => local.getMultiaddrs().length === originalMultiaddrsLength + 1) + await pWaitFor(() => local.getMultiaddrs().length === originalMultiaddrsLength + 1, { + timeout: 1000 + }) const relayedAddr = local.getMultiaddrs()[local.getMultiaddrs().length - 1] await remote.peerStore.addressBook.set(local.peerId, [relayedAddr]) From d281a60dac973eeb0c842ffd70cd8bad3ae1156a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marin=20Petruni=C4=87?= Date: Mon, 5 Sep 2022 16:17:35 +0200 Subject: [PATCH 410/447] fix: discovery mechanism examples not working (#1365) Co-authored-by: achingbrain - fixed tests that were passing even though the example isn't working - added timeouts to avoid infinite wait Fixes #1229 --- examples/discovery-mechanisms/3.js | 4 ++-- examples/discovery-mechanisms/test-2.js | 2 +- examples/discovery-mechanisms/test-3.js | 15 ++++++++++----- examples/package.json | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/examples/discovery-mechanisms/3.js b/examples/discovery-mechanisms/3.js index c12d5f73ae..1b3d18b9e8 100644 --- a/examples/discovery-mechanisms/3.js +++ b/examples/discovery-mechanisms/3.js @@ -67,7 +67,7 @@ const createNode = async (bootstrappers) => { const peer = evt.detail console.log(`Peer ${node1.peerId.toString()} discovered: ${peer.id.toString()}`) }) - node2.addEventListener('peer:discovery',(evt) => { + node2.addEventListener('peer:discovery', (evt) => { const peer = evt.detail console.log(`Peer ${node2.peerId.toString()} discovered: ${peer.id.toString()}`) }) @@ -77,4 +77,4 @@ const createNode = async (bootstrappers) => { node1.start(), node2.start() ]) -})(); +})() diff --git a/examples/discovery-mechanisms/test-2.js b/examples/discovery-mechanisms/test-2.js index 9cef3bfd24..fefb7e43f5 100644 --- a/examples/discovery-mechanisms/test-2.js +++ b/examples/discovery-mechanisms/test-2.js @@ -27,7 +27,7 @@ export async function test () { }) }) - await pWaitFor(() => discoveredNodes > 1) + await pWaitFor(() => discoveredNodes > 1, 600000) proc.kill() } diff --git a/examples/discovery-mechanisms/test-3.js b/examples/discovery-mechanisms/test-3.js index 24a033110d..650b4db0e1 100644 --- a/examples/discovery-mechanisms/test-3.js +++ b/examples/discovery-mechanisms/test-3.js @@ -7,7 +7,7 @@ import { fileURLToPath } from 'url' const __dirname = path.dirname(fileURLToPath(import.meta.url)) export async function test () { - let discoveredNodes = 0 + const discoveredPeers = [] process.stdout.write('3.js\n') @@ -19,15 +19,20 @@ export async function test () { proc.all.on('data', async (data) => { process.stdout.write(data) const str = uint8ArrayToString(data) - + const discoveredPeersRegex = /Peer\s+(?[^\s]+)\s+discovered:\s+(?[^\s]+)/ str.split('\n').forEach(line => { - if (line.includes('discovered:')) { - discoveredNodes++ + const peers = line.match(discoveredPeersRegex) + if (peers != null) { + // sort so we don't count reversed pair twice + const match = [peers.groups.Peer1, peers.groups.Peer2].sort().join(',') + if (!discoveredPeers.includes(match)) { + discoveredPeers.push(match) + } } }) }) - await pWaitFor(() => discoveredNodes > 3) + await pWaitFor(() => discoveredPeers.length > 2, 600000) proc.kill() } diff --git a/examples/package.json b/examples/package.json index 6e86102b1e..c3baa68c9d 100644 --- a/examples/package.json +++ b/examples/package.json @@ -9,7 +9,7 @@ }, "license": "MIT", "dependencies": { - "@libp2p/pubsub-peer-discovery": "^6.0.1", + "@libp2p/pubsub-peer-discovery": "^6.0.2", "@libp2p/floodsub": "^3.0.3", "@nodeutils/defaults-deep": "^1.1.0", "execa": "^6.1.0", From 57ef75493d403317a76ed211033e187232663184 Mon Sep 17 00:00:00 2001 From: tabcat Date: Wed, 7 Sep 2022 04:57:39 -0500 Subject: [PATCH 411/447] docs: re-add talks section (#1368) The libp2p <3 ethereum video seems to have been moved to https://archive.devcon.org/archive/watch/2/libp2p-devp2p-ipfs-and-ethereum-networking/ unfortunately couldn't find the slides and demos previously linked --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c6a4ace284..ffc66e2bec 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,8 @@ We are in the process of writing better documentation, blog posts, tutorials and - [docs.libp2p.io](https://docs.libp2p.io) - [Specification (WIP)](https://github.com/libp2p/specs) - [Discussion Forums](https://discuss.libp2p.io) +- Talks + - [`libp2p <3 ethereum` at DEVCON2](https://archive.devcon.org/archive/watch/2/libp2p-devp2p-ipfs-and-ethereum-networking/) - Articles - [The overview of libp2p](https://github.com/libp2p/libp2p#description) From d63e08115bbac62170b792e48c5ae83b47bec043 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 7 Sep 2022 10:58:36 +0100 Subject: [PATCH 412/447] chore: release 0.39.0 (#1359) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c1f9fae3f..e8e247f544 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,18 @@ +## [0.39.0](https://www.github.com/libp2p/js-libp2p/compare/v0.38.0...v0.39.0) (2022-09-05) + + +### ⚠ BREAKING CHANGES + +* the `loadKeychain` method has been removed as it is no longer necessary + +### Bug Fixes + +* discovery mechanism examples not working ([#1365](https://www.github.com/libp2p/js-libp2p/issues/1365)) ([d281a60](https://www.github.com/libp2p/js-libp2p/commit/d281a60dac973eeb0c842ffd70cd8bad3ae1156a)), closes [#1229](https://www.github.com/libp2p/js-libp2p/issues/1229) +* load self key into keychain on startup if not present ([#1357](https://www.github.com/libp2p/js-libp2p/issues/1357)) ([1f38ab7](https://www.github.com/libp2p/js-libp2p/commit/1f38ab7ac8380c9501b252d076bb356662978882)), closes [#1315](https://www.github.com/libp2p/js-libp2p/issues/1315) + ## [0.38.0](https://www.github.com/libp2p/js-libp2p/compare/v0.37.3...v0.38.0) (2022-08-17) diff --git a/package.json b/package.json index 9a78c1ee1f..074b7c7638 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.38.0", + "version": "0.39.0", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p#readme", From 43b04189987f11a7729b522d1e1dbdc1caceb874 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 8 Sep 2022 12:47:03 +0100 Subject: [PATCH 413/447] fix: overwrite stream fields after handshake (#1305) Instead of creating a new stream objects instead just overwrite the duplex fields with ones from the mss stream. --- examples/delegated-routing/package.json | 2 +- examples/libp2p-in-the-browser/package.json | 2 +- examples/webrtc-direct/package.json | 2 +- package.json | 2 +- src/upgrader.ts | 17 +++++++---------- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/examples/delegated-routing/package.json b/examples/delegated-routing/package.json index 8c7d430290..ddbbecc37c 100644 --- a/examples/delegated-routing/package.json +++ b/examples/delegated-routing/package.json @@ -9,7 +9,7 @@ "@libp2p/delegated-content-routing": "^2.0.1", "@libp2p/delegated-peer-routing": "^2.0.1", "@libp2p/kad-dht": "^3.0.0", - "@libp2p/mplex": "^5.0.0", + "@libp2p/mplex": "^5.2.1", "@libp2p/webrtc-star": "^3.0.0", "@libp2p/websockets": "^3.0.0", "react": "^17.0.2", diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json index f53ae096a1..23b00d2145 100644 --- a/examples/libp2p-in-the-browser/package.json +++ b/examples/libp2p-in-the-browser/package.json @@ -11,7 +11,7 @@ "dependencies": { "@chainsafe/libp2p-noise": "^8.0.0", "@libp2p/bootstrap": "^2.0.0", - "@libp2p/mplex": "^5.0.0", + "@libp2p/mplex": "^5.2.1", "@libp2p/webrtc-star": "^3.0.0", "@libp2p/websockets": "^3.0.0", "libp2p": "../../" diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index d5be295f4a..e1bf6de6c6 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -12,7 +12,7 @@ "@libp2p/webrtc-direct": "^2.0.0", "@chainsafe/libp2p-noise": "^8.0.0", "@libp2p/bootstrap": "^2.0.0", - "@libp2p/mplex": "^5.0.0", + "@libp2p/mplex": "^5.2.1", "libp2p": "../../", "wrtc": "^0.4.7" }, diff --git a/package.json b/package.json index 074b7c7638..b5f7f71193 100644 --- a/package.json +++ b/package.json @@ -180,7 +180,7 @@ "@libp2p/interop": "^2.0.0", "@libp2p/kad-dht": "^3.0.4", "@libp2p/mdns": "^3.0.0", - "@libp2p/mplex": "^5.0.0", + "@libp2p/mplex": "^5.2.1", "@libp2p/pubsub": "^3.0.1", "@libp2p/tcp": "^3.0.5", "@libp2p/topology": "^3.0.0", diff --git a/src/upgrader.ts b/src/upgrader.ts index bc0a8d960f..23159d0335 100644 --- a/src/upgrader.ts +++ b/src/upgrader.ts @@ -440,20 +440,17 @@ export class DefaultUpgrader extends EventEmitter implements Upg throw err } - muxedStream.stat.protocol = protocol - // If a protocol stream has been successfully negotiated and is to be passed to the application, // the peerstore should ensure that the peer is registered with that protocol this.components.getPeerStore().protoBook.add(remotePeer, [protocol]).catch(err => log.error(err)) - return { - ...muxedStream, - ...stream, - stat: { - ...muxedStream.stat, - protocol - } - } + // after the handshake the returned stream can have early data so override + // the souce/sink + muxedStream.source = stream.source + muxedStream.sink = stream.sink + muxedStream.stat.protocol = protocol + + return muxedStream } catch (err: any) { log.error('could not create new stream', err) From dd14f82ed5f4dfb082c4cbedab2935881788b8b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Sep 2022 12:47:31 +0100 Subject: [PATCH 414/447] deps(dev): bump ipfs-http-client from 57.0.3 to 58.0.0 (#1369) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b5f7f71193..1fcb950f24 100644 --- a/package.json +++ b/package.json @@ -196,7 +196,7 @@ "execa": "^6.1.0", "go-libp2p": "^0.0.6", "into-stream": "^7.0.0", - "ipfs-http-client": "^57.0.1", + "ipfs-http-client": "^58.0.0", "it-pushable": "^3.0.0", "it-to-buffer": "^2.0.2", "nock": "^13.0.3", From b87632f97f44aecf583df06aed865bc4e087391a Mon Sep 17 00:00:00 2001 From: Cayman Date: Fri, 9 Sep 2022 06:27:34 -0400 Subject: [PATCH 415/447] fix: add yamux interop tests (#1290) Test stream compatibility with https://www.npmjs.com/package/@chainsafe/libp2p-yamux Co-authored-by: achingbrain --- package.json | 7 ++++--- src/upgrader.ts | 6 +++++- test/interop.ts | 9 ++++++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 1fcb950f24..81e524a0b0 100644 --- a/package.json +++ b/package.json @@ -168,16 +168,17 @@ }, "devDependencies": { "@chainsafe/libp2p-noise": "^8.0.0", + "@chainsafe/libp2p-yamux": "^1.0.0", "@libp2p/bootstrap": "^2.0.0", - "@libp2p/daemon-client": "^2.0.4", - "@libp2p/daemon-server": "^2.0.4", + "@libp2p/daemon-client": "^3.0.0", + "@libp2p/daemon-server": "^3.0.0", "@libp2p/delegated-content-routing": "^2.0.1", "@libp2p/delegated-peer-routing": "^2.0.1", "@libp2p/floodsub": "^3.0.0", "@libp2p/interface-compliance-tests": "^3.0.1", "@libp2p/interface-connection-encrypter-compliance-tests": "^2.0.1", "@libp2p/interface-mocks": "^4.0.1", - "@libp2p/interop": "^2.0.0", + "@libp2p/interop": "^3.0.0", "@libp2p/kad-dht": "^3.0.4", "@libp2p/mdns": "^3.0.0", "@libp2p/mplex": "^5.2.1", diff --git a/src/upgrader.ts b/src/upgrader.ts index 23159d0335..6b12304721 100644 --- a/src/upgrader.ts +++ b/src/upgrader.ts @@ -374,6 +374,10 @@ export class DefaultUpgrader extends EventEmitter implements Upg return } + // after the handshake the returned stream can have early data so override + // the souce/sink + muxedStream.source = stream.source + muxedStream.sink = stream.sink muxedStream.stat.protocol = protocol // If a protocol stream has been successfully negotiated and is to be passed to the application, @@ -381,7 +385,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg this.components.getPeerStore().protoBook.add(remotePeer, [protocol]).catch(err => log.error(err)) connection.addStream(muxedStream) - this._onStream({ connection, stream: { ...muxedStream, ...stream }, protocol }) + this._onStream({ connection, stream: muxedStream, protocol }) }) .catch(err => { log.error(err) diff --git a/test/interop.ts b/test/interop.ts index bf83312e6c..9768e73e63 100644 --- a/test/interop.ts +++ b/test/interop.ts @@ -12,6 +12,7 @@ import { execa } from 'execa' import pDefer from 'p-defer' import { logger } from '@libp2p/logger' import { Mplex } from '@libp2p/mplex' +import { Yamux } from '@chainsafe/libp2p-yamux' import fs from 'fs' import { unmarshalPrivateKey } from '@libp2p/crypto/keys' import type { PeerId } from '@libp2p/interface-peer-id' @@ -93,10 +94,16 @@ async function createJsPeer (options: SpawnOptions): Promise { listen: ['/ip4/0.0.0.0/tcp/0'] }, transports: [new TCP()], - streamMuxers: [new Mplex()], + streamMuxers: [], connectionEncryption: [new Noise()] } + if (options.muxer === 'mplex') { + opts.streamMuxers?.push(new Mplex()) + } else { + opts.streamMuxers?.push(new Yamux()) + } + if (options.dht === true) { // go-libp2p-daemon only has the older single-table DHT instead of the dual // lan/wan version found in recent go-ipfs versions. unfortunately it's been From 0218acfae26fa69475b2ce0678b1c754c7eda605 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 9 Sep 2022 19:00:11 +0100 Subject: [PATCH 416/447] fix: report dialer metrics (#1377) Converts the dialer to a component so it can access metrics --- package.json | 3 +- src/connection-manager/dialer/dial-request.ts | 2 +- src/connection-manager/dialer/index.ts | 16 +++-- src/connection-manager/index.ts | 10 +--- src/libp2p.ts | 6 +- test/dialing/dial-request.spec.ts | 15 ++--- test/dialing/direct.node.ts | 26 +++------ test/dialing/direct.spec.ts | 58 +++++++------------ 8 files changed, 54 insertions(+), 82 deletions(-) diff --git a/package.json b/package.json index 81e524a0b0..859b41dd4b 100644 --- a/package.json +++ b/package.json @@ -98,12 +98,13 @@ }, "dependencies": { "@achingbrain/nat-port-mapper": "^1.0.3", - "@libp2p/components": "^2.0.3", + "@libp2p/components": "^2.1.0", "@libp2p/connection": "^4.0.1", "@libp2p/crypto": "^1.0.3", "@libp2p/interface-address-manager": "^1.0.2", "@libp2p/interface-connection": "^3.0.1", "@libp2p/interface-connection-encrypter": "^2.0.1", + "@libp2p/interface-connection-manager": "^1.1.0", "@libp2p/interface-content-routing": "^1.0.2", "@libp2p/interface-dht": "^1.0.1", "@libp2p/interface-metrics": "^3.0.0", diff --git a/src/connection-manager/dialer/dial-request.ts b/src/connection-manager/dialer/dial-request.ts index 9004bb4d23..18e90de34f 100644 --- a/src/connection-manager/dialer/dial-request.ts +++ b/src/connection-manager/dialer/dial-request.ts @@ -7,7 +7,7 @@ import { logger } from '@libp2p/logger' import type { Multiaddr } from '@multiformats/multiaddr' import type { Connection } from '@libp2p/interface-connection' import type { AbortOptions } from '@libp2p/interfaces' -import type { Dialer } from './index.js' +import type { Dialer } from '@libp2p/interface-connection-manager' const log = logger('libp2p:dialer:dial-request') diff --git a/src/connection-manager/dialer/index.ts b/src/connection-manager/dialer/index.ts index f40c735be7..9015965589 100644 --- a/src/connection-manager/dialer/index.ts +++ b/src/connection-manager/dialer/index.ts @@ -24,10 +24,11 @@ import type { Startable } from '@libp2p/interfaces/startable' import type { PeerId } from '@libp2p/interface-peer-id' import { getPeer } from '../../get-peer.js' import sort from 'it-sort' -import { Components, Initializable } from '@libp2p/components' +import type { Components } from '@libp2p/components' import map from 'it-map' import type { AddressSorter } from '@libp2p/interface-peer-store' import type { ComponentMetricsTracker } from '@libp2p/interface-metrics' +import type { Dialer } from '@libp2p/interface-connection-manager' const log = logger('libp2p:dialer') @@ -85,8 +86,8 @@ export interface DialerInit { metrics?: ComponentMetricsTracker } -export class Dialer implements Startable, Initializable { - private components: Components = new Components() +export class DefaultDialer implements Startable, Dialer { + private readonly components: Components private readonly addressSorter: AddressSorter private readonly maxAddrsToDial: number private readonly timeout: number @@ -96,13 +97,14 @@ export class Dialer implements Startable, Initializable { public pendingDialTargets: Map private started: boolean - constructor (init: DialerInit = {}) { + constructor (components: Components, init: DialerInit = {}) { this.started = false this.addressSorter = init.addressSorter ?? publicAddressesFirst this.maxAddrsToDial = init.maxAddrsToDial ?? MAX_ADDRS_TO_DIAL this.timeout = init.dialTimeout ?? DIAL_TIMEOUT this.maxDialsPerPeer = init.maxDialsPerPeer ?? MAX_PER_PEER_DIALS this.tokens = [...new Array(init.maxParallelDials ?? MAX_PARALLEL_DIALS)].map((_, index) => index) + this.components = components this.pendingDials = trackedMap({ component: METRICS_COMPONENT, metric: METRICS_PENDING_DIALS, @@ -111,7 +113,7 @@ export class Dialer implements Startable, Initializable { this.pendingDialTargets = trackedMap({ component: METRICS_COMPONENT, metric: METRICS_PENDING_DIAL_TARGETS, - metrics: init.metrics + metrics: components.getMetrics() }) for (const [key, value] of Object.entries(init.resolvers ?? {})) { @@ -119,10 +121,6 @@ export class Dialer implements Startable, Initializable { } } - init (components: Components): void { - this.components = components - } - isStarted () { return this.started } diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index 4b6f001a9c..c3d0de88d7 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -14,7 +14,6 @@ import type { Connection } from '@libp2p/interface-connection' import type { ConnectionManager } from '@libp2p/interface-connection-manager' import { Components, Initializable } from '@libp2p/components' import * as STATUS from '@libp2p/interface-connection/status' -import { Dialer } from './dialer/index.js' import type { AddressSorter } from '@libp2p/interface-peer-store' import type { Resolver } from '@multiformats/multiaddr' import { PeerMap } from '@libp2p/peer-collections' @@ -144,7 +143,6 @@ export interface ConnectionManagerEvents { * Responsible for managing known connections. */ export class DefaultConnectionManager extends EventEmitter implements ConnectionManager, Startable, Initializable { - public readonly dialer: Dialer private components = new Components() private readonly opts: Required private readonly connections: Map @@ -184,8 +182,6 @@ export class DefaultConnectionManager extends EventEmitter implements Libp2p { inboundUpgradeTimeout: init.connectionManager.inboundUpgradeTimeout })) + // Create the dialer + this.components.setDialer(new DefaultDialer(this.components, init.connectionManager)) + // Create the Connection Manager this.connectionManager = this.components.setConnectionManager(new DefaultConnectionManager(init.connectionManager)) @@ -338,7 +342,7 @@ export class Libp2pNode extends EventEmitter implements Libp2p { ) await Promise.all( - this.services.map(servce => servce.stop()) + this.services.map(service => service.stop()) ) await Promise.all( diff --git a/test/dialing/dial-request.spec.ts b/test/dialing/dial-request.spec.ts index b6dd5222a3..cf9840fe8f 100644 --- a/test/dialing/dial-request.spec.ts +++ b/test/dialing/dial-request.spec.ts @@ -9,7 +9,8 @@ import { DialAction, DialRequest } from '../../src/connection-manager/dialer/dia import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-mocks' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { Multiaddr } from '@multiformats/multiaddr' -import { Dialer } from '../../src/connection-manager/dialer/index.js' +import { DefaultDialer } from '../../src/connection-manager/dialer/index.js' +import { Components } from '@libp2p/components' const error = new Error('dial failure') describe('Dial Request', () => { @@ -23,7 +24,7 @@ describe('Dial Request', () => { } const dialAction: DialAction = async (num) => await actions[num.toString()]() const controller = new AbortController() - const dialer = new Dialer({ + const dialer = new DefaultDialer(new Components(), { maxParallelDials: 2 }) const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') @@ -53,7 +54,7 @@ describe('Dial Request', () => { } const dialAction: DialAction = async (num) => await actions[num.toString()]() const controller = new AbortController() - const dialer = new Dialer({ + const dialer = new DefaultDialer(new Components(), { maxParallelDials: 2 }) const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') @@ -98,7 +99,7 @@ describe('Dial Request', () => { const dialAction: DialAction = async (num) => await actions[num.toString()]() const addrs = Object.keys(actions) const controller = new AbortController() - const dialer = new Dialer({ + const dialer = new DefaultDialer(new Components(), { maxParallelDials: 2 }) const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') @@ -138,7 +139,7 @@ describe('Dial Request', () => { const dialAction: DialAction = async (num) => await actions[num.toString()]() const controller = new AbortController() - const dialer = new Dialer({ + const dialer = new DefaultDialer(new Components(), { maxParallelDials: 2 }) const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') @@ -184,7 +185,7 @@ describe('Dial Request', () => { const dialAction: DialAction = async (num) => await actions[num.toString()]() const addrs = Object.keys(actions) const controller = new AbortController() - const dialer = new Dialer({ + const dialer = new DefaultDialer(new Components(), { maxParallelDials: 2 }) const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') @@ -237,7 +238,7 @@ describe('Dial Request', () => { const dialRequest = new DialRequest({ addrs: Object.keys(actions).map(str => new Multiaddr(str)), - dialer: new Dialer({ + dialer: new DefaultDialer(new Components(), { maxParallelDials: 3 }), dialAction: async (ma, opts) => { diff --git a/test/dialing/direct.node.ts b/test/dialing/direct.node.ts index d4965adba0..146701f873 100644 --- a/test/dialing/direct.node.ts +++ b/test/dialing/direct.node.ts @@ -17,7 +17,7 @@ import { Connection, isConnection } from '@libp2p/interface-connection' import { AbortError } from '@libp2p/interfaces/errors' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { MemoryDatastore } from 'datastore-core/memory' -import { Dialer } from '../../src/connection-manager/dialer/index.js' +import { DefaultDialer } from '../../src/connection-manager/dialer/index.js' import { DefaultAddressManager } from '../../src/address-manager/index.js' import { PersistentPeerStore } from '@libp2p/peer-store' import { DefaultTransportManager } from '../../src/transport-manager.js' @@ -95,8 +95,7 @@ describe('Dialing (direct, TCP)', () => { }) it('should be able to connect to a remote node via its multiaddr', async () => { - const dialer = new Dialer() - dialer.init(localComponents) + const dialer = new DefaultDialer(localComponents) const connection = await dialer.dial(remoteAddr) expect(connection).to.exist() @@ -104,8 +103,7 @@ describe('Dialing (direct, TCP)', () => { }) it('should fail to connect to an unsupported multiaddr', async () => { - const dialer = new Dialer() - dialer.init(localComponents) + const dialer = new DefaultDialer(localComponents) await expect(dialer.dial(unsupportedAddr)) .to.eventually.be.rejectedWith(Error) @@ -113,8 +111,7 @@ describe('Dialing (direct, TCP)', () => { }) it('should fail to connect if peer has no known addresses', async () => { - const dialer = new Dialer() - dialer.init(localComponents) + const dialer = new DefaultDialer(localComponents) const peerId = await createFromJSON(Peers[1]) await expect(dialer.dial(peerId)) @@ -125,8 +122,7 @@ describe('Dialing (direct, TCP)', () => { it('should be able to connect to a given peer id', async () => { await localComponents.getPeerStore().addressBook.set(remoteComponents.getPeerId(), remoteTM.getAddrs()) - const dialer = new Dialer() - dialer.init(localComponents) + const dialer = new DefaultDialer(localComponents) const connection = await dialer.dial(remoteComponents.getPeerId()) expect(connection).to.exist() @@ -136,8 +132,7 @@ describe('Dialing (direct, TCP)', () => { it('should fail to connect to a given peer with unsupported addresses', async () => { await localComponents.getPeerStore().addressBook.add(remoteComponents.getPeerId(), [unsupportedAddr]) - const dialer = new Dialer() - dialer.init(localComponents) + const dialer = new DefaultDialer(localComponents) await expect(dialer.dial(remoteComponents.getPeerId())) .to.eventually.be.rejectedWith(Error) @@ -150,8 +145,7 @@ describe('Dialing (direct, TCP)', () => { const peerId = await createFromJSON(Peers[1]) await localComponents.getPeerStore().addressBook.add(peerId, [...remoteAddrs, unsupportedAddr]) - const dialer = new Dialer() - dialer.init(localComponents) + const dialer = new DefaultDialer(localComponents) sinon.spy(localTM, 'dial') const connection = await dialer.dial(peerId) @@ -162,10 +156,9 @@ describe('Dialing (direct, TCP)', () => { }) it('should abort dials on queue task timeout', async () => { - const dialer = new Dialer({ + const dialer = new DefaultDialer(localComponents, { dialTimeout: 50 }) - dialer.init(localComponents) sinon.stub(localTM, 'dial').callsFake(async (addr, options = {}) => { expect(options.signal).to.exist() @@ -191,10 +184,9 @@ describe('Dialing (direct, TCP)', () => { await localComponents.getPeerStore().addressBook.add(peerId, addrs) - const dialer = new Dialer({ + const dialer = new DefaultDialer(localComponents, { maxParallelDials: 2 }) - dialer.init(localComponents) expect(dialer.tokens).to.have.lengthOf(2) diff --git a/test/dialing/direct.spec.ts b/test/dialing/direct.spec.ts index f20ad79c49..d2f0a82649 100644 --- a/test/dialing/direct.spec.ts +++ b/test/dialing/direct.spec.ts @@ -13,7 +13,7 @@ import { AbortError } from '@libp2p/interfaces/errors' import { MemoryDatastore } from 'datastore-core/memory' import { codes as ErrorCodes } from '../../src/errors.js' import * as Constants from '../../src/constants.js' -import { Dialer, DialTarget } from '../../src/connection-manager/dialer/index.js' +import { DefaultDialer, DialTarget } from '../../src/connection-manager/dialer/index.js' import { publicAddressesFirst } from '@libp2p/utils/address-sort' import { PersistentPeerStore } from '@libp2p/peer-store' import { DefaultTransportManager } from '../../src/transport-manager.js' @@ -72,8 +72,7 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should limit the number of tokens it provides', () => { - const dialer = new Dialer() - dialer.init(localComponents) + const dialer = new DefaultDialer(localComponents) const maxPerPeer = Constants.MAX_PER_PEER_DIALS expect(dialer.tokens).to.have.lengthOf(Constants.MAX_PARALLEL_DIALS) @@ -83,10 +82,9 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should not return tokens if none are left', () => { - const dialer = new Dialer({ + const dialer = new DefaultDialer(localComponents, { maxDialsPerPeer: Infinity }) - dialer.init(localComponents) const maxTokens = dialer.tokens.length @@ -97,8 +95,7 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should NOT be able to return a token twice', () => { - const dialer = new Dialer() - dialer.init(localComponents) + const dialer = new DefaultDialer(localComponents) const tokens = dialer.getTokens(1) expect(tokens).to.have.length(1) @@ -109,8 +106,7 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should be able to connect to a remote node via its multiaddr', async () => { - const dialer = new Dialer() - dialer.init(localComponents) + const dialer = new DefaultDialer(localComponents) const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') await localComponents.getPeerStore().addressBook.set(remotePeerId, [remoteAddr]) @@ -121,8 +117,7 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should fail to connect to an unsupported multiaddr', async () => { - const dialer = new Dialer() - dialer.init(localComponents) + const dialer = new DefaultDialer(localComponents) await expect(dialer.dial(unsupportedAddr.encapsulate(`/p2p/${remoteComponents.getPeerId().toString()}`))) .to.eventually.be.rejectedWith(Error) @@ -130,8 +125,7 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should be able to connect to a given peer', async () => { - const dialer = new Dialer() - dialer.init(localComponents) + const dialer = new DefaultDialer(localComponents) const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') await localComponents.getPeerStore().addressBook.set(remotePeerId, [remoteAddr]) @@ -142,8 +136,7 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should fail to connect to a given peer with unsupported addresses', async () => { - const dialer = new Dialer() - dialer.init(localComponents) + const dialer = new DefaultDialer(localComponents) const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') await localComponents.getPeerStore().addressBook.set(remotePeerId, [unsupportedAddr]) @@ -154,10 +147,9 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should abort dials on queue task timeout', async () => { - const dialer = new Dialer({ + const dialer = new DefaultDialer(localComponents, { dialTimeout: 50 }) - dialer.init(localComponents) const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') await localComponents.getPeerStore().addressBook.set(remotePeerId, [remoteAddr]) @@ -177,10 +169,9 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should throw when a peer advertises more than the allowed number of peers', async () => { - const dialer = new Dialer({ + const dialer = new DefaultDialer(localComponents, { maxAddrsToDial: 10 }) - dialer.init(localComponents) const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') await localComponents.getPeerStore().addressBook.set(remotePeerId, Array.from({ length: 11 }, (_, i) => new Multiaddr(`/ip4/127.0.0.1/tcp/1500${i}/ws/p2p/12D3KooWHFKTMzwerBtsVmtz4ZZEQy2heafxzWw6wNn5PPYkBxJ5`))) @@ -200,11 +191,10 @@ describe('Dialing (direct, WebSockets)', () => { const publicAddressesFirstSpy = sinon.spy(publicAddressesFirst) const localTMDialStub = sinon.stub(localTM, 'dial').callsFake(async (ma) => mockConnection(mockMultiaddrConnection(mockDuplex(), peerIdFromString(ma.getPeerId() ?? '')))) - const dialer = new Dialer({ + const dialer = new DefaultDialer(localComponents, { addressSorter: publicAddressesFirstSpy, maxParallelDials: 3 }) - dialer.init(localComponents) // Inject data in the AddressBook await localComponents.getPeerStore().addressBook.add(remoteComponents.getPeerId(), peerMultiaddrs) @@ -229,10 +219,9 @@ describe('Dialing (direct, WebSockets)', () => { ] const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') - const dialer = new Dialer({ + const dialer = new DefaultDialer(localComponents, { maxParallelDials: 2 }) - dialer.init(localComponents) // Inject data in the AddressBook await localComponents.getPeerStore().addressBook.add(remotePeerId, addrs) @@ -268,10 +257,9 @@ describe('Dialing (direct, WebSockets)', () => { new Multiaddr('/ip4/0.0.0.0/tcp/8001/ws'), new Multiaddr('/ip4/0.0.0.0/tcp/8002/ws') ] - const dialer = new Dialer({ + const dialer = new DefaultDialer(localComponents, { maxParallelDials: 2 }) - dialer.init(localComponents) // Inject data in the AddressBook await localComponents.getPeerStore().addressBook.add(remoteComponents.getPeerId(), addrs) @@ -309,8 +297,7 @@ describe('Dialing (direct, WebSockets)', () => { }) it('should cancel pending dial targets before proceeding', async () => { - const dialer = new Dialer() - dialer.init(localComponents) + const dialer = new DefaultDialer(localComponents) sinon.stub(dialer, '_createDialTarget').callsFake(async () => { const deferredDial = pDefer() @@ -364,8 +351,7 @@ describe('libp2p.dialer (direct, WebSockets)', () => { ] }) - const connectionManager = libp2p.components.getConnectionManager() as DefaultConnectionManager - const dialer = connectionManager.dialer + const dialer = libp2p.components.getDialer() expect(dialer).to.exist() expect(dialer).to.have.property('tokens').with.lengthOf(Constants.MAX_PARALLEL_DIALS) @@ -395,8 +381,7 @@ describe('libp2p.dialer (direct, WebSockets)', () => { } libp2p = await createLibp2pNode(config) - const connectionManager = libp2p.components.getConnectionManager() as DefaultConnectionManager - const dialer = connectionManager.dialer + const dialer = libp2p.components.getDialer() expect(dialer).to.exist() expect(dialer).to.have.property('tokens').with.lengthOf(config.connectionManager.maxParallelDials) @@ -420,8 +405,7 @@ describe('libp2p.dialer (direct, WebSockets)', () => { ] }) - const connectionManager = libp2p.components.getConnectionManager() as DefaultConnectionManager - const dialerDialSpy = sinon.spy(connectionManager.dialer, 'dial') + const dialerDialSpy = sinon.spy(libp2p.components.getDialer(), 'dial') const addressBookAddSpy = sinon.spy(libp2p.components.getPeerStore().addressBook, 'add') await libp2p.start() @@ -543,8 +527,8 @@ describe('libp2p.dialer (direct, WebSockets)', () => { ] }) - const connectionManager = libp2p.components.getConnectionManager() as DefaultConnectionManager - sinon.stub(connectionManager.dialer, '_createDialTarget').callsFake(async () => { + const dialer = libp2p.components.getDialer() as DefaultDialer + sinon.stub(dialer, '_createDialTarget').callsFake(async () => { const deferredDial = pDefer() return await deferredDial.promise }) @@ -582,8 +566,8 @@ describe('libp2p.dialer (direct, WebSockets)', () => { await libp2p.start() - const connectionManager = libp2p.components.getConnectionManager() as DefaultConnectionManager - const dialerDestroyStub = sinon.spy(connectionManager.dialer, 'stop') + const dialer = libp2p.components.getDialer() as DefaultDialer + const dialerDestroyStub = sinon.spy(dialer, 'stop') await libp2p.stop() From 111e75d05e41eb2141fefb2cfafee0937fe068c2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 11 Sep 2022 09:04:59 +0100 Subject: [PATCH 417/447] chore: release 0.39.1 (#1376) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8e247f544..bdfa61b540 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,15 @@ +### [0.39.1](https://www.github.com/libp2p/js-libp2p/compare/v0.39.0...v0.39.1) (2022-09-09) + + +### Bug Fixes + +* add yamux interop tests ([#1290](https://www.github.com/libp2p/js-libp2p/issues/1290)) ([b87632f](https://www.github.com/libp2p/js-libp2p/commit/b87632f97f44aecf583df06aed865bc4e087391a)) +* overwrite stream fields after handshake ([#1305](https://www.github.com/libp2p/js-libp2p/issues/1305)) ([43b0418](https://www.github.com/libp2p/js-libp2p/commit/43b04189987f11a7729b522d1e1dbdc1caceb874)) +* report dialer metrics ([#1377](https://www.github.com/libp2p/js-libp2p/issues/1377)) ([0218acf](https://www.github.com/libp2p/js-libp2p/commit/0218acfae26fa69475b2ce0678b1c754c7eda605)) + ## [0.39.0](https://www.github.com/libp2p/js-libp2p/compare/v0.38.0...v0.39.0) (2022-09-05) diff --git a/package.json b/package.json index 859b41dd4b..9861754814 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.39.0", + "version": "0.39.1", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p#readme", From a4566ede92253a59e07713cefe2dda3a9d8e5fa7 Mon Sep 17 00:00:00 2001 From: "libp2p-mgmt-read-write[bot]" <104492852+libp2p-mgmt-read-write[bot]@users.noreply.github.com> Date: Mon, 19 Sep 2022 13:40:57 +0000 Subject: [PATCH 418/447] chore: Update .github/workflows/stale.yml [skip ci] --- .github/workflows/stale.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000000..6f6d895d19 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,26 @@ +name: Close and mark stale issue + +on: + schedule: + - cron: '0 0 * * *' + +jobs: + stale: + + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + + steps: + - uses: actions/stale@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'Oops, seems like we needed more information for this issue, please comment with more details or this issue will be closed in 7 days.' + close-issue-message: 'This issue was closed because it is missing author input.' + stale-issue-label: 'kind/stale' + any-of-labels: 'need/author-input' + exempt-issue-labels: 'need/triage,need/community-input,need/maintainer-input,need/maintainers-input,need/analysis,status/blocked,status/in-progress,status/ready,status/deferred,status/inactive' + days-before-issue-stale: 6 + days-before-issue-close: 7 + enable-statistics: true From 633d4a9740ea02e32c0bb290c0a3958b68f181e9 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 21 Sep 2022 14:41:20 +0100 Subject: [PATCH 419/447] fix: remove ipfs dependency and upgrade multiaddr (#1387) - Upgrades @multiformats/multiaddr to 11.0.0 - Removes ipfs-http-client and delegate router dependencies - Test delegation using interface stubs instead of implementations --- examples/chat/src/dialer.js | 4 +- examples/delegated-routing/package.json | 8 +- examples/libp2p-in-the-browser/package.json | 10 +- examples/webrtc-direct/package.json | 6 +- package.json | 63 ++++--- src/address-manager/index.ts | 15 +- src/circuit/README.md | 4 +- src/circuit/circuit/utils.ts | 6 +- src/circuit/listener.ts | 5 +- src/circuit/transport.ts | 13 +- src/connection-manager/dialer/index.ts | 7 +- src/get-peer.ts | 7 +- src/identify/index.ts | 8 +- src/nat-manager.ts | 4 +- test/addresses/address-manager.spec.ts | 8 +- test/addresses/addresses.node.ts | 8 +- test/content-routing/content-routing.node.ts | 123 +++++--------- test/content-routing/dht/operation.node.ts | 7 +- test/core/consume-peer-record.spec.ts | 4 +- test/dialing/dial-request.spec.ts | 14 +- test/dialing/direct.node.ts | 13 +- test/dialing/direct.spec.ts | 27 +-- test/dialing/resolver.spec.ts | 21 +-- test/fixtures/browser.ts | 4 +- test/identify/index.spec.ts | 4 +- test/identify/push.spec.ts | 8 +- test/identify/service.spec.ts | 4 +- test/insecure/plaintext.spec.ts | 10 +- test/interop.ts | 6 +- test/peer-discovery/index.node.ts | 4 +- test/peer-routing/peer-routing.node.ts | 168 +++++-------------- test/ping/ping.node.ts | 4 +- test/pnet/index.spec.ts | 10 +- test/relay/auto-relay.node.ts | 65 +++---- test/relay/relay.node.ts | 4 +- test/transports/transport-manager.node.ts | 6 +- test/transports/transport-manager.spec.ts | 6 +- test/upgrading/upgrader.spec.ts | 6 +- test/utils/creators/peer.ts | 4 +- 39 files changed, 286 insertions(+), 412 deletions(-) diff --git a/examples/chat/src/dialer.js b/examples/chat/src/dialer.js index 8f37e7551b..0143bfcccf 100644 --- a/examples/chat/src/dialer.js +++ b/examples/chat/src/dialer.js @@ -1,6 +1,6 @@ /* eslint-disable no-console */ -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { createLibp2p } from './libp2p.js' import { stdinToStream, streamToConsole } from './stream.js' import { createFromJSON } from '@libp2p/peer-id-factory' @@ -31,7 +31,7 @@ async function run () { }) // Dial to the remote peer (the "listener") - const listenerMa = new Multiaddr(`/ip4/127.0.0.1/tcp/10333/p2p/${idListener.toString()}`) + const listenerMa = multiaddr(`/ip4/127.0.0.1/tcp/10333/p2p/${idListener.toString()}`) const stream = await nodeDialer.dialProtocol(listenerMa, '/chat/1.0.0') console.log('Dialer dialed to listener on protocol: /chat/1.0.0') diff --git a/examples/delegated-routing/package.json b/examples/delegated-routing/package.json index ddbbecc37c..0a2590243e 100644 --- a/examples/delegated-routing/package.json +++ b/examples/delegated-routing/package.json @@ -3,15 +3,15 @@ "version": "0.1.0", "private": true, "dependencies": { - "@chainsafe/libp2p-noise": "^8.0.0", + "@chainsafe/libp2p-noise": "^8.0.1", "ipfs-core": "^0.15.4", "libp2p": "../../", "@libp2p/delegated-content-routing": "^2.0.1", "@libp2p/delegated-peer-routing": "^2.0.1", "@libp2p/kad-dht": "^3.0.0", - "@libp2p/mplex": "^5.2.1", - "@libp2p/webrtc-star": "^3.0.0", - "@libp2p/websockets": "^3.0.0", + "@libp2p/mplex": "^5.2.3", + "@libp2p/webrtc-star": "^3.0.3", + "@libp2p/websockets": "^3.0.4", "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "5.0.0" diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json index 23b00d2145..8758c8fa6d 100644 --- a/examples/libp2p-in-the-browser/package.json +++ b/examples/libp2p-in-the-browser/package.json @@ -9,11 +9,11 @@ }, "license": "ISC", "dependencies": { - "@chainsafe/libp2p-noise": "^8.0.0", - "@libp2p/bootstrap": "^2.0.0", - "@libp2p/mplex": "^5.2.1", - "@libp2p/webrtc-star": "^3.0.0", - "@libp2p/websockets": "^3.0.0", + "@chainsafe/libp2p-noise": "^8.0.1", + "@libp2p/bootstrap": "^2.0.1", + "@libp2p/mplex": "^5.2.3", + "@libp2p/webrtc-star": "^3.0.3", + "@libp2p/websockets": "^3.0.4", "libp2p": "../../" }, "devDependencies": { diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index e1bf6de6c6..b53e5b3ca0 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -10,9 +10,9 @@ "license": "ISC", "dependencies": { "@libp2p/webrtc-direct": "^2.0.0", - "@chainsafe/libp2p-noise": "^8.0.0", - "@libp2p/bootstrap": "^2.0.0", - "@libp2p/mplex": "^5.2.1", + "@chainsafe/libp2p-noise": "^8.0.1", + "@libp2p/bootstrap": "^2.0.1", + "@libp2p/mplex": "^5.2.3", "libp2p": "../../", "wrtc": "^0.4.7" }, diff --git a/package.json b/package.json index 9861754814..f15267950d 100644 --- a/package.json +++ b/package.json @@ -99,36 +99,36 @@ "dependencies": { "@achingbrain/nat-port-mapper": "^1.0.3", "@libp2p/components": "^2.1.0", - "@libp2p/connection": "^4.0.1", - "@libp2p/crypto": "^1.0.3", - "@libp2p/interface-address-manager": "^1.0.2", - "@libp2p/interface-connection": "^3.0.1", + "@libp2p/connection": "^4.0.2", + "@libp2p/crypto": "^1.0.4", + "@libp2p/interface-address-manager": "^1.0.3", + "@libp2p/interface-connection": "^3.0.2", "@libp2p/interface-connection-encrypter": "^2.0.1", - "@libp2p/interface-connection-manager": "^1.1.0", + "@libp2p/interface-connection-manager": "^1.1.1", "@libp2p/interface-content-routing": "^1.0.2", "@libp2p/interface-dht": "^1.0.1", "@libp2p/interface-metrics": "^3.0.0", "@libp2p/interface-peer-discovery": "^1.0.1", "@libp2p/interface-peer-id": "^1.0.4", - "@libp2p/interface-peer-info": "^1.0.2", + "@libp2p/interface-peer-info": "^1.0.3", "@libp2p/interface-peer-routing": "^1.0.1", - "@libp2p/interface-peer-store": "^1.2.1", - "@libp2p/interface-pubsub": "^2.0.1", + "@libp2p/interface-peer-store": "^1.2.2", + "@libp2p/interface-pubsub": "^2.1.0", "@libp2p/interface-registrar": "^2.0.3", "@libp2p/interface-stream-muxer": "^2.0.2", - "@libp2p/interface-transport": "^1.0.3", + "@libp2p/interface-transport": "^1.0.4", "@libp2p/interfaces": "^3.0.3", "@libp2p/logger": "^2.0.1", "@libp2p/multistream-select": "^3.0.0", "@libp2p/peer-collections": "^2.0.0", "@libp2p/peer-id": "^1.1.15", "@libp2p/peer-id-factory": "^1.0.18", - "@libp2p/peer-record": "^4.0.2", - "@libp2p/peer-store": "^3.1.3", + "@libp2p/peer-record": "^4.0.3", + "@libp2p/peer-store": "^3.1.5", "@libp2p/tracked-map": "^2.0.1", - "@libp2p/utils": "^3.0.1", + "@libp2p/utils": "^3.0.2", "@multiformats/mafmt": "^11.0.2", - "@multiformats/multiaddr": "^10.3.3", + "@multiformats/multiaddr": "^11.0.0", "abortable-iterator": "^4.0.2", "any-signal": "^3.0.0", "datastore-core": "^8.0.1", @@ -168,26 +168,24 @@ "xsalsa20": "^1.1.0" }, "devDependencies": { - "@chainsafe/libp2p-noise": "^8.0.0", + "@chainsafe/libp2p-noise": "^8.0.1", "@chainsafe/libp2p-yamux": "^1.0.0", - "@libp2p/bootstrap": "^2.0.0", - "@libp2p/daemon-client": "^3.0.0", - "@libp2p/daemon-server": "^3.0.0", - "@libp2p/delegated-content-routing": "^2.0.1", - "@libp2p/delegated-peer-routing": "^2.0.1", + "@libp2p/bootstrap": "^2.0.1", + "@libp2p/daemon-client": "^3.0.1", + "@libp2p/daemon-server": "^3.0.1", "@libp2p/floodsub": "^3.0.0", - "@libp2p/interface-compliance-tests": "^3.0.1", - "@libp2p/interface-connection-encrypter-compliance-tests": "^2.0.1", - "@libp2p/interface-mocks": "^4.0.1", - "@libp2p/interop": "^3.0.0", - "@libp2p/kad-dht": "^3.0.4", - "@libp2p/mdns": "^3.0.0", - "@libp2p/mplex": "^5.2.1", - "@libp2p/pubsub": "^3.0.1", - "@libp2p/tcp": "^3.0.5", - "@libp2p/topology": "^3.0.0", - "@libp2p/webrtc-star": "^3.0.0", - "@libp2p/websockets": "^3.0.0", + "@libp2p/interface-compliance-tests": "^3.0.2", + "@libp2p/interface-connection-encrypter-compliance-tests": "^2.0.2", + "@libp2p/interface-mocks": "^4.0.3", + "@libp2p/interop": "^3.0.1", + "@libp2p/kad-dht": "^3.0.5", + "@libp2p/mdns": "^3.0.1", + "@libp2p/mplex": "^5.2.3", + "@libp2p/pubsub": "^3.1.3", + "@libp2p/tcp": "^3.1.1", + "@libp2p/topology": "^3.0.1", + "@libp2p/webrtc-star": "^3.0.3", + "@libp2p/websockets": "^3.0.4", "@types/node-forge": "^1.0.0", "@types/p-fifo": "^1.0.0", "@types/varint": "^6.0.0", @@ -197,11 +195,8 @@ "delay": "^5.0.0", "execa": "^6.1.0", "go-libp2p": "^0.0.6", - "into-stream": "^7.0.0", - "ipfs-http-client": "^58.0.0", "it-pushable": "^3.0.0", "it-to-buffer": "^2.0.2", - "nock": "^13.0.3", "npm-run-all": "^4.1.5", "p-defer": "^4.0.0", "p-event": "^5.0.1", diff --git a/src/address-manager/index.ts b/src/address-manager/index.ts index e011bde4f7..b6726994c3 100644 --- a/src/address-manager/index.ts +++ b/src/address-manager/index.ts @@ -1,6 +1,7 @@ import type { AddressManagerEvents } from '@libp2p/interface-address-manager' import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events' -import { Multiaddr } from '@multiformats/multiaddr' +import type { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { peerIdFromString } from '@libp2p/peer-id' import type { Components } from '@libp2p/components' @@ -58,28 +59,28 @@ export class DefaultAddressManager extends EventEmitter { * Get peer listen multiaddrs */ getListenAddrs (): Multiaddr[] { - return Array.from(this.listen).map((a) => new Multiaddr(a)) + return Array.from(this.listen).map((a) => multiaddr(a)) } /** * Get peer announcing multiaddrs */ getAnnounceAddrs (): Multiaddr[] { - return Array.from(this.announce).map((a) => new Multiaddr(a)) + return Array.from(this.announce).map((a) => multiaddr(a)) } /** * Get observed multiaddrs */ getObservedAddrs (): Multiaddr[] { - return Array.from(this.observed).map((a) => new Multiaddr(a)) + return Array.from(this.observed).map((a) => multiaddr(a)) } /** * Add peer observed addresses */ addObservedAddr (addr: string | Multiaddr): void { - let ma = new Multiaddr(addr) + let ma = multiaddr(addr) const remotePeer = ma.getPeerId() // strip our peer id if it has been passed @@ -88,7 +89,7 @@ export class DefaultAddressManager extends EventEmitter { // use same encoding for comparison if (remotePeerId.equals(this.components.getPeerId())) { - ma = ma.decapsulate(new Multiaddr(`/p2p/${this.components.getPeerId().toString()}`)) + ma = ma.decapsulate(multiaddr(`/p2p/${this.components.getPeerId().toString()}`)) } } @@ -118,7 +119,7 @@ export class DefaultAddressManager extends EventEmitter { // Create advertising list return this.announceFilter(Array.from(addrSet) - .map(str => new Multiaddr(str))) + .map(str => multiaddr(str))) .map(ma => { if (ma.getPeerId() === this.components.getPeerId().toString()) { return ma diff --git a/src/circuit/README.md b/src/circuit/README.md index 330df42a12..4450cebe4f 100644 --- a/src/circuit/README.md +++ b/src/circuit/README.md @@ -37,7 +37,7 @@ Libp2p circuit configuration can be seen at [Setup with Relay](../../doc/CONFIGU Once you have a circuit relay node running, you can configure other nodes to use it as a relay as follows: ```js -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import Libp2p from 'libp2p' import { TCP } from '@libp2p/tcp' import { Mplex } from '@libp2p/mplex' @@ -47,7 +47,7 @@ const relayAddr = ... const node = await createLibp2p({ addresses: { - listen: [new Multiaddr(`${relayAddr}/p2p-circuit`)] + listen: [multiaddr(`${relayAddr}/p2p-circuit`)] }, transports: [ new TCP() diff --git a/src/circuit/circuit/utils.ts b/src/circuit/circuit/utils.ts index 21e5e2938a..b1c6d78e96 100644 --- a/src/circuit/circuit/utils.ts +++ b/src/circuit/circuit/utils.ts @@ -1,4 +1,4 @@ -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { CircuitRelay } from '../pb/index.js' import type { StreamHandler } from './stream-handler.js' @@ -19,7 +19,7 @@ export function validateAddrs (msg: CircuitRelay, streamHandler: StreamHandler) try { if (msg.dstPeer?.addrs != null) { msg.dstPeer.addrs.forEach((addr) => { - return new Multiaddr(addr) + return multiaddr(addr) }) } } catch (err: any) { @@ -32,7 +32,7 @@ export function validateAddrs (msg: CircuitRelay, streamHandler: StreamHandler) try { if (msg.srcPeer?.addrs != null) { msg.srcPeer.addrs.forEach((addr) => { - return new Multiaddr(addr) + return multiaddr(addr) }) } } catch (err: any) { diff --git a/src/circuit/listener.ts b/src/circuit/listener.ts index 0e96cc1f98..7a6422729d 100644 --- a/src/circuit/listener.ts +++ b/src/circuit/listener.ts @@ -3,7 +3,8 @@ import type { ConnectionManager } from '@libp2p/interface-connection-manager' import type { PeerStore } from '@libp2p/interface-peer-store' import type { Listener } from '@libp2p/interface-transport' import { peerIdFromString } from '@libp2p/peer-id' -import { Multiaddr } from '@multiformats/multiaddr' +import type { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' export interface ListenerOptions { peerStore: PeerStore @@ -18,7 +19,7 @@ export function createListener (options: ListenerOptions): Listener { */ async function listen (addr: Multiaddr): Promise { const addrString = addr.toString().split('/p2p-circuit').find(a => a !== '') - const ma = new Multiaddr(addrString) + const ma = multiaddr(addrString) const relayPeerStr = ma.getPeerId() diff --git a/src/circuit/transport.ts b/src/circuit/transport.ts index 4fe69480e6..db21b29765 100644 --- a/src/circuit/transport.ts +++ b/src/circuit/transport.ts @@ -1,7 +1,8 @@ import { logger } from '@libp2p/logger' import errCode from 'err-code' import * as mafmt from '@multiformats/mafmt' -import { Multiaddr } from '@multiformats/multiaddr' +import type { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { CircuitRelay as CircuitPB } from './pb/index.js' import { codes } from '../errors.js' import { streamToMaConnection } from '@libp2p/utils/stream-to-ma-conn' @@ -134,8 +135,8 @@ export class Circuit implements Transport, Initializable { if (virtualConnection != null) { const remoteAddr = connection.remoteAddr .encapsulate('/p2p-circuit') - .encapsulate(new Multiaddr(request.dstPeer?.addrs[0])) - const localAddr = new Multiaddr(request.srcPeer?.addrs[0]) + .encapsulate(multiaddr(request.dstPeer?.addrs[0])) + const localAddr = multiaddr(request.srcPeer?.addrs[0]) const maConn = streamToMaConnection({ stream: virtualConnection, remoteAddr, @@ -162,8 +163,8 @@ export class Circuit implements Transport, Initializable { async dial (ma: Multiaddr, options: AbortOptions = {}): Promise { // Check the multiaddr to see if it contains a relay and a destination peer const addrs = ma.toString().split('/p2p-circuit') - const relayAddr = new Multiaddr(addrs[0]) - const destinationAddr = new Multiaddr(addrs[addrs.length - 1]) + const relayAddr = multiaddr(addrs[0]) + const destinationAddr = multiaddr(addrs[addrs.length - 1]) const relayId = relayAddr.getPeerId() const destinationId = destinationAddr.getPeerId() @@ -198,7 +199,7 @@ export class Circuit implements Transport, Initializable { }, dstPeer: { id: destinationPeer.toBytes(), - addrs: [new Multiaddr(destinationAddr).bytes] + addrs: [multiaddr(destinationAddr).bytes] } } }) diff --git a/src/connection-manager/dialer/index.ts b/src/connection-manager/dialer/index.ts index 9015965589..7a731c945b 100644 --- a/src/connection-manager/dialer/index.ts +++ b/src/connection-manager/dialer/index.ts @@ -3,7 +3,8 @@ import all from 'it-all' import filter from 'it-filter' import { pipe } from 'it-pipe' import errCode from 'err-code' -import { Multiaddr, Resolver } from '@multiformats/multiaddr' +import type { Multiaddr, Resolver } from '@multiformats/multiaddr' +import { multiaddr, resolvers } from '@multiformats/multiaddr' import { TimeoutController } from 'timeout-abort-controller' import { AbortError } from '@libp2p/interfaces/errors' import { anySignal } from 'any-signal' @@ -117,7 +118,7 @@ export class DefaultDialer implements Startable, Dialer { }) for (const [key, value] of Object.entries(init.resolvers ?? {})) { - Multiaddr.resolvers.set(key, value) + resolvers.set(key, value) } } @@ -371,7 +372,7 @@ export class DefaultDialer implements Startable, Dialer { */ async _resolveRecord (ma: Multiaddr, options: AbortOptions): Promise { try { - ma = new Multiaddr(ma.toString()) // Use current multiaddr module + ma = multiaddr(ma.toString()) // Use current multiaddr module const multiaddrs = await ma.resolve(options) return multiaddrs } catch (err) { diff --git a/src/get-peer.ts b/src/get-peer.ts index 8406fda45d..96b5965923 100644 --- a/src/get-peer.ts +++ b/src/get-peer.ts @@ -1,5 +1,6 @@ import { peerIdFromString } from '@libp2p/peer-id' -import { Multiaddr } from '@multiformats/multiaddr' +import type { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr, isMultiaddr } from '@multiformats/multiaddr' import errCode from 'err-code' import { codes } from './errors.js' import { isPeerId } from '@libp2p/interface-peer-id' @@ -39,12 +40,12 @@ export function getPeer (peer: PeerId | Multiaddr | string): PeerInfo { } if (typeof peer === 'string') { - peer = new Multiaddr(peer) + peer = multiaddr(peer) } let addr - if (Multiaddr.isMultiaddr(peer)) { + if (isMultiaddr(peer)) { addr = peer peer = peerIdFromMultiaddr(peer) } diff --git a/src/identify/index.ts b/src/identify/index.ts index a0f051423b..a90776add3 100644 --- a/src/identify/index.ts +++ b/src/identify/index.ts @@ -5,7 +5,7 @@ import { pipe } from 'it-pipe' import drain from 'it-drain' import first from 'it-first' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { Multiaddr, protocols } from '@multiformats/multiaddr' +import { multiaddr, protocols } from '@multiformats/multiaddr' import { Identify } from './pb/message.js' import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record' import { @@ -354,7 +354,7 @@ export class IdentifyService implements Startable { // LEGACY: Update peers data in PeerStore try { - await this.components.getPeerStore().addressBook.set(id, listenAddrs.map((addr) => new Multiaddr(addr))) + await this.components.getPeerStore().addressBook.set(id, listenAddrs.map((addr) => multiaddr(addr))) } catch (err: any) { log.error('received invalid addrs', err) } @@ -506,7 +506,7 @@ export class IdentifyService implements Startable { // LEGACY: Update peers data in PeerStore try { await this.components.getPeerStore().addressBook.set(id, - message.listenAddrs.map((addr) => new Multiaddr(addr))) + message.listenAddrs.map((addr) => multiaddr(addr))) } catch (err: any) { log.error('received invalid addrs', err) } @@ -527,7 +527,7 @@ export class IdentifyService implements Startable { static getCleanMultiaddr (addr: Uint8Array | string | null | undefined) { if (addr != null && addr.length > 0) { try { - return new Multiaddr(addr) + return multiaddr(addr) } catch { } diff --git a/src/nat-manager.ts b/src/nat-manager.ts index 7e9cb0c61b..6da6639ec4 100644 --- a/src/nat-manager.ts +++ b/src/nat-manager.ts @@ -1,6 +1,6 @@ import { upnpNat, NatAPI } from '@achingbrain/nat-port-mapper' import { logger } from '@libp2p/logger' -import { Multiaddr } from '@multiformats/multiaddr' +import { fromNodeAddress } from '@multiformats/multiaddr' import { isBrowser } from 'wherearewe' import isPrivateIp from 'private-ip' import * as pkg from './version.js' @@ -157,7 +157,7 @@ export class NatManager implements Startable { protocol: transport.toUpperCase() === 'TCP' ? 'TCP' : 'UDP' }) - this.components.getAddressManager().addObservedAddr(Multiaddr.fromNodeAddress({ + this.components.getAddressManager().addObservedAddr(fromNodeAddress({ family: 4, address: publicIp, port: publicPort diff --git a/test/addresses/address-manager.spec.ts b/test/addresses/address-manager.spec.ts index 9095e62080..2413f0eb79 100644 --- a/test/addresses/address-manager.spec.ts +++ b/test/addresses/address-manager.spec.ts @@ -1,7 +1,7 @@ /* eslint-env mocha */ import { expect } from 'aegir/chai' -import { Multiaddr, protocols } from '@multiformats/multiaddr' +import { multiaddr, protocols } from '@multiformats/multiaddr' import { AddressFilter, DefaultAddressManager } from '../../src/address-manager/index.js' import { createNode } from '../utils/creators/peer.js' import { createFromJSON } from '@libp2p/peer-id-factory' @@ -48,8 +48,8 @@ describe('Address Manager', () => { const listenMultiaddrs = am.getListenAddrs() expect(listenMultiaddrs.length).to.equal(2) - expect(listenMultiaddrs[0].equals(new Multiaddr(listenAddresses[0]))).to.equal(true) - expect(listenMultiaddrs[1].equals(new Multiaddr(listenAddresses[1]))).to.equal(true) + expect(listenMultiaddrs[0].equals(multiaddr(listenAddresses[0]))).to.equal(true) + expect(listenMultiaddrs[1].equals(multiaddr(listenAddresses[1]))).to.equal(true) }) it('should return announce multiaddrs on get', () => { @@ -67,7 +67,7 @@ describe('Address Manager', () => { const announceMultiaddrs = am.getAnnounceAddrs() expect(announceMultiaddrs.length).to.equal(1) - expect(announceMultiaddrs[0].equals(new Multiaddr(announceAddreses[0]))).to.equal(true) + expect(announceMultiaddrs[0].equals(multiaddr(announceAddreses[0]))).to.equal(true) }) it('should add observed addresses', () => { diff --git a/test/addresses/addresses.node.ts b/test/addresses/addresses.node.ts index 836a0f11fa..37ac534513 100644 --- a/test/addresses/addresses.node.ts +++ b/test/addresses/addresses.node.ts @@ -2,7 +2,7 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' -import { Multiaddr, protocols } from '@multiformats/multiaddr' +import { multiaddr, protocols } from '@multiformats/multiaddr' import { isLoopback } from '@libp2p/utils/multiaddr/is-loopback' import { AddressesOptions } from './utils.js' import { createNode } from '../utils/creators/peer.js' @@ -114,9 +114,9 @@ describe('libp2p.multiaddrs', () => { expect(libp2p.components.getAddressManager().getAddresses()).to.have.lengthOf(0) // Stub transportManager addresses to add a public address - const stubMa = new Multiaddr('/ip4/120.220.10.1/tcp/1000') + const stubMa = multiaddr('/ip4/120.220.10.1/tcp/1000') sinon.stub(libp2p.components.getTransportManager(), 'getAddrs').returns([ - ...listenAddresses.map((a) => new Multiaddr(a)), + ...listenAddresses.map((a) => multiaddr(a)), stubMa ]) @@ -165,7 +165,7 @@ describe('libp2p.multiaddrs', () => { expect(libp2p.components.getAddressManager().getAddresses()).to.have.lengthOf(listenAddresses.length) - libp2p.components.getAddressManager().addObservedAddr(new Multiaddr(ma)) + libp2p.components.getAddressManager().addObservedAddr(multiaddr(ma)) expect(libp2p.components.getAddressManager().getAddresses()).to.have.lengthOf(listenAddresses.length + 1) expect(libp2p.components.getAddressManager().getAddresses().map(ma => ma.decapsulateCode(protocols('p2p').code).toString())).to.include(ma) diff --git a/test/content-routing/content-routing.node.ts b/test/content-routing/content-routing.node.ts index 0a6280a775..acd81badc6 100644 --- a/test/content-routing/content-routing.node.ts +++ b/test/content-routing/content-routing.node.ts @@ -1,13 +1,10 @@ /* eslint-env mocha */ import { expect } from 'aegir/chai' -import nock from 'nock' import sinon from 'sinon' import pDefer from 'p-defer' import { CID } from 'multiformats/cid' -import { create as createIpfsHttpClient } from 'ipfs-http-client' -import { DelegatedContentRouting } from '@libp2p/delegated-content-routing' -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import drain from 'it-drain' import all from 'it-all' import { createNode, createPeerId, populateAddressBooks } from '../utils/creators/peer.js' @@ -16,6 +13,9 @@ import { createRoutingOptions } from './utils.js' import type { Libp2p } from '../../src/index.js' import type { PeerInfo } from '@libp2p/interface-peer-info' import type { Libp2pNode } from '../../src/libp2p.js' +import type { ContentRouting } from '@libp2p/interface-content-routing' +import { StubbedInstance, stubInterface } from 'ts-sinon' +import { peerIdFromString } from '@libp2p/peer-id' describe('content-routing', () => { describe('no routers', () => { @@ -119,14 +119,12 @@ describe('content-routing', () => { describe('via delegate router', () => { let node: Libp2pNode - let delegate: DelegatedContentRouting + let delegate: StubbedInstance beforeEach(async () => { - delegate = new DelegatedContentRouting(createIpfsHttpClient({ - host: '0.0.0.0', - protocol: 'http', - port: 60197 - })) + delegate = stubInterface() + delegate.provide.returns(Promise.resolve()) + delegate.findProviders.returns(async function * () {}()) node = await createNode({ config: createBaseOptions({ @@ -149,7 +147,7 @@ describe('content-routing', () => { it('should use the delegate router to provide', async () => { const deferred = pDefer() - sinon.stub(delegate, 'provide').callsFake(async () => { + delegate.provide.callsFake(async () => { deferred.resolve() }) @@ -161,14 +159,14 @@ describe('content-routing', () => { it('should use the delegate router to find providers', async () => { const deferred = pDefer() - sinon.stub(delegate, 'findProviders').callsFake(async function * () { + delegate.findProviders.returns(async function * () { yield { id: node.peerId, multiaddrs: [], protocols: [] } deferred.resolve() - }) + }()) await drain(node.contentRouting.findProviders(CID.parse('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB'))) @@ -177,94 +175,61 @@ describe('content-routing', () => { it('should be able to register as a provider', async () => { const cid = CID.parse('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB') - const provider = 'QmZNgCqZCvTsi3B4Vt7gsSqpkqDpE7M2Y9TDmEhbDb4ceF' - - const mockBlockApi = nock('http://0.0.0.0:60197') - // mock the block/stat call - .post('/api/v0/block/stat') - .query(true) - .reply(200, '{"Key":"QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB","Size":"2169"}', [ - 'Content-Type', 'application/json', - 'X-Chunked-Output', '1' - ]) - const mockDhtApi = nock('http://0.0.0.0:60197') - // mock the dht/provide call - .post('/api/v0/dht/provide') - .query(true) - .reply(200, `{"Extra":"","ID":"QmWKqWXCtRXEeCQTo3FoZ7g4AfnGiauYYiczvNxFCHicbB","Responses":[{"Addrs":["/ip4/0.0.0.0/tcp/0"],"ID":"${provider}"}],"Type":4}\n`, [ - 'Content-Type', 'application/json', - 'X-Chunked-Output', '1' - ]) await node.contentRouting.provide(cid) - expect(mockBlockApi.isDone()).to.equal(true) - expect(mockDhtApi.isDone()).to.equal(true) + expect(delegate.provide.calledWith(cid)).to.equal(true) }) it('should handle errors when registering as a provider', async () => { const cid = CID.parse('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB') - const mockApi = nock('http://0.0.0.0:60197') - // mock the block/stat call - .post('/api/v0/block/stat') - .query(true) - .reply(502, 'Bad Gateway', ['Content-Type', 'application/json']) + + delegate.provide.withArgs(cid).throws(new Error('Could not provide')) await expect(node.contentRouting.provide(cid)) .to.eventually.be.rejected() - - expect(mockApi.isDone()).to.equal(true) + .with.property('message', 'Could not provide') }) it('should be able to find providers', async () => { const cid = CID.parse('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB') const provider = 'QmZNgCqZCvTsi3B4Vt7gsSqpkqDpE7M2Y9TDmEhbDb4ceF' - const mockApi = nock('http://0.0.0.0:60197') - .post('/api/v0/dht/findprovs') - .query(true) - .reply(200, `{"Extra":"","ID":"QmWKqWXCtRXEeCQTo3FoZ7g4AfnGiauYYiczvNxFCHicbB","Responses":[{"Addrs":["/ip4/0.0.0.0/tcp/0"],"ID":"${provider}"}],"Type":4}\n`, [ - 'Content-Type', 'application/json', - 'X-Chunked-Output', '1' - ]) + delegate.findProviders.withArgs(cid).returns(async function * () { + yield { + id: peerIdFromString(provider), + multiaddrs: [ + multiaddr('/ip4/0.0.0.0/tcp/0') + ], + protocols: [] + } + }()) const providers = await all(node.contentRouting.findProviders(cid)) expect(providers).to.have.length(1) expect(providers[0].id.toString()).to.equal(provider) - expect(mockApi.isDone()).to.equal(true) }) it('should handle errors when finding providers', async () => { const cid = CID.parse('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB') - const mockApi = nock('http://0.0.0.0:60197') - .post('/api/v0/dht/findprovs') - .query(true) - .reply(502, 'Bad Gateway', [ - 'X-Chunked-Output', '1' - ]) - try { - for await (const _ of node.contentRouting.findProviders(cid)) { } // eslint-disable-line - throw new Error('should handle errors when finding providers') - } catch (err: any) { - expect(err).to.exist() - } + delegate.findProviders.withArgs(cid).throws(new Error('Could not find providers')) - expect(mockApi.isDone()).to.equal(true) + await expect(drain(node.contentRouting.findProviders(cid))) + .to.eventually.be.rejected() + .with.property('message', 'Could not find providers') }) }) describe('via dht and delegate routers', () => { let node: Libp2pNode - let delegate: DelegatedContentRouting + let delegate: StubbedInstance beforeEach(async () => { - delegate = new DelegatedContentRouting(createIpfsHttpClient({ - host: '0.0.0.0', - protocol: 'http', - port: 60197 - })) + delegate = stubInterface() + delegate.provide.returns(Promise.resolve()) + delegate.findProviders.returns(async function * () {}()) node = await createNode({ config: createRoutingOptions({ @@ -284,7 +249,7 @@ describe('content-routing', () => { const result: PeerInfo = { id: providerPeerId, multiaddrs: [ - new Multiaddr('/ip4/123.123.123.123/tcp/49320') + multiaddr('/ip4/123.123.123.123/tcp/49320') ], protocols: [] } @@ -294,7 +259,7 @@ describe('content-routing', () => { } sinon.stub(node.dht, 'findProviders').callsFake(async function * () {}) - sinon.stub(delegate, 'findProviders').callsFake(async function * () { + delegate.findProviders.callsFake(async function * () { yield result }) @@ -313,7 +278,7 @@ describe('content-routing', () => { const result = { id: providerPeerId, multiaddrs: [ - new Multiaddr('/ip4/123.123.123.123/tcp/49320') + multiaddr('/ip4/123.123.123.123/tcp/49320') ], protocols: [] } @@ -327,7 +292,7 @@ describe('content-routing', () => { sinon.stub(node.dht, 'findProviders').callsFake(async function * () { // eslint-disable-line require-yield await defer.promise }) - sinon.stub(delegate, 'findProviders').callsFake(async function * () { + delegate.findProviders.callsFake(async function * () { yield result await defer.promise @@ -344,7 +309,7 @@ describe('content-routing', () => { const result = { id: providerPeerId, multiaddrs: [ - new Multiaddr('/ip4/123.123.123.123/tcp/49320') + multiaddr('/ip4/123.123.123.123/tcp/49320') ], protocols: [] } @@ -363,7 +328,7 @@ describe('content-routing', () => { ] } }) - sinon.stub(delegate, 'findProviders').callsFake(async function * () { + delegate.findProviders.callsFake(async function * () { yield result }) @@ -377,14 +342,14 @@ describe('content-routing', () => { const result1 = { id: providerPeerId, multiaddrs: [ - new Multiaddr('/ip4/123.123.123.123/tcp/49320') + multiaddr('/ip4/123.123.123.123/tcp/49320') ], protocols: [] } const result2 = { id: providerPeerId, multiaddrs: [ - new Multiaddr('/ip4/213.213.213.213/tcp/2344') + multiaddr('/ip4/213.213.213.213/tcp/2344') ], protocols: [] } @@ -403,7 +368,7 @@ describe('content-routing', () => { ] } }) - sinon.stub(delegate, 'findProviders').callsFake(async function * () { + delegate.findProviders.callsFake(async function * () { yield result2 }) @@ -430,7 +395,7 @@ describe('content-routing', () => { dhtDeferred.resolve() }) - sinon.stub(delegate, 'provide').callsFake(async function () { + delegate.provide.callsFake(async function () { delegatedDeferred.resolve() }) @@ -465,7 +430,7 @@ describe('content-routing', () => { } }) - sinon.stub(delegate, 'findProviders').callsFake(async function * () { // eslint-disable-line require-yield + delegate.findProviders.callsFake(async function * () { // eslint-disable-line require-yield }) const providers = [] @@ -491,7 +456,7 @@ describe('content-routing', () => { sinon.stub(node.dht, 'findProviders').callsFake(async function * () {}) - sinon.stub(delegate, 'findProviders').callsFake(async function * () { + delegate.findProviders.callsFake(async function * () { yield results[0] }) diff --git a/test/content-routing/dht/operation.node.ts b/test/content-routing/dht/operation.node.ts index 82b03e6784..4711e1531f 100644 --- a/test/content-routing/dht/operation.node.ts +++ b/test/content-routing/dht/operation.node.ts @@ -1,7 +1,8 @@ /* eslint-env mocha */ import { expect } from 'aegir/chai' -import { Multiaddr } from '@multiformats/multiaddr' +import type { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import pWaitFor from 'p-wait-for' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { subsystemMulticodecs, createSubsystemOptions } from './utils.js' @@ -10,8 +11,8 @@ import type { PeerId } from '@libp2p/interface-peer-id' import { createLibp2pNode, Libp2pNode } from '../../../src/libp2p.js' import { start } from '@libp2p/interfaces/startable' -const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/8000') -const remoteListenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/8001') +const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/8000') +const remoteListenAddr = multiaddr('/ip4/127.0.0.1/tcp/8001') async function getRemoteAddr (remotePeerId: PeerId, libp2p: Libp2pNode) { const addrs = await libp2p.components.getPeerStore().addressBook.get(remotePeerId) diff --git a/test/core/consume-peer-record.spec.ts b/test/core/consume-peer-record.spec.ts index 0f7a126ce0..0f5770d43b 100644 --- a/test/core/consume-peer-record.spec.ts +++ b/test/core/consume-peer-record.spec.ts @@ -3,7 +3,7 @@ import { WebSockets } from '@libp2p/websockets' import { Plaintext } from '../../src/insecure/index.js' import { createPeerId } from '../utils/creators/peer.js' -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' import type { Libp2pOptions } from '../../src/index.js' @@ -42,7 +42,7 @@ describe('Consume peer record', () => { await libp2p.start() - libp2p.components.getAddressManager().addObservedAddr(new Multiaddr('/ip4/123.123.123.123/tcp/3983')) + libp2p.components.getAddressManager().addObservedAddr(multiaddr('/ip4/123.123.123.123/tcp/3983')) await p diff --git a/test/dialing/dial-request.spec.ts b/test/dialing/dial-request.spec.ts index cf9840fe8f..10763f513c 100644 --- a/test/dialing/dial-request.spec.ts +++ b/test/dialing/dial-request.spec.ts @@ -8,7 +8,7 @@ import delay from 'delay' import { DialAction, DialRequest } from '../../src/connection-manager/dialer/dial-request.js' import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-mocks' import { createEd25519PeerId } from '@libp2p/peer-id-factory' -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { DefaultDialer } from '../../src/connection-manager/dialer/index.js' import { Components } from '@libp2p/components' const error = new Error('dial failure') @@ -29,7 +29,7 @@ describe('Dial Request', () => { }) const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') const dialRequest = new DialRequest({ - addrs: Object.keys(actions).map(str => new Multiaddr(str)), + addrs: Object.keys(actions).map(str => multiaddr(str)), dialer, dialAction }) @@ -59,7 +59,7 @@ describe('Dial Request', () => { }) const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') const dialRequest = new DialRequest({ - addrs: Object.keys(actions).map(str => new Multiaddr(str)), + addrs: Object.keys(actions).map(str => multiaddr(str)), dialer, dialAction }) @@ -105,7 +105,7 @@ describe('Dial Request', () => { const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') const dialerGetTokensSpy = sinon.spy(dialer, 'getTokens') const dialRequest = new DialRequest({ - addrs: Object.keys(actions).map(str => new Multiaddr(str)), + addrs: Object.keys(actions).map(str => multiaddr(str)), dialer, dialAction }) @@ -144,7 +144,7 @@ describe('Dial Request', () => { }) const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') const dialRequest = new DialRequest({ - addrs: Object.keys(actions).map(str => new Multiaddr(str)), + addrs: Object.keys(actions).map(str => multiaddr(str)), dialer, dialAction }) @@ -191,7 +191,7 @@ describe('Dial Request', () => { const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') const dialerGetTokensSpy = sinon.spy(dialer, 'getTokens') const dialRequest = new DialRequest({ - addrs: Object.keys(actions).map(str => new Multiaddr(str)), + addrs: Object.keys(actions).map(str => multiaddr(str)), dialer, dialAction }) @@ -237,7 +237,7 @@ describe('Dial Request', () => { const signals: Record = {} const dialRequest = new DialRequest({ - addrs: Object.keys(actions).map(str => new Multiaddr(str)), + addrs: Object.keys(actions).map(str => multiaddr(str)), dialer: new DefaultDialer(new Components(), { maxParallelDials: 3 }), diff --git a/test/dialing/direct.node.ts b/test/dialing/direct.node.ts index 146701f873..be0e2e0660 100644 --- a/test/dialing/direct.node.ts +++ b/test/dialing/direct.node.ts @@ -5,7 +5,8 @@ import sinon from 'sinon' import { TCP } from '@libp2p/tcp' import { Mplex } from '@libp2p/mplex' import { Plaintext } from '../../src/insecure/index.js' -import { Multiaddr } from '@multiformats/multiaddr' +import type { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import delay from 'delay' import pDefer from 'p-defer' @@ -33,8 +34,8 @@ import swarmKey from '../fixtures/swarm.key.js' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' const swarmKeyBuffer = uint8ArrayFromString(swarmKey) -const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') -const unsupportedAddr = new Multiaddr('/ip4/127.0.0.1/tcp/9999/ws/p2p/QmckxVrJw1Yo8LqvmDJNUmdAsKtSbiKWmrXJFyKmUraBoN') +const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0') +const unsupportedAddr = multiaddr('/ip4/127.0.0.1/tcp/9999/ws/p2p/QmckxVrJw1Yo8LqvmDJNUmdAsKtSbiKWmrXJFyKmUraBoN') describe('Dialing (direct, TCP)', () => { let remoteTM: DefaultTransportManager @@ -176,9 +177,9 @@ describe('Dialing (direct, TCP)', () => { it('should dial to the max concurrency', async () => { const addrs = [ - new Multiaddr('/ip4/0.0.0.0/tcp/8000'), - new Multiaddr('/ip4/0.0.0.0/tcp/8001'), - new Multiaddr('/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 peerId = await createFromJSON(Peers[1]) diff --git a/test/dialing/direct.spec.ts b/test/dialing/direct.spec.ts index d2f0a82649..cf5bc14fae 100644 --- a/test/dialing/direct.spec.ts +++ b/test/dialing/direct.spec.ts @@ -8,7 +8,8 @@ import { WebSockets } from '@libp2p/websockets' import * as filters from '@libp2p/websockets/filters' import { Mplex } from '@libp2p/mplex' import { Plaintext } from '../../src/insecure/index.js' -import { Multiaddr } from '@multiformats/multiaddr' +import type { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { AbortError } from '@libp2p/interfaces/errors' import { MemoryDatastore } from 'datastore-core/memory' import { codes as ErrorCodes } from '../../src/errors.js' @@ -31,7 +32,7 @@ import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' import type { PeerId } from '@libp2p/interface-peer-id' import { pEvent } from 'p-event' -const unsupportedAddr = new Multiaddr('/ip4/127.0.0.1/tcp/9999') +const unsupportedAddr = multiaddr('/ip4/127.0.0.1/tcp/9999') describe('Dialing (direct, WebSockets)', () => { let localTM: TransportManager @@ -174,7 +175,7 @@ describe('Dialing (direct, WebSockets)', () => { }) const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') - await localComponents.getPeerStore().addressBook.set(remotePeerId, Array.from({ length: 11 }, (_, i) => new Multiaddr(`/ip4/127.0.0.1/tcp/1500${i}/ws/p2p/12D3KooWHFKTMzwerBtsVmtz4ZZEQy2heafxzWw6wNn5PPYkBxJ5`))) + await localComponents.getPeerStore().addressBook.set(remotePeerId, Array.from({ length: 11 }, (_, i) => multiaddr(`/ip4/127.0.0.1/tcp/1500${i}/ws/p2p/12D3KooWHFKTMzwerBtsVmtz4ZZEQy2heafxzWw6wNn5PPYkBxJ5`))) await expect(dialer.dial(remoteAddr)) .to.eventually.be.rejected() @@ -183,9 +184,9 @@ describe('Dialing (direct, WebSockets)', () => { it('should sort addresses on dial', async () => { const peerMultiaddrs = [ - new Multiaddr('/ip4/127.0.0.1/tcp/15001/ws'), - new Multiaddr('/ip4/20.0.0.1/tcp/15001/ws'), - new Multiaddr('/ip4/30.0.0.1/tcp/15001/ws') + multiaddr('/ip4/127.0.0.1/tcp/15001/ws'), + multiaddr('/ip4/20.0.0.1/tcp/15001/ws'), + multiaddr('/ip4/30.0.0.1/tcp/15001/ws') ] const publicAddressesFirstSpy = sinon.spy(publicAddressesFirst) @@ -213,9 +214,9 @@ describe('Dialing (direct, WebSockets)', () => { it('should dial to the max concurrency', async () => { const addrs = [ - new Multiaddr('/ip4/0.0.0.0/tcp/8000/ws'), - new Multiaddr('/ip4/0.0.0.0/tcp/8001/ws'), - new Multiaddr('/ip4/0.0.0.0/tcp/8002/ws') + multiaddr('/ip4/0.0.0.0/tcp/8000/ws'), + multiaddr('/ip4/0.0.0.0/tcp/8001/ws'), + multiaddr('/ip4/0.0.0.0/tcp/8002/ws') ] const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') @@ -253,9 +254,9 @@ describe('Dialing (direct, WebSockets)', () => { it('.destroy should abort pending dials', async () => { const addrs = [ - new Multiaddr('/ip4/0.0.0.0/tcp/8000/ws'), - new Multiaddr('/ip4/0.0.0.0/tcp/8001/ws'), - new Multiaddr('/ip4/0.0.0.0/tcp/8002/ws') + multiaddr('/ip4/0.0.0.0/tcp/8000/ws'), + multiaddr('/ip4/0.0.0.0/tcp/8001/ws'), + multiaddr('/ip4/0.0.0.0/tcp/8002/ws') ] const dialer = new DefaultDialer(localComponents, { maxParallelDials: 2 @@ -592,7 +593,7 @@ describe('libp2p.dialer (direct, WebSockets)', () => { await libp2p.start() - await expect(libp2p.dial(new Multiaddr(`/ip4/127.0.0.1/tcp/1234/ws/p2p/${peerId.toString()}`))) + await expect(libp2p.dial(multiaddr(`/ip4/127.0.0.1/tcp/1234/ws/p2p/${peerId.toString()}`))) .to.eventually.be.rejected() .and.to.have.property('code', ErrorCodes.ERR_DIALED_SELF) }) diff --git a/test/dialing/resolver.spec.ts b/test/dialing/resolver.spec.ts index ded6e52df1..8aa12b342e 100644 --- a/test/dialing/resolver.spec.ts +++ b/test/dialing/resolver.spec.ts @@ -2,7 +2,8 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' -import { Multiaddr } from '@multiformats/multiaddr' +import type { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { codes as ErrorCodes } from '../../src/errors.js' import { createNode } from '../utils/creators/peer.js' import { createBaseOptions } from '../utils/base-options.browser.js' @@ -90,8 +91,8 @@ describe('Dialing (resolvable addresses)', () => { it('resolves dnsaddr to ws local address', async () => { const remoteId = remoteLibp2p.peerId - const dialAddr = new Multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId.toString()}`) - const relayedAddrFetched = new Multiaddr(relayedAddr(remoteId)) + const dialAddr = multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId.toString()}`) + const relayedAddrFetched = multiaddr(relayedAddr(remoteId)) // Transport spy const transport = getTransport(libp2p, Circuit.prototype[Symbol.toStringTag]) @@ -111,8 +112,8 @@ describe('Dialing (resolvable addresses)', () => { it('resolves a dnsaddr recursively', async () => { const remoteId = remoteLibp2p.peerId - const dialAddr = new Multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId.toString()}`) - const relayedAddrFetched = new Multiaddr(relayedAddr(remoteId)) + const dialAddr = multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId.toString()}`) + const relayedAddrFetched = multiaddr(relayedAddr(remoteId)) // Transport spy const transport = getTransport(libp2p, Circuit.prototype[Symbol.toStringTag]) @@ -142,10 +143,10 @@ describe('Dialing (resolvable addresses)', () => { // Resolver just returns the received multiaddrs it('stops recursive resolve if finds dns4/dns6 and dials it', async () => { const remoteId = remoteLibp2p.peerId - const dialAddr = new Multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId.toString()}`) + const dialAddr = multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId.toString()}`) // Stub resolver - const dnsMa = new Multiaddr(`/dns4/ams-1.remote.libp2p.io/tcp/443/wss/p2p/${remoteId.toString()}`) + const dnsMa = multiaddr(`/dns4/ams-1.remote.libp2p.io/tcp/443/wss/p2p/${remoteId.toString()}`) resolver.returns(Promise.resolve([ `${dnsMa.toString()}` ])) @@ -170,8 +171,8 @@ describe('Dialing (resolvable addresses)', () => { it('resolves a dnsaddr recursively not failing if one address fails to resolve', async () => { const remoteId = remoteLibp2p.peerId - const dialAddr = new Multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId.toString()}`) - const relayedAddrFetched = new Multiaddr(relayedAddr(remoteId)) + const dialAddr = multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId.toString()}`) + const relayedAddrFetched = multiaddr(relayedAddr(remoteId)) // Transport spy const transport = getTransport(libp2p, Circuit.prototype[Symbol.toStringTag]) @@ -193,7 +194,7 @@ describe('Dialing (resolvable addresses)', () => { it('fails to dial if resolve fails and there are no addresses to dial', async () => { const remoteId = remoteLibp2p.peerId - const dialAddr = new Multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId.toString()}`) + const dialAddr = multiaddr(`/dnsaddr/remote.libp2p.io/p2p/${remoteId.toString()}`) // Stub resolver resolver.returns(Promise.reject(new Error())) diff --git a/test/fixtures/browser.ts b/test/fixtures/browser.ts index 6e0a56529b..37c704f8c0 100644 --- a/test/fixtures/browser.ts +++ b/test/fixtures/browser.ts @@ -1,6 +1,6 @@ -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' export const MULTIADDRS_WEBSOCKETS = [ - new Multiaddr('/ip4/127.0.0.1/tcp/15001/ws/p2p/12D3KooWHFKTMzwerBtsVmtz4ZZEQy2heafxzWw6wNn5PPYkBxJ5') + multiaddr('/ip4/127.0.0.1/tcp/15001/ws/p2p/12D3KooWHFKTMzwerBtsVmtz4ZZEQy2heafxzWw6wNn5PPYkBxJ5') ] diff --git a/test/identify/index.spec.ts b/test/identify/index.spec.ts index b9aefdffa6..d28b036c9a 100644 --- a/test/identify/index.spec.ts +++ b/test/identify/index.spec.ts @@ -3,7 +3,7 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { codes } from '../../src/errors.js' import { IdentifyService, IdentifyServiceInit, Message } from '../../src/identify/index.js' @@ -30,7 +30,7 @@ import { TimeoutController } from 'timeout-abort-controller' import { CustomEvent } from '@libp2p/interfaces/events' import pDefer from 'p-defer' -const listenMaddrs = [new Multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] +const listenMaddrs = [multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] const defaultInit: IdentifyServiceInit = { protocolPrefix: 'ipfs', diff --git a/test/identify/push.spec.ts b/test/identify/push.spec.ts index 312a07d08a..f48d16e2b7 100644 --- a/test/identify/push.spec.ts +++ b/test/identify/push.spec.ts @@ -2,7 +2,7 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { IdentifyService, IdentifyServiceInit } from '../../src/identify/index.js' import Peers from '../fixtures/peers.js' import { PersistentPeerStore } from '@libp2p/peer-store' @@ -25,7 +25,7 @@ import delay from 'delay' import { pEvent } from 'p-event' import { start, stop } from '@libp2p/interfaces/startable' -const listenMaddrs = [new Multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] +const listenMaddrs = [multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] const defaultInit: IdentifyServiceInit = { protocolPrefix: 'ipfs', @@ -119,7 +119,7 @@ describe('identify (push)', () => { await remoteIdentify.identify(remoteToLocal) const updatedProtocol = '/special-new-protocol/1.0.0' - const updatedAddress = new Multiaddr('/ip4/127.0.0.1/tcp/48322') + const updatedAddress = multiaddr('/ip4/127.0.0.1/tcp/48322') // should have protocols but not our new one const identifiedProtocols = await remoteComponents.getPeerStore().protoBook.get(localComponents.getPeerId()) @@ -250,7 +250,7 @@ describe('identify (push)', () => { await remoteIdentify.identify(remoteToLocal) const updatedProtocol = '/special-new-protocol/1.0.0' - const updatedAddress = new Multiaddr('/ip4/127.0.0.1/tcp/48322') + const updatedAddress = multiaddr('/ip4/127.0.0.1/tcp/48322') // should have protocols but not our new one const identifiedProtocols = await remoteComponents.getPeerStore().protoBook.get(localComponents.getPeerId()) diff --git a/test/identify/service.spec.ts b/test/identify/service.spec.ts index f63541b070..245956cfe5 100644 --- a/test/identify/service.spec.ts +++ b/test/identify/service.spec.ts @@ -2,7 +2,7 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import Peers from '../fixtures/peers.js' import { createLibp2pNode } from '../../src/libp2p.js' @@ -228,7 +228,7 @@ describe('libp2p.dialer.identifyService', () => { await identityServiceIdentifySpy.firstCall.returnValue sinon.stub(libp2p, 'isStarted').returns(true) - await libp2p.peerStore.addressBook.add(libp2p.peerId, [new Multiaddr('/ip4/180.0.0.1/tcp/15001/ws')]) + await libp2p.peerStore.addressBook.add(libp2p.peerId, [multiaddr('/ip4/180.0.0.1/tcp/15001/ws')]) // the protocol change event listener in the identity service is async await pWaitFor(() => identityServicePushSpy.callCount === 1) diff --git a/test/insecure/plaintext.spec.ts b/test/insecure/plaintext.spec.ts index 7e3a9a7edf..6ce70547c3 100644 --- a/test/insecure/plaintext.spec.ts +++ b/test/insecure/plaintext.spec.ts @@ -12,7 +12,7 @@ import type { PeerId } from '@libp2p/interface-peer-id' import { createFromJSON, createRSAPeerId } from '@libp2p/peer-id-factory' import type { ConnectionEncrypter } from '@libp2p/interface-connection-encrypter' import { mockMultiaddrConnPair } from '@libp2p/interface-mocks' -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { peerIdFromBytes } from '@libp2p/peer-id' describe('plaintext', () => { @@ -39,8 +39,8 @@ describe('plaintext', () => { const { inbound, outbound } = mockMultiaddrConnPair({ remotePeer, addrs: [ - new Multiaddr('/ip4/127.0.0.1/tcp/1234'), - new Multiaddr('/ip4/127.0.0.1/tcp/1235') + multiaddr('/ip4/127.0.0.1/tcp/1234'), + multiaddr('/ip4/127.0.0.1/tcp/1235') ] }) @@ -60,8 +60,8 @@ describe('plaintext', () => { const { inbound, outbound } = mockMultiaddrConnPair({ remotePeer, addrs: [ - new Multiaddr('/ip4/127.0.0.1/tcp/1234'), - new Multiaddr('/ip4/127.0.0.1/tcp/1235') + multiaddr('/ip4/127.0.0.1/tcp/1234'), + multiaddr('/ip4/127.0.0.1/tcp/1235') ] }) diff --git a/test/interop.ts b/test/interop.ts index 9768e73e63..3d4b9f3994 100644 --- a/test/interop.ts +++ b/test/interop.ts @@ -5,7 +5,7 @@ import { createClient } from '@libp2p/daemon-client' import { createLibp2p, Libp2pOptions } from '../src/index.js' import { Noise } from '@chainsafe/libp2p-noise' import { TCP } from '@libp2p/tcp' -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { KadDHT } from '@libp2p/kad-dht' import { path as p2pd } from 'go-libp2p' import { execa } from 'execa' @@ -23,7 +23,7 @@ import { FloodSub } from '@libp2p/floodsub' async function createGoPeer (options: SpawnOptions): Promise { const controlPort = Math.floor(Math.random() * (50000 - 10000 + 1)) + 10000 - const apiAddr = new Multiaddr(`/ip4/0.0.0.0/tcp/${controlPort}`) + const apiAddr = multiaddr(`/ip4/0.0.0.0/tcp/${controlPort}`) const log = logger(`go-libp2p:${controlPort}`) @@ -133,7 +133,7 @@ async function createJsPeer (options: SpawnOptions): Promise { } const node = await createLibp2p(opts) - const server = await createServer(new Multiaddr('/ip4/0.0.0.0/tcp/0'), node) + const server = await createServer(multiaddr('/ip4/0.0.0.0/tcp/0'), node) await server.start() return { diff --git a/test/peer-discovery/index.node.ts b/test/peer-discovery/index.node.ts index 9b8591b0d8..72729cdd42 100644 --- a/test/peer-discovery/index.node.ts +++ b/test/peer-discovery/index.node.ts @@ -7,7 +7,7 @@ import { Bootstrap } from '@libp2p/bootstrap' import { randomBytes } from '@libp2p/crypto' import { KadDHT } from '@libp2p/kad-dht' import { MulticastDNS } from '@libp2p/mdns' -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { createBaseOptions } from '../utils/base-options.js' import { createPeerId } from '../utils/creators/peer.js' @@ -16,7 +16,7 @@ import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' import { CustomEvent } from '@libp2p/interfaces/events' import type { PeerInfo } from '@libp2p/interface-peer-info' -const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') +const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0') describe('peer discovery scenarios', () => { let peerId: PeerId, remotePeerId1: PeerId, remotePeerId2: PeerId diff --git a/test/peer-routing/peer-routing.node.ts b/test/peer-routing/peer-routing.node.ts index f924d86b2e..204dba11eb 100644 --- a/test/peer-routing/peer-routing.node.ts +++ b/test/peer-routing/peer-routing.node.ts @@ -1,17 +1,13 @@ /* eslint-env mocha */ import { expect } from 'aegir/chai' -import nock from 'nock' import sinon from 'sinon' -import intoStream from 'into-stream' import delay from 'delay' import pDefer from 'p-defer' import pWaitFor from 'p-wait-for' import drain from 'it-drain' import all from 'it-all' -import { create as createIpfsHttpClient } from 'ipfs-http-client' -import { DelegatedPeerRouting } from '@libp2p/delegated-peer-routing' -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { createNode, createPeerId, populateAddressBooks } from '../utils/creators/peer.js' import type { Libp2pNode } from '../../src/libp2p.js' import { createBaseOptions } from '../utils/base-options.js' @@ -19,9 +15,10 @@ import { createRoutingOptions } from './utils.js' import type { PeerId } from '@libp2p/interface-peer-id' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { EventTypes, MessageType } from '@libp2p/interface-dht' -import { peerIdFromString } from '@libp2p/peer-id' import type { PeerInfo } from '@libp2p/interface-peer-info' import { KadDHT } from '@libp2p/kad-dht' +import type { PeerRouting } from '@libp2p/interface-peer-routing' +import { StubbedInstance, stubInterface } from 'ts-sinon' describe('peer-routing', () => { let peerId: PeerId @@ -237,14 +234,12 @@ describe('peer-routing', () => { describe('via delegate router', () => { let node: Libp2pNode - let delegate: DelegatedPeerRouting + let delegate: StubbedInstance beforeEach(async () => { - delegate = new DelegatedPeerRouting(createIpfsHttpClient({ - host: '0.0.0.0', - protocol: 'http', - port: 60197 - })) + delegate = stubInterface() + delegate.findPeer.returns(Promise.reject(new Error('Could not find peer'))) + delegate.getClosestPeers.returns(async function * () {}()) node = await createNode({ config: createBaseOptions({ @@ -254,7 +249,6 @@ describe('peer-routing', () => { }) afterEach(() => { - nock.cleanAll() sinon.restore() }) @@ -268,7 +262,7 @@ describe('peer-routing', () => { it('should use the delegate router to find peers', async () => { const remotePeerId = await createPeerId() - const delegateFindPeerStub = sinon.stub(delegate, 'findPeer').callsFake(async function () { + delegate.findPeer.callsFake(async function () { return { id: remotePeerId, multiaddrs: [], @@ -276,16 +270,15 @@ describe('peer-routing', () => { } }) - expect(delegateFindPeerStub.called).to.be.false() + expect(delegate.findPeer.called).to.be.false() await node.peerRouting.findPeer(remotePeerId) - expect(delegateFindPeerStub.called).to.be.true() - delegateFindPeerStub.restore() + expect(delegate.findPeer.called).to.be.true() }) it('should use the delegate router to get the closest peers', async () => { const remotePeerId = await createPeerId() - const delegateGetClosestPeersStub = sinon.stub(delegate, 'getClosestPeers').callsFake(async function * () { + delegate.getClosestPeers.callsFake(async function * () { yield { id: remotePeerId, multiaddrs: [], @@ -293,26 +286,9 @@ describe('peer-routing', () => { } }) - expect(delegateGetClosestPeersStub.called).to.be.false() + expect(delegate.getClosestPeers.called).to.be.false() await drain(node.peerRouting.getClosestPeers(remotePeerId.toBytes())) - expect(delegateGetClosestPeersStub.called).to.be.true() - delegateGetClosestPeersStub.restore() - }) - - it('should be able to find a peer', async () => { - const peerKey = peerIdFromString('QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL') - const mockApi = nock('http://0.0.0.0:60197') - .post('/api/v0/dht/findpeer') - .query(true) - .reply(200, `{"Extra":"","ID":"some other id","Responses":null,"Type":0}\n{"Extra":"","ID":"","Responses":[{"Addrs":["/ip4/127.0.0.1/tcp/4001"],"ID":"${peerKey.toString()}"}],"Type":2}\n`, [ - 'Content-Type', 'application/json', - 'X-Chunked-Output', '1' - ]) - - const peer = await node.peerRouting.findPeer(peerKey) - - expect(peer.id.toString()).to.equal(peerKey.toString()) - expect(mockApi.isDone()).to.equal(true) + expect(delegate.getClosestPeers.called).to.be.true() }) it('should error when peer tries to find itself', async () => { @@ -321,91 +297,27 @@ describe('peer-routing', () => { .and.to.have.property('code', 'ERR_FIND_SELF') }) - it('should error when a peer cannot be found', async () => { - const peerId = await createEd25519PeerId() - const mockApi = nock('http://0.0.0.0:60197') - .post('/api/v0/dht/findpeer') - .query(true) - .reply(200, '{"Extra":"","ID":"some other id","Responses":null,"Type":6}\n{"Extra":"","ID":"yet another id","Responses":null,"Type":0}\n{"Extra":"routing:not found","ID":"","Responses":null,"Type":3}\n', [ - 'Content-Type', 'application/json', - 'X-Chunked-Output', '1' - ]) - - await expect(node.peerRouting.findPeer(peerId)) - .to.eventually.be.rejected() - - expect(mockApi.isDone()).to.equal(true) - }) - - it('should handle errors from the api', async () => { - const peerId = await createEd25519PeerId() - const mockApi = nock('http://0.0.0.0:60197') - .post('/api/v0/dht/findpeer') - .query(true) - .reply(502) - - await expect(node.peerRouting.findPeer(peerId)) - .to.eventually.be.rejected() - - expect(mockApi.isDone()).to.equal(true) - }) - - it('should be able to get the closest peers', async () => { - const peerId = await createEd25519PeerId() - const closest1 = '12D3KooWLewYMMdGWAtuX852n4rgCWkK7EBn4CWbwwBzhsVoKxk3' - const closest2 = '12D3KooWDtoQbpKhtnWddfj72QmpFvvLDTsBLTFkjvgQm6cde2AK' - - const mockApi = nock('http://0.0.0.0:60197') - .post('/api/v0/dht/query') - .query(true) - .reply(200, - () => intoStream([ - `{"Extra":"","ID":"${closest1}","Responses":[{"ID":"${closest1}","Addrs":["/ip4/127.0.0.1/tcp/63930","/ip4/127.0.0.1/tcp/63930"]}],"Type":1}\n`, - `{"Extra":"","ID":"${closest2}","Responses":[{"ID":"${closest2}","Addrs":["/ip4/127.0.0.1/tcp/63506","/ip4/127.0.0.1/tcp/63506"]}],"Type":1}\n`, - `{"Extra":"","ID":"${closest2}","Responses":[],"Type":2}\n`, - `{"Extra":"","ID":"${closest1}","Responses":[],"Type":2}\n` - ]), - [ - 'Content-Type', 'application/json', - 'X-Chunked-Output', '1' - ]) - - const closestPeers = await all(node.peerRouting.getClosestPeers(peerId.toBytes())) - - expect(closestPeers).to.have.length(2) - expect(closestPeers[0].id.toString()).to.equal(closest1) - expect(closestPeers[0].multiaddrs).to.have.lengthOf(2) - expect(closestPeers[1].id.toString()).to.equal(closest2) - expect(closestPeers[1].multiaddrs).to.have.lengthOf(2) - expect(mockApi.isDone()).to.equal(true) - }) - - it('should handle errors when getting the closest peers', async () => { - const peerId = await createEd25519PeerId() - - const mockApi = nock('http://0.0.0.0:60197') - .post('/api/v0/dht/query') - .query(true) - .reply(502, 'Bad Gateway', [ - 'X-Chunked-Output', '1' - ]) + it('should handle errors from the delegate when finding closest peers', async () => { + const remotePeerId = await createPeerId() - await expect(drain(node.peerRouting.getClosestPeers(peerId.toBytes()))).to.eventually.be.rejected() + delegate.getClosestPeers.callsFake(async function * () { // eslint-disable-line require-yield + throw new Error('Could not find closer peers') + }) - expect(mockApi.isDone()).to.equal(true) + expect(delegate.getClosestPeers.called).to.be.false() + await expect(drain(node.peerRouting.getClosestPeers(remotePeerId.toBytes()))) + .to.eventually.be.rejectedWith('Could not find closer peers') }) }) describe('via dht and delegate routers', () => { let node: Libp2pNode - let delegate: DelegatedPeerRouting + let delegate: StubbedInstance beforeEach(async () => { - delegate = new DelegatedPeerRouting(createIpfsHttpClient({ - host: '0.0.0.0', - protocol: 'http', - port: 60197 - })) + delegate = stubInterface() + delegate.findPeer.throws(new Error('Could not find peer')) + delegate.getClosestPeers.returns(async function * () {}()) node = await createNode({ config: createRoutingOptions({ @@ -434,7 +346,8 @@ describe('peer-routing', () => { } sinon.stub(node.dht, 'findPeer').callsFake(async function * () {}) - sinon.stub(delegate, 'findPeer').callsFake(async () => { + delegate.findPeer.reset() + delegate.findPeer.callsFake(async () => { return results }) @@ -466,7 +379,8 @@ describe('peer-routing', () => { } await defer.promise }) - sinon.stub(delegate, 'findPeer').callsFake(async () => { + delegate.findPeer.reset() + delegate.findPeer.callsFake(async () => { return results }) @@ -498,7 +412,8 @@ describe('peer-routing', () => { peer: result } }) - sinon.stub(delegate, 'findPeer').callsFake(async () => { + delegate.findPeer.reset() + delegate.findPeer.callsFake(async () => { return await defer.promise }) @@ -513,7 +428,7 @@ describe('peer-routing', () => { const result = { id: remotePeerId, multiaddrs: [ - new Multiaddr('/ip4/123.123.123.123/tcp/38982') + multiaddr('/ip4/123.123.123.123/tcp/38982') ], protocols: [] } @@ -532,7 +447,8 @@ describe('peer-routing', () => { peer: result } }) - sinon.stub(delegate, 'findPeer').callsFake(async () => { + delegate.findPeer.reset() + delegate.findPeer.callsFake(async () => { const deferred = pDefer() return await deferred.promise @@ -557,7 +473,7 @@ describe('peer-routing', () => { sinon.stub(node.dht, 'getClosestPeers').callsFake(async function * () { }) - sinon.stub(delegate, 'getClosestPeers').callsFake(async function * () { + delegate.getClosestPeers.callsFake(async function * () { yield results[0] }) @@ -572,7 +488,7 @@ describe('peer-routing', () => { const result = { id: remotePeerId, multiaddrs: [ - new Multiaddr('/ip4/123.123.123.123/tcp/38982') + multiaddr('/ip4/123.123.123.123/tcp/38982') ], protocols: [] } @@ -585,7 +501,7 @@ describe('peer-routing', () => { sinon.stub(node.dht, 'getClosestPeers').callsFake(async function * () { }) - sinon.stub(delegate, 'getClosestPeers').callsFake(async function * () { + delegate.getClosestPeers.callsFake(async function * () { yield result }) @@ -599,7 +515,7 @@ describe('peer-routing', () => { const results = [{ id: remotePeerId, multiaddrs: [ - new Multiaddr('/ip4/123.123.123.123/tcp/38982') + multiaddr('/ip4/123.123.123.123/tcp/38982') ], protocols: [] }] @@ -619,7 +535,7 @@ describe('peer-routing', () => { } }) - sinon.stub(delegate, 'getClosestPeers').callsFake(async function * () { + delegate.getClosestPeers.callsFake(async function * () { yield * results }) @@ -650,8 +566,8 @@ describe('peer-routing', () => { it('should be enabled and start by default', async () => { const results: PeerInfo[] = [ - { id: peerIds[0], multiaddrs: [new Multiaddr('/ip4/30.0.0.1/tcp/2000')], protocols: [] }, - { id: peerIds[1], multiaddrs: [new Multiaddr('/ip4/32.0.0.1/tcp/2000')], protocols: [] } + { id: peerIds[0], multiaddrs: [multiaddr('/ip4/30.0.0.1/tcp/2000')], protocols: [] }, + { id: peerIds[1], multiaddrs: [multiaddr('/ip4/32.0.0.1/tcp/2000')], protocols: [] } ] node = await createNode({ @@ -773,7 +689,7 @@ describe('peer-routing', () => { messageType: MessageType.FIND_NODE, from: peerIds[0], closer: [ - { id: peerIds[0], multiaddrs: [new Multiaddr('/ip4/30.0.0.1/tcp/2000')], protocols: [] } + { id: peerIds[0], multiaddrs: [multiaddr('/ip4/30.0.0.1/tcp/2000')], protocols: [] } ], providers: [] } diff --git a/test/ping/ping.node.ts b/test/ping/ping.node.ts index 5ee6a13a38..61cd01ff04 100644 --- a/test/ping/ping.node.ts +++ b/test/ping/ping.node.ts @@ -5,7 +5,7 @@ import { pipe } from 'it-pipe' import { createNode, populateAddressBooks } from '../utils/creators/peer.js' import { createBaseOptions } from '../utils/base-options.js' import { PROTOCOL } from '../../src/ping/constants.js' -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import pDefer from 'p-defer' import type { Libp2pNode } from '../../src/libp2p.js' @@ -27,7 +27,7 @@ describe('ping', () => { afterEach(async () => await Promise.all(nodes.map(async n => await n.stop()))) it('ping once from peer0 to peer1 using a multiaddr', async () => { - const ma = new Multiaddr(`${nodes[2].getMultiaddrs()[0].toString()}/p2p/${nodes[2].peerId.toString()}`) + const ma = multiaddr(`${nodes[2].getMultiaddrs()[0].toString()}/p2p/${nodes[2].peerId.toString()}`) const latency = await nodes[0].ping(ma) expect(latency).to.be.a('Number') diff --git a/test/pnet/index.spec.ts b/test/pnet/index.spec.ts index 6675db8e06..52a8e72b40 100644 --- a/test/pnet/index.spec.ts +++ b/test/pnet/index.spec.ts @@ -6,7 +6,7 @@ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { PreSharedKeyConnectionProtector, generateKey } from '../../src/pnet/index.js' import { INVALID_PSK } from '../../src/pnet/errors.js' import { mockMultiaddrConnPair } from '@libp2p/interface-mocks' -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { createEd25519PeerId } from '@libp2p/peer-id-factory' const swarmKeyBuffer = new Uint8Array(95) @@ -28,8 +28,8 @@ describe('private network', () => { it('should protect a simple connection', async () => { const { inbound, outbound } = mockMultiaddrConnPair({ addrs: [ - new Multiaddr('/ip4/127.0.0.1/tcp/1234'), - new Multiaddr('/ip4/127.0.0.1/tcp/1235') + multiaddr('/ip4/127.0.0.1/tcp/1234'), + multiaddr('/ip4/127.0.0.1/tcp/1235') ], remotePeer: await createEd25519PeerId() }) @@ -63,8 +63,8 @@ describe('private network', () => { it('should not be able to share correct data with different keys', async () => { const { inbound, outbound } = mockMultiaddrConnPair({ addrs: [ - new Multiaddr('/ip4/127.0.0.1/tcp/1234'), - new Multiaddr('/ip4/127.0.0.1/tcp/1235') + multiaddr('/ip4/127.0.0.1/tcp/1234'), + multiaddr('/ip4/127.0.0.1/tcp/1235') ], remotePeer: await createEd25519PeerId() }) diff --git a/test/relay/auto-relay.node.ts b/test/relay/auto-relay.node.ts index 77b0cf7eab..a5d2b1075b 100644 --- a/test/relay/auto-relay.node.ts +++ b/test/relay/auto-relay.node.ts @@ -5,16 +5,14 @@ import { pEvent } from 'p-event' import defer from 'p-defer' import pWaitFor from 'p-wait-for' import sinon from 'sinon' -import nock from 'nock' -import { create as createIpfsHttpClient } from 'ipfs-http-client' -import { DelegatedContentRouting } from '@libp2p/delegated-content-routing' import { RELAY_CODEC } from '../../src/circuit/multicodec.js' import { createNode } from '../utils/creators/peer.js' import type { Libp2pNode } from '../../src/libp2p.js' import type { Options as PWaitForOptions } from 'p-wait-for' -import type Sinon from 'sinon' import { createRelayOptions, createNodeOptions } from './utils.js' import { protocols } from '@multiformats/multiaddr' +import { StubbedInstance, stubInterface } from 'ts-sinon' +import type { ContentRouting } from '@libp2p/interface-content-routing' async function usingAsRelay (node: Libp2pNode, relay: Libp2pNode, opts?: PWaitForOptions) { // Wait for peer to be used as a relay @@ -331,27 +329,33 @@ describe('auto-relay', () => { let local: Libp2pNode let remote: Libp2pNode let relayLibp2p: Libp2pNode - let contentRoutingProvideSpy: Sinon.SinonSpy + let localDelegate: StubbedInstance + let remoteDelegate: StubbedInstance + let relayDelegate: StubbedInstance beforeEach(async () => { - const delegate = new DelegatedContentRouting(createIpfsHttpClient({ - host: '0.0.0.0', - protocol: 'http', - port: 60197 - })) + localDelegate = stubInterface() + localDelegate.findProviders.returns(async function * () {}()) + + remoteDelegate = stubInterface() + remoteDelegate.findProviders.returns(async function * () {}()) + + relayDelegate = stubInterface() + relayDelegate.provide.returns(Promise.resolve()) + relayDelegate.findProviders.returns(async function * () {}()) ;[local, remote, relayLibp2p] = await Promise.all([ createNode({ config: createNodeOptions({ contentRouters: [ - delegate + localDelegate ] }) }), createNode({ config: createNodeOptions({ contentRouters: [ - delegate + remoteDelegate ] }) }), @@ -369,43 +373,30 @@ describe('auto-relay', () => { } }, contentRouters: [ - delegate + relayDelegate ] }) }) ]) - - contentRoutingProvideSpy = 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, undefined, [ - 'Content-Type', 'application/json', - 'X-Chunked-Output', '1' - ]) - // Start each node await Promise.all([local, remote, relayLibp2p].map(async libp2p => await libp2p.start())) // Should provide on start - await pWaitFor(() => contentRoutingProvideSpy.callCount === 1) + await pWaitFor(() => relayDelegate.provide.callCount === 1) - const provider = relayLibp2p.peerId.toString() + const provider = relayLibp2p.peerId const multiaddrs = relayLibp2p.getMultiaddrs().map(ma => ma.decapsulateCode(protocols('p2p').code)) - // Mock findProviders - nock('http://0.0.0.0:60197') - .post('/api/v0/dht/findprovs') - .query(true) - .twice() - .reply(200, `{"Extra":"","ID":"${provider}","Responses":[{"Addrs":${JSON.stringify(multiaddrs)},"ID":"${provider}"}],"Type":4}\n`, [ - 'Content-Type', 'application/json', - 'X-Chunked-Output', '1' - ]) + localDelegate.findProviders.returns(async function * () { + yield { + id: provider, + multiaddrs, + protocols: [] + } + }()) }) afterEach(async () => { @@ -417,8 +408,6 @@ describe('auto-relay', () => { const originalMultiaddrsLength = local.getMultiaddrs().length // Spy Find Providers - const contentRoutingFindProvidersSpy = sinon.spy(local.contentRouting, 'findProviders') - const relayAddr = relayLibp2p.getMultiaddrs().pop() if (relayAddr == null) { @@ -435,7 +424,7 @@ describe('auto-relay', () => { await local.hangUp(relayAddr) // Should try to find relay service providers - await pWaitFor(() => contentRoutingFindProvidersSpy.callCount === 1, { + await pWaitFor(() => localDelegate.findProviders.callCount === 1, { timeout: 1000 }) diff --git a/test/relay/relay.node.ts b/test/relay/relay.node.ts index c99bf80319..1f0a899b28 100644 --- a/test/relay/relay.node.ts +++ b/test/relay/relay.node.ts @@ -2,7 +2,7 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { pipe } from 'it-pipe' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { createNode } from '../utils/creators/peer.js' @@ -150,7 +150,7 @@ describe('Dialing (via relay, TCP)', () => { // Connect the destination peer and the relay const tcpAddrs = dstLibp2p.components.getTransportManager().getAddrs() - sinon.stub(dstLibp2p.components.getAddressManager(), 'getListenAddrs').returns([new Multiaddr(`${relayAddr.toString()}/p2p-circuit`)]) + sinon.stub(dstLibp2p.components.getAddressManager(), 'getListenAddrs').returns([multiaddr(`${relayAddr.toString()}/p2p-circuit`)]) await dstLibp2p.components.getTransportManager().listen(dstLibp2p.components.getAddressManager().getListenAddrs()) expect(dstLibp2p.components.getTransportManager().getAddrs()).to.have.deep.members([...tcpAddrs, dialAddr.decapsulate('p2p')]) diff --git a/test/transports/transport-manager.node.ts b/test/transports/transport-manager.node.ts index b5fbcf7c8d..5a14f4ff30 100644 --- a/test/transports/transport-manager.node.ts +++ b/test/transports/transport-manager.node.ts @@ -7,7 +7,7 @@ import { DefaultTransportManager } from '../../src/transport-manager.js' import { PersistentPeerStore } from '@libp2p/peer-store' import { PeerRecord } from '@libp2p/peer-record' import { TCP } from '@libp2p/tcp' -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { mockUpgrader } from '@libp2p/interface-mocks' import sinon from 'sinon' import Peers from '../fixtures/peers.js' @@ -18,8 +18,8 @@ import { Components } from '@libp2p/components' import { PeerRecordUpdater } from '../../src/peer-record-updater.js' const addrs = [ - new Multiaddr('/ip4/127.0.0.1/tcp/0'), - new Multiaddr('/ip4/127.0.0.1/tcp/0') + multiaddr('/ip4/127.0.0.1/tcp/0'), + multiaddr('/ip4/127.0.0.1/tcp/0') ] describe('Transport Manager (TCP)', () => { diff --git a/test/transports/transport-manager.spec.ts b/test/transports/transport-manager.spec.ts index 348ef695d6..a019bbca1f 100644 --- a/test/transports/transport-manager.spec.ts +++ b/test/transports/transport-manager.spec.ts @@ -2,7 +2,7 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { WebSockets } from '@libp2p/websockets' import * as filters from '@libp2p/websockets/filters' import { Plaintext } from '../../src/insecure/index.js' @@ -17,7 +17,7 @@ import { createEd25519PeerId, createFromJSON } from '@libp2p/peer-id-factory' import type { PeerId } from '@libp2p/interface-peer-id' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' -const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') +const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0') describe('Transport Manager (WebSockets)', () => { let tm: DefaultTransportManager @@ -71,7 +71,7 @@ describe('Transport Manager (WebSockets)', () => { it('should fail to dial an unsupported address', async () => { tm.add(new WebSockets({ filter: filters.all })) - const addr = new Multiaddr('/ip4/127.0.0.1/tcp/0') + const addr = multiaddr('/ip4/127.0.0.1/tcp/0') await expect(tm.dial(addr)) .to.eventually.be.rejected() .and.to.have.property('code', ErrorCodes.ERR_TRANSPORT_UNAVAILABLE) diff --git a/test/upgrading/upgrader.spec.ts b/test/upgrading/upgrader.spec.ts index 24027209c1..c01c050c9e 100644 --- a/test/upgrading/upgrader.spec.ts +++ b/test/upgrading/upgrader.spec.ts @@ -3,7 +3,7 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' import { Mplex } from '@libp2p/mplex' -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { pipe } from 'it-pipe' import all from 'it-all' import pSettle from 'p-settle' @@ -34,8 +34,8 @@ import { PersistentPeerStore } from '@libp2p/peer-store' import { MemoryDatastore } from 'datastore-core' const addrs = [ - new Multiaddr('/ip4/127.0.0.1/tcp/0'), - new Multiaddr('/ip4/127.0.0.1/tcp/0') + multiaddr('/ip4/127.0.0.1/tcp/0'), + multiaddr('/ip4/127.0.0.1/tcp/0') ] describe('Upgrader', () => { diff --git a/test/utils/creators/peer.ts b/test/utils/creators/peer.ts index 85569e5b76..63875a1eb5 100644 --- a/test/utils/creators/peer.ts +++ b/test/utils/creators/peer.ts @@ -1,4 +1,4 @@ -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import Peers from '../../fixtures/peers.js' import { createBaseOptions } from '../base-options.browser.js' import { createEd25519PeerId, createFromJSON, createRSAPeerId } from '@libp2p/peer-id-factory' @@ -6,7 +6,7 @@ import { createLibp2pNode, Libp2pNode } from '../../../src/libp2p.js' import type { AddressesConfig, Libp2pOptions } from '../../../src/index.js' import type { PeerId } from '@libp2p/interface-peer-id' -const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/0') +const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0') export interface CreatePeerOptions { /** From a4ac5342526667350853eb800d0228339af02f2c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 21 Sep 2022 14:57:07 +0100 Subject: [PATCH 420/447] chore: release 0.39.2 (#1388) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdfa61b540..226b67a65d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,13 @@ +### [0.39.2](https://www.github.com/libp2p/js-libp2p/compare/v0.39.1...v0.39.2) (2022-09-21) + + +### Bug Fixes + +* remove ipfs dependency and upgrade multiaddr ([#1387](https://www.github.com/libp2p/js-libp2p/issues/1387)) ([633d4a9](https://www.github.com/libp2p/js-libp2p/commit/633d4a9740ea02e32c0bb290c0a3958b68f181e9)) + ### [0.39.1](https://www.github.com/libp2p/js-libp2p/compare/v0.39.0...v0.39.1) (2022-09-09) diff --git a/package.json b/package.json index f15267950d..ad4b4c93b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.39.1", + "version": "0.39.2", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p#readme", From a11260c75353f3e46d8289ad360f986f0680407b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20M=C3=BCller?= Date: Thu, 22 Sep 2022 19:36:55 +0200 Subject: [PATCH 421/447] docs: update noise package name in instructions (#1383) libp2p-noise has been moved to @chainsafe/libp2p-noise --- doc/GETTING_STARTED.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/GETTING_STARTED.md b/doc/GETTING_STARTED.md index c6169f1f82..75dad671e9 100644 --- a/doc/GETTING_STARTED.md +++ b/doc/GETTING_STARTED.md @@ -69,7 +69,7 @@ Encryption is an important part of communicating on the libp2p network. Every co There are a growing number of Crypto modules being developed for libp2p. As those are released they will be tracked in the [Connection Encryption section of the configuration readme](./CONFIGURATION.md#connection-encryption). For now, we are going to configure our node to use the `libp2p-noise` module. ```sh -npm install libp2p-noise +npm install @chainsafe/libp2p-noise ``` With `libp2p-noise` installed, we can add it to our existing configuration by importing it and adding it to the `modules.connEncryption` array: From 62198414b358fd3c4011cf7e64896e803afa8e6b Mon Sep 17 00:00:00 2001 From: Saul Date: Fri, 23 Sep 2022 05:40:33 +1200 Subject: [PATCH 422/447] docs: fix examples in documentation (#1379) This PR fixes all the errors in the code snippets in the readme files contained in the examples directory and it fixes the doc/GETTING_STARTED.md file. I have also updated the old package names, e.g. `libp2p-websockets` -> `@libp2p/websockets`. Resolves #1367 and resolves #1361 Co-authored-by: saul --- doc/GETTING_STARTED.md | 43 ++++--- examples/auto-relay/README.md | 18 +-- examples/discovery-mechanisms/README.md | 112 +++++++++++------- examples/peer-and-content-routing/README.md | 62 +++++----- examples/protocol-and-stream-muxing/README.md | 86 +++++++------- examples/pubsub/README.md | 73 ++++++------ examples/pubsub/message-filtering/README.md | 47 ++++---- examples/transports/README.md | 79 ++++++------ examples/webrtc-direct/README.md | 2 +- 9 files changed, 282 insertions(+), 240 deletions(-) diff --git a/doc/GETTING_STARTED.md b/doc/GETTING_STARTED.md index 75dad671e9..b765913119 100644 --- a/doc/GETTING_STARTED.md +++ b/doc/GETTING_STARTED.md @@ -34,12 +34,12 @@ Now that we have libp2p installed, let's configure the minimum needed to get you Libp2p uses Transports to establish connections between peers over the network. Transports are the components responsible for performing the actual exchange of data between libp2p nodes. You can configure any number of Transports, but you only need 1 to start with. Supporting more Transports will improve the ability of your node to speak to a larger number of nodes on the network, as matching Transports are required for two nodes to communicate with one another. -You should select Transports according to the runtime of your application; Node.js or the browser. You can see a list of some of the available Transports in the [configuration readme](./CONFIGURATION.md#transport). For this guide let's install `libp2p-websockets`, as it can be used in both Node.js and the browser. +You should select Transports according to the runtime of your application; Node.js or the browser. You can see a list of some of the available Transports in the [configuration readme](./CONFIGURATION.md#transport). For this guide let's install `@libp2p/websockets`, as it can be used in both Node.js and the browser. -Start by installing `libp2p-websockets`: +Start by installing `@libp2p/websockets`: ```sh -npm install libp2p-websockets +npm install @libp2p/websockets ``` Now that we have the module installed, let's configure libp2p to use the Transport. We'll use the [`Libp2p.create`](./API.md#create) method, which takes a single configuration object as its only parameter. We can add the Transport by passing it into the `modules.transport` array: @@ -66,13 +66,13 @@ If you want to know more about libp2p transports, you should read the following Encryption is an important part of communicating on the libp2p network. Every connection must be encrypted to help ensure security for everyone. As such, Connection Encryption (Crypto) is a required component of libp2p. -There are a growing number of Crypto modules being developed for libp2p. As those are released they will be tracked in the [Connection Encryption section of the configuration readme](./CONFIGURATION.md#connection-encryption). For now, we are going to configure our node to use the `libp2p-noise` module. +There are a growing number of Crypto modules being developed for libp2p. As those are released they will be tracked in the [Connection Encryption section of the configuration readme](./CONFIGURATION.md#connection-encryption). For now, we are going to configure our node to use the `@chainsafe/libp2p-noise` module. ```sh npm install @chainsafe/libp2p-noise ``` -With `libp2p-noise` installed, we can add it to our existing configuration by importing it and adding it to the `modules.connEncryption` array: +With `@chainsafe/libp2p-noise` installed, we can add it to our existing configuration by importing it and adding it to the `modules.connEncryption` array: ```js import { createLibp2p } from 'libp2p' @@ -96,12 +96,12 @@ If you want to know more about libp2p connection encryption, you should read the While multiplexers are not strictly required, they are highly recommended as they improve the effectiveness and efficiency of connections for the various protocols libp2p runs. Adding a multiplexer to your configuration will allow libp2p to run several of its internal protocols, like Identify, as well as allow your application to easily run any number of protocols over a single connection. -Looking at the [available stream multiplexing](./CONFIGURATION.md#stream-multiplexing) modules, js-libp2p currently only supports `libp2p-mplex`, so we will use that here. Bear in mind that future libp2p Transports might have `multiplexing` capabilities already built-in (such as `QUIC`). +Looking at the [available stream multiplexing](./CONFIGURATION.md#stream-multiplexing) modules, js-libp2p currently only supports `@libp2p/mplex`, so we will use that here. Bear in mind that future libp2p Transports might have `multiplexing` capabilities already built-in (such as `QUIC`). -You can install `libp2p-mplex` and add it to your libp2p node as follows in the next example. +You can install `@libp2p/mplex` and add it to your libp2p node as follows in the next example. ```sh -npm install libp2p-mplex +npm install @libp2p/mplex ``` ```js @@ -148,12 +148,9 @@ const node = await createLibp2p({ await node.start() console.log('libp2p has started') -const listenAddrs = node.transportManager.getAddrs() +const listenAddrs = node.getMultiaddrs() console.log('libp2p is listening on the following addresses: ', listenAddrs) -const advertiseAddrs = node.multiaddrs -console.log('libp2p is advertising the following addresses: ', advertiseAddrs) - // stop libp2p await node.stop() console.log('libp2p has stopped') @@ -170,17 +167,17 @@ Peer discovery is an important part of creating a well connected libp2p node. A For each discovered peer libp2p will emit a `peer:discovery` event which includes metadata about that peer. You can read the [Events](./API.md#events) in the API doc to learn more. Looking at the [available peer discovery](./CONFIGURATION.md#peer-discovery) protocols, there are several options to be considered: -- If you already know the addresses of some other network peers, you should consider using `libp2p-bootstrap` as this is the easiest way of getting your peer into the network. -- If it is likely that you will have other peers on your local network, `libp2p-mdns` is a must if you're node is not running in the browser. It allows peers to discover each other when on the same local network. -- If your application is browser based you can use the `libp2p-webrtc-star` Transport, which includes a rendezvous based peer sharing service. -- A random walk approach can be used via `libp2p-kad-dht`, to crawl the network and find new peers along the way. +- If you already know the addresses of some other network peers, you should consider using `@libp2p/bootstrap` as this is the easiest way of getting your peer into the network. +- If it is likely that you will have other peers on your local network, `@libp2p/mdns` is a must if you're node is not running in the browser. It allows peers to discover each other when on the same local network. +- If your application is browser based you can use the `@libp2p/webrtc-star` Transport, which includes a rendezvous based peer sharing service. +- A random walk approach can be used via `@libp2p/kad-dht`, to crawl the network and find new peers along the way. -For this guide we will configure `libp2p-bootstrap` as this is useful for joining the public network. +For this guide we will configure `@libp2p/bootstrap` as this is useful for joining the public network. -Let's install `libp2p-bootstrap`. +Let's install `@libp2p/bootstrap`. ```sh -npm install libp2p-bootstrap +npm install @libp2p/bootstrap ``` We can provide specific configurations for each protocol within a `config.peerDiscovery` property in the options as shown below. @@ -221,12 +218,12 @@ const node = await createLibp2p({ } }) -node.on('peer:discovery', (peer) => { - console.log('Discovered %s', peer.id.toB58String()) // Log discovered peer +node.addEventListener('peer:discovery', (evt) => { + console.log('Discovered %s', evt.detail.id.toString()) // Log discovered peer }) -node.connectionManager.on('peer:connect', (connection) => { - console.log('Connected to %s', connection.remotePeer.toB58String()) // Log connected peer +node.connectionManager.addEventListener('peer:connect', (evt) => { + console.log('Connected to %s', evt.detail.remotePeer.toString()) // Log connected peer }) // start libp2p diff --git a/examples/auto-relay/README.md b/examples/auto-relay/README.md index d547213189..1fd98d55fb 100644 --- a/examples/auto-relay/README.md +++ b/examples/auto-relay/README.md @@ -24,7 +24,7 @@ import { Mplex } from '@libp2p/mplex' const node = await createLibp2p({ transports: [new WebSockets()], connectionEncryption: [new Noise()], - streamMuxers: [new Mplex()] + streamMuxers: [new Mplex()], addresses: { listen: ['/ip4/0.0.0.0/tcp/0/ws'] // TODO check "What is next?" section @@ -43,9 +43,9 @@ const node = await createLibp2p({ await node.start() -console.log(`Node started with id ${node.peerId.toB58String()}`) +console.log(`Node started with id ${node.peerId.toString()}`) console.log('Listening on:') -node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`)) +node.getMultiaddrs().forEach((ma) => console.log(ma.toString())) ``` The Relay HOP advertise functionality is **NOT** required to be enabled. However, if you are interested in advertising on the network that this node is available to be used as a HOP Relay you can enable it. A content router module or Rendezvous needs to be configured to leverage this option. @@ -94,17 +94,17 @@ const node = await createLibp2p({ }) await node.start() -console.log(`Node started with id ${node.peerId.toB58String()}`) +console.log(`Node started with id ${node.peerId.toString()}`) const conn = await node.dial(relayAddr) console.log(`Connected to the HOP relay ${conn.remotePeer.toString()}`) // Wait for connection and relay to be bind for the example purpose -node.peerStore.on('change:multiaddrs', ({ peerId }) => { +node.peerStore.addEventListener('change:multiaddrs', (evt) => { // Updated self multiaddrs? - if (peerId.equals(node.peerId)) { - console.log(`Advertising with a relay address of ${node.multiaddrs[0].toString()}/p2p/${node.peerId.toB58String()}`) + if (evt.detail.peerId.equals(node.peerId)) { + console.log(`Advertising with a relay address of ${node.getMultiaddrs()[0].toString()}`) } }) ``` @@ -151,7 +151,7 @@ const node = await createLibp2p({ }) await node.start() -console.log(`Node started with id ${node.peerId.toB58String()}`) +console.log(`Node started with id ${node.peerId.toString()}`) const conn = await node.dial(autoRelayNodeAddr) console.log(`Connected to the auto relay node via ${conn.remoteAddr.toString()}`) @@ -176,4 +176,4 @@ As you can see from the output, the remote address of the established connection Before moving into production, there are a few things that you should take into account. -A relay node should not advertise its private address in a real world scenario, as the node would not be reachable by others. You should provide an array of public addresses in the libp2p `addresses.announce` option. If you are using websockets, bear in mind that due to browser’s security policies you cannot establish unencrypted connection from secure context. The simplest solution is to setup SSL with nginx and proxy to the node and setup a domain name for the certificate. \ No newline at end of file +A relay node should not advertise its private address in a real world scenario, as the node would not be reachable by others. You should provide an array of public addresses in the libp2p `addresses.announce` option. If you are using websockets, bear in mind that due to browser’s security policies you cannot establish unencrypted connection from secure context. The simplest solution is to setup SSL with nginx and proxy to the node and setup a domain name for the certificate. diff --git a/examples/discovery-mechanisms/README.md b/examples/discovery-mechanisms/README.md index b7d245184d..706e4a1eab 100644 --- a/examples/discovery-mechanisms/README.md +++ b/examples/discovery-mechanisms/README.md @@ -15,6 +15,9 @@ First, we create our libp2p node. ```JavaScript import { createLibp2p } from 'libp2p' import { Bootstrap } from '@libp2p/bootstrap' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' const node = await createLibp2p({ transports: [ @@ -52,7 +55,6 @@ Now, once we create and start the node, we can listen for events such as `peer:d ```JavaScript const node = await createLibp2p({ - peerId, addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, @@ -73,13 +75,13 @@ const node = await createLibp2p({ ] }) -node.connectionManager.on('peer:connect', (connection) => { - console.log('Connection established to:', connection.remotePeer.toB58String()) // Emitted when a new connection has been created +node.connectionManager.addEventListener('peer:connect', (evt) => { + console.log('Connection established to:', evt.detail.remotePeer.toString()) // Emitted when a new connection has been created }) -node.on('peer:discovery', (peerId) => { +node.addEventListener('peer:discovery', (evt) => { // No need to dial, autoDial is on - console.log('Discovered:', peerId.toB58String()) + console.log('Discovered:', evt.detail.id.toString()) }) await node.start() @@ -105,16 +107,19 @@ Connection established to: QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb ## 2. MulticastDNS to find other peers in the network -For this example, we need `libp2p-mdns`, go ahead and `npm install` it. You can find the complete solution at [2.js](./2.js). +For this example, we need `@libp2p/mdns`, go ahead and `npm install` it. You can find the complete solution at [2.js](./2.js). Update your libp2p configuration to include MulticastDNS. ```JavaScript import { createLibp2p } from 'libp2p' import { MulticastDNS } from '@libp2p/mdns' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' const createNode = () => { - return Libp2p.create({ + return createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, @@ -144,8 +149,8 @@ const [node1, node2] = await Promise.all([ createNode() ]) -node1.on('peer:discovery', (peer) => console.log('Discovered:', peerId.toB58String())) -node2.on('peer:discovery', (peer) => console.log('Discovered:', peerId.toB58String())) +node1.addEventListener('peer:discovery', (evt) => console.log('Discovered:', evt.detail.id.toString())) +node2.addEventListener('peer:discovery', (evt) => console.log('Discovered:', evt.detail.id.toString())) await Promise.all([ node1.start(), @@ -163,7 +168,7 @@ Discovered: QmRcXXhtG8vTqwVBRonKWtV4ovDoC1Fe56WYtcrw694eiJ ## 3. Pubsub based Peer Discovery -For this example, we need [`libp2p-pubsub-peer-discovery`](https://github.com/libp2p/js-libp2p-pubsub-peer-discovery/), go ahead and `npm install` it. You also need to spin up a set of [`libp2p-relay-servers`](https://github.com/libp2p/js-libp2p-relay-server). These servers act as relay servers and a peer discovery source. +For this example, we need [`@libp2p/pubsub-peer-discovery`](https://github.com/libp2p/js-libp2p-pubsub-peer-discovery/), go ahead and `npm install` it. You also need to spin up a set of [`libp2p-relay-servers`](https://github.com/libp2p/js-libp2p-relay-server). These servers act as relay servers and a peer discovery source. In the context of this example, we will create and run the `libp2p-relay-server` in the same code snippet. You can find the complete solution at [3.js](./3.js). @@ -174,9 +179,9 @@ import { createLibp2p } from 'libp2p' import { TCP } from '@libp2p/tcp' import { Mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' -import { Gossipsub } from 'libp2p-gossipsub' +import { GossipSub } from '@chainsafe/libp2p-gossipsub' import { Bootstrap } from '@libp2p/bootstrap' -const PubsubPeerDiscovery from 'libp2p-pubsub-peer-discovery') +import { PubSubPeerDiscovery } from '@libp2p/pubsub-peer-discovery' const createNode = async (bootstrapers) => { const node = await createLibp2p({ @@ -184,23 +189,25 @@ const createNode = async (bootstrapers) => { listen: ['/ip4/0.0.0.0/tcp/0'] }, transports: [ - new TCP() - ], - streamMuxers: [ - new Mplex() - ], - connectionEncryption: [ - new Noise() - ], - peerDiscovery: [ - new Bootstrap({ - interval: 60e3, - list: bootstrapers - }), - new PubsubPeerDiscovery({ - interval: 1000 - }) - ]) + new TCP() + ], + streamMuxers: [ + new Mplex() + ], + connectionEncryption: [ + new Noise() + ], + pubsub: new GossipSub({ allowPublishToZeroPeers: true }), + peerDiscovery: [ + new Bootstrap({ + interval: 60e3, + list: bootstrapers + }), + new PubSubPeerDiscovery({ + interval: 1000 + }) + ] + }) return node } @@ -209,28 +216,49 @@ const createNode = async (bootstrapers) => { We will use the `libp2p-relay-server` as bootstrap nodes for the libp2p nodes, so that they establish a connection with the relay after starting. As a result, after they establish a connection with the relay, the pubsub discovery will kick in and the relay will advertise them. ```js -const relay = await createRelayServer({ - addresses: { - listen: ['/ip4/0.0.0.0/tcp/0'] - } +const relay = await createLibp2p({ + addresses: { + listen: [ + '/ip4/0.0.0.0/tcp/0' + ] + }, + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()], + pubsub: new GossipSub({ allowPublishToZeroPeers: true }), + peerDiscovery: [ + new PubSubPeerDiscovery({ + interval: 1000 + }) + ], + relay: { + 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 + } + } }) -console.log(`libp2p relay starting with id: ${relay.peerId.toB58String()}`) + +console.log(`libp2p relay starting with id: ${relay.peerId.toString()}`) + await relay.start() -const relayMultiaddrs = relay.multiaddrs.map((m) => `${m.toString()}/p2p/${relay.peerId.toB58String()}`) + +const relayMultiaddrs = relay.getMultiaddrs() const [node1, node2] = await Promise.all([ createNode(relayMultiaddrs), createNode(relayMultiaddrs) ]) -node1.on('peer:discovery', (peerId) => { - console.log(`Peer ${node1.peerId.toB58String()} discovered: ${peerId.toB58String()}`) +node1.addEventListener('peer:discovery', (evt) => { + console.log(`Peer ${node1.peerId.toString()} discovered: ${evt.detail.id.toString()}`) }) -node2.on('peer:discovery', (peerId) => { - console.log(`Peer ${node2.peerId.toB58String()} discovered: ${peerId.toB58String()}`) +node2.addEventListener('peer:discovery', (evt) => { + console.log(`Peer ${node2.peerId.toString()} discovered: ${evt.detail.id.toString()}`) }) -;[node1, node2].forEach((node, index) => console.log(`Node ${index} starting with id: ${node.peerId.toB58String()}`)) +;[node1, node2].forEach((node, index) => console.log(`Node ${index} starting with id: ${node.peerId.toString()}`)) + await Promise.all([ node1.start(), node2.start() @@ -258,6 +286,6 @@ This is really useful when running libp2p in constrained environments like a bro 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. For example [libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht) can be used for peer discovery. An example of 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). +- 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. For example [@libp2p/kad-dht](https://github.com/libp2p/js-libp2p-kad-dht) can be used for peer discovery. An example of 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! diff --git a/examples/peer-and-content-routing/README.md b/examples/peer-and-content-routing/README.md index 8e8ec3b516..074cc693bc 100644 --- a/examples/peer-and-content-routing/README.md +++ b/examples/peer-and-content-routing/README.md @@ -15,34 +15,37 @@ First, let's update our config to support Peer Routing and Content Routing. ```JavaScript import { createLibp2p } from 'libp2p' import { KadDHT } from '@libp2p/kad-dht' - -const node = await createLibp2p({ - addresses: { - listen: ['/ip4/0.0.0.0/tcp/0'] - }, - transports: [ - new TCP() - ], - streamMuxers: [ - new Mplex() - ], - connEncryption: [ - new Noise() - ], - // we add the DHT module that will enable Peer and Content Routing - dht: KadDHT -}) +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' + +const createNode = async () => { + const node = await createLibp2p({ + addresses: { + listen: ['/ip4/0.0.0.0/tcp/0'] + }, + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()], + dht: new KadDHT() + }) + + await node.start() + return node +} ``` Once that is done, we can use the createNode function we developed in the previous example to create 3 nodes. Connect node 1 to node 2 and node 2 to node 3. We will use node 2 as a way to find the whereabouts of node 3 ```JavaScript -const node1 = nodes[0] -const node2 = nodes[1] -const node3 = nodes[2] +const [node1, node2, node3] = await Promise.all([ + createNode(), + createNode(), + createNode() +]) -await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) -await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) +await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs()) +await node2.peerStore.addressBook.set(node3.peerId, node3.getMultiaddrs()) await Promise.all([ node1.dial(node2.peerId), @@ -50,12 +53,12 @@ await Promise.all([ ]) // Set up of the cons might take time -await delay(100) +await new Promise(resolve => setTimeout(resolve, 100)) const peer = await node1.peerRouting.findPeer(node3.peerId) console.log('Found it, multiaddrs are:') -peer.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${peer.id.toB58String()}`)) +peer.multiaddrs.forEach((ma) => console.log(ma.toString())) ``` You should see the output being something like: @@ -78,12 +81,17 @@ You can find this example completed in [2.js](./2.js), however as you will see i Instead of calling `peerRouting.findPeer`, we will use `contentRouting.provide` and `contentRouting.findProviders`. ```JavaScript +import { CID } from 'multiformats/cid' +import all from 'it-all' + +const cid = CID.parse('QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL') await node1.contentRouting.provide(cid) -console.log('Node %s is providing %s', node1.peerId.toB58String(), cid.toString()) -const provs = await all(node3.contentRouting.findProviders(cid, { timeout: 5000 })) +console.log('Node %s is providing %s', node1.peerId.toString(), cid.toString()) + +const providers = await all(node3.contentRouting.findProviders(cid, { timeout: 5000 })) -console.log('Found provider:', providers[0].id.toB58String()) +console.log('Found provider:', providers[0].id.toString()) ``` The output of your program should look like: diff --git a/examples/protocol-and-stream-muxing/README.md b/examples/protocol-and-stream-muxing/README.md index 3e76b3966b..2e888dbfc3 100644 --- a/examples/protocol-and-stream-muxing/README.md +++ b/examples/protocol-and-stream-muxing/README.md @@ -6,21 +6,25 @@ The feature of agreeing on a protocol over an established connection is what we # 1. Handle multiple protocols -Let's see _protocol multiplexing_ in action! You will need the following modules for this example: `libp2p`, `libp2p-tcp`, `peer-id`, `it-pipe`, `it-buffer` and `streaming-iterables`. This example reuses the base left by the [Transports](../transports) example. You can see the complete solution at [1.js](./1.js). +Let's see _protocol multiplexing_ in action! You will need the following modules for this example: `libp2p`, `@libp2p/tcp`, `@libp2p/peer-id`, `it-pipe`, `it-buffer` and `streaming-iterables`. This example reuses the base left by the [Transports](../transports) example. You can see the complete solution at [1.js](./1.js). After creating the nodes, we need to tell libp2p which protocols to handle. ```JavaScript import { pipe } from 'it-pipe' -const { map } from 'streaming-iterables') -const { toBuffer } from 'it-buffer') +import { map } from 'streaming-iterables' +import { toBuffer } from 'it-buffer' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' // ... -const node1 = nodes[0] -const node2 = nodes[1] +const [node1, node2] = await Promise.all([ + createNode(), + createNode() +]) // Add node's 2 data to the PeerStore -await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) +await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs()) // Here we are telling libp2p that if someone dials this node to talk with the `/your-protocol` // multicodec, the protocol identifier, please call this handler and give it the stream @@ -30,7 +34,7 @@ node2.handle('/your-protocol', ({ stream }) => { stream, source => (async function () { for await (const msg of source) { - console.log(msg.toString()) + console.log(uint8ArrayToString(msg.subarray())) } })() ) @@ -43,7 +47,7 @@ After the protocol is _handled_, now we can dial to it. const stream = await node1.dialProtocol(node2.peerId, ['/your-protocol']) await pipe( - ['my own protocol, wow!'], + [uint8ArrayFromString('my own protocol, wow!')], stream ) ``` @@ -56,7 +60,7 @@ node2.handle('/another-protocol/1.0.1', ({ stream }) => { stream, async function (source) { for await (const msg of source) { - console.log(msg.toString()) + console.log(uint8ArrayToString(msg.subarray())) } } ) @@ -65,7 +69,7 @@ node2.handle('/another-protocol/1.0.1', ({ stream }) => { const stream = await node1.dialProtocol(node2.peerId, ['/another-protocol/1.0.0']) await pipe( - ['my own protocol, wow!'], + [uint8ArrayFromString('my own protocol, wow!')], stream ) ``` @@ -75,8 +79,8 @@ This feature is super power for network protocols. It works in the same way as v There is still one last feature, you can provide multiple protocols for the same handler. If you have a backwards incompatible change, but it only requires minor changes to the code, you may prefer to do protocol checking instead of having multiple handlers ```JavaScript -node2.handle(['/another-protocol/1.0.0', '/another-protocol/2.0.0'], ({ protocol, stream }) => { - if (protocol === '/another-protocol/2.0.0') { +node2.handle(['/another-protocol/1.0.0', '/another-protocol/2.0.0'], ({ stream }) => { + if (stream.stat.protocol === '/another-protocol/2.0.0') { // handle backwards compatibility } @@ -84,7 +88,7 @@ node2.handle(['/another-protocol/1.0.0', '/another-protocol/2.0.0'], ({ protocol stream, async function (source) { for await (const msg of source) { - console.log(msg.toString()) + console.log(uint8ArrayToString(msg.subarray())) } } ) @@ -107,27 +111,27 @@ import { TCP } from '@libp2p/tcp' import { Mplex } from '@libp2p/mplex' //... -const createNode = () => { - return Libp2p.create({ - transports: [ - new TCP() - ], - streamMuxers: [ - new Mplex() - ] - }) -} +createLibp2p({ + //... + transports: [ + new TCP() + ], + streamMuxers: [ + new Mplex() + ] +}) + ``` With this, we can dial as many times as we want to a peer and always reuse the same established underlying connection. ```JavaScript -node2.handle(['/a', '/b'], ({ protocol, stream }) => { +node2.handle(['/a', '/b'], ({ stream }) => { pipe( stream, async function (source) { for await (const msg of source) { - console.log(`from: ${protocol}, msg: ${msg.toString()}`) + console.log(`from: ${stream.stat.protocol}, msg: ${uint8ArrayToString(msg.subarray())}`) } } ) @@ -135,19 +139,19 @@ node2.handle(['/a', '/b'], ({ protocol, stream }) => { const stream = await node1.dialProtocol(node2.peerId, ['/a']) await pipe( - ['protocol (a)'], + [uint8ArrayFromString('protocol (a)')], stream ) const stream2 = await node1.dialProtocol(node2.peerId, ['/b']) await pipe( - ['protocol (b)'], + [uint8ArrayFromString('protocol (b)')], stream2 ) const stream3 = await node1.dialProtocol(node2.peerId, ['/b']) await pipe( - ['another stream on protocol (b)'], + [uint8ArrayFromString('another stream on protocol (b)')], stream3 ) ``` @@ -172,15 +176,13 @@ You can see this working on example [3.js](./3.js). As we've seen earlier, we can create our node with this createNode function. ```js const createNode = async () => { - const node = await Libp2p.create({ + const node = await createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - modules: { - transport: [TCP], - streamMuxer: [MPLEX], - connEncryption: [NOISE] - } + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()], }) await node.start() @@ -199,7 +201,7 @@ const [node1, node2] = await Promise.all([ Since, we want to connect these nodes `node1` & `node2`, we add our `node2` multiaddr in key-value pair in `node1` peer store. ```js -await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) +await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs()) ``` You may notice that we are only adding `node2` to `node1` peer store. This is because we want to dial up a bidirectional connection between these two nodes. @@ -211,7 +213,7 @@ node1.handle('/node-1', ({ stream }) => { stream, async function (source) { for await (const msg of source) { - console.log(msg.toString()) + console.log(uint8ArrayToString(msg.subarray())) } } ) @@ -222,7 +224,7 @@ node2.handle('/node-2', ({ stream }) => { stream, async function (source) { for await (const msg of source) { - console.log(msg.toString()) + console.log(uint8ArrayToString(msg.subarray())) } } ) @@ -231,14 +233,14 @@ node2.handle('/node-2', ({ stream }) => { // Dialing node2 from node1 const stream1 = await node1.dialProtocol(node2.peerId, ['/node-2']) await pipe( - ['from 1 to 2'], + [uint8ArrayFromString('from 1 to 2')], stream1 ) // Dialing node1 from node2 const stream2 = await node2.dialProtocol(node1.peerId, ['/node-1']) await pipe( - ['from 2 to 1'], + [uint8ArrayFromString('from 2 to 1')], stream2 ) ``` @@ -258,14 +260,14 @@ The code below will result into an error as `the dial address is not valid`. // Dialing from node2 to node1 const stream2 = await node2.dialProtocol(node1.peerId, ['/node-1']) await pipe( - ['from 2 to 1'], + [uint8ArrayFromString('from 2 to 1')], stream2 ) // Dialing from node1 to node2 const stream1 = await node1.dialProtocol(node2.peerId, ['/node-2']) await pipe( - ['from 1 to 2'], + [uint8ArrayFromString('from 1 to 2')], stream1 ) -``` \ No newline at end of file +``` diff --git a/examples/pubsub/README.md b/examples/pubsub/README.md index 1bf86c31c2..19e6e22d4c 100644 --- a/examples/pubsub/README.md +++ b/examples/pubsub/README.md @@ -1,6 +1,6 @@ # Publish Subscribe -Publish Subscribe is also included on the stack. Currently, we have two PubSub implementation available [libp2p-floodsub](https://github.com/libp2p/js-libp2p-floodsub) and [libp2p-gossipsub](https://github.com/ChainSafe/js-libp2p-gossipsub), with many more being researched at [research-pubsub](https://github.com/libp2p/research-pubsub). +Publish Subscribe is also included on the stack. Currently, we have two PubSub implementation available [@libp2p/floodsub](https://github.com/libp2p/js-libp2p-floodsub) and [@chainsafe/libp2p-gossipsub](https://github.com/ChainSafe/js-libp2p-gossipsub), with many more being researched at [research-pubsub](https://github.com/libp2p/research-pubsub). We've seen many interesting use cases appear with this, here are some highlights: @@ -22,54 +22,60 @@ First, let's update our libp2p configuration with a pubsub implementation. ```JavaScript import { createLibp2p } from 'libp2p' -import { Gossipsub } from 'libp2p-gossipsub' - -const node = await createLibp2p({ - addresses: { - listen: ['/ip4/0.0.0.0/tcp/0'] - }, - transports: [ - new TCP() - ], - streamMuxers: [ - new Mplex() - ], - connectionEncryption: [ - new Noise() - ], - // we add the Pubsub module we want - pubsub: new Gossipsub() -}) +import { GossipSub } from '@chainsafe/libp2p-gossipsub' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' + +const createNode = async () => { + const node = await createLibp2p({ + addresses: { + listen: ['/ip4/0.0.0.0/tcp/0'] + }, + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()], + // we add the Pubsub module we want + pubsub: new GossipSub({ allowPublishToZeroPeers: true }) + }) + + await node.start() + + return node +} ``` Once that is done, we only need to create a few libp2p nodes, connect them and everything is ready to start using pubsub. ```JavaScript -const { fromString } from 'uint8arrays/from-string') -const { toString } from 'uint8arrays/to-string') +import { fromString as uint8ArrayFromString } from "uint8arrays/from-string"; +import { toString as uint8ArrayToString } from "uint8arrays/to-string"; + const topic = 'news' -const node1 = nodes[0] -const node2 = nodes[1] +const [node1, node2] = await Promise.all([ + createNode(), + createNode() +]) // Add node's 2 data to the PeerStore -await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) +await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs()) await node1.dial(node2.peerId) -node1.pubsub.on(topic, (msg) => { - console.log(`node1 received: ${toString(msg.data)}`) +node1.pubsub.addEventListener("message", (evt) => { + console.log(`node1 received: ${uint8ArrayToString(evt.detail.data)} on topic ${evt.detail.topic}`) }) await node1.pubsub.subscribe(topic) // Will not receive own published messages by default -node2.pubsub.on(topic, (msg) => { - console.log(`node2 received: ${toString(msg.data)}`) +node2.pubsub.addEventListener("message", (evt) => { + console.log(`node2 received: ${uint8ArrayToString(evt.detail.data)} on topic ${evt.detail.topic}`) }) await node2.pubsub.subscribe(topic) // node2 publishes "news" every second setInterval(() => { - node2.pubsub.publish(topic, fromString('Bird bird bird, bird is the word!')).catch(err => { + node2.pubsub.publish(topic, uint8ArrayFromString('Bird bird bird, bird is the word!')).catch(err => { console.error(err) }) }, 1000) @@ -87,14 +93,7 @@ node1 received: Bird bird bird, bird is the word! You can change the pubsub `emitSelf` option if you want the publishing node to receive its own messages. ```JavaScript -const defaults = { - config: { - pubsub: { - enabled: true, - emitSelf: true - } - } -} +new GossipSub({ allowPublishToZeroPeers: true, emitSelf: true }) ``` The output of the program should look like: diff --git a/examples/pubsub/message-filtering/README.md b/examples/pubsub/message-filtering/README.md index 11aad74207..83f5b84f06 100644 --- a/examples/pubsub/message-filtering/README.md +++ b/examples/pubsub/message-filtering/README.md @@ -8,23 +8,27 @@ First, let's update our libp2p configuration with a pubsub implementation. ```JavaScript import { createLibp2p } from 'libp2p' -import { Gossipsub } from 'libp2p-gossipsub' - -const node = await createLibp2p({ - addresses: { - listen: ['/ip4/0.0.0.0/tcp/0'] - }, - transports: [ - new TCP() - ], - streamMuxers: [ - new Mplex() - ], - connectionEncryption: [ - new Noise() - ], - pubsub: new Gossipsub() -}) +import { GossipSub } from '@chainsafe/libp2p-gossipsub' +import { TCP } from '@libp2p/tcp' +import { Mplex } from '@libp2p/mplex' +import { Noise } from '@chainsafe/libp2p-noise' + +const createNode = async () => { + const node = await createLibp2p({ + addresses: { + listen: ['/ip4/0.0.0.0/tcp/0'] + }, + transports: [new TCP()], + streamMuxers: [new Mplex()], + connectionEncryption: [new Noise()], + // we add the Pubsub module we want + pubsub: new GossipSub({ allowPublishToZeroPeers: true }) + }) + + await node.start() + + return node +} ``` 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. @@ -36,16 +40,19 @@ const [node1, node2, node3] = await Promise.all([ createNode(), ]) -await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) +await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs()) await node1.dial(node2.peerId) -await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) +await node2.peerStore.addressBook.set(node3.peerId, node3.getMultiaddrs()) await node2.dial(node3.peerId) ``` Now we' can subscribe to the fruit topic and log incoming messages. ```JavaScript +import { fromString as uint8ArrayFromString } from "uint8arrays/from-string"; +import { toString as uint8ArrayToString } from "uint8arrays/to-string"; + const topic = 'fruit' node1.pubsub.addEventListener('message', (msg) => { @@ -116,4 +123,4 @@ node3 received: apple node1 received: orange node2 received: orange node3 received: orange -``` \ No newline at end of file +``` diff --git a/examples/transports/README.md b/examples/transports/README.md index fb4b920525..153cd1b734 100644 --- a/examples/transports/README.md +++ b/examples/transports/README.md @@ -13,7 +13,7 @@ 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 @chainsafe/libp2p-noise +> npm install libp2p @libp2p/tcp @chainsafe/libp2p-noise ``` Then, in your favorite text editor create a file with the `.js` extension. I've called mine `1.js`. @@ -58,7 +58,7 @@ console.log('node has started (true/false):', node.isStarted()) // 0, which means "listen in any network interface and pick // a port for me console.log('listening on:') -node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`)) +node.getMultiaddrs().forEach((ma) => console.log(ma.toString())) ``` Running this should result in something like: @@ -80,15 +80,15 @@ Now that we have our `createNode` function, let's create two nodes and make them For this step, we will need some more dependencies. ```bash -> npm install it-pipe it-to-buffer @libp2p/mplex +> npm install it-pipe it-all @libp2p/mplex ``` And we also need to import the modules on our .js file: ```js import { pipe } from 'it-pipe' -import toBuffer from 'it-to-buffer' import { Mplex } from '@libp2p/mplex' +import all from 'it-all' ``` We are going to reuse the `createNode` function from step 1, but this time add a stream multiplexer from `libp2p-mplex`. @@ -114,38 +114,39 @@ We will also make things simpler by creating another function to print the multi ```JavaScript function printAddrs (node, number) { console.log('node %s is listening on:', number) - node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`)) + node.getMultiaddrs().forEach((ma) => console.log(ma.toString())) } ``` Then add, ```js -(async () => { - const [node1, node2] = await Promise.all([ - createNode(), - createNode() - ]) - - printAddrs(node1, '1') - printAddrs(node2, '2') - - node2.handle('/print', async ({ stream }) => { - const result = await pipe( - stream, - toBuffer - ) - console.log(result.toString()) - }) +import { fromString as uint8ArrayFromString } from "uint8arrays/from-string"; +import { toString as uint8ArrayToString } from "uint8arrays/to-string"; + +const [node1, node2] = await Promise.all([ + createNode(), + createNode() +]) - await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) - const stream = await node1.dialProtocol(node2.peerId, '/print') +printAddrs(node1, '1') +printAddrs(node2, '2') - await pipe( - ['Hello', ' ', 'p2p', ' ', 'world', '!'], - stream +node2.handle('/print', async ({ stream }) => { + const result = await pipe( + stream, + all ) -})(); + console.log(result.map(buf => uint8ArrayToString(buf.subarray())).join("")) +}) + +await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs()) +const stream = await node1.dialProtocol(node2.peerId, '/print') + +await pipe( + ['Hello', ' ', 'p2p', ' ', 'world', '!'].map(str => uint8ArrayFromString(str)), + stream +) ``` For more information refer to the [docs](https://github.com/libp2p/js-libp2p/blob/master/doc/API.md). @@ -168,10 +169,10 @@ Next, we want nodes to have multiple transports available to increase their chan 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`: +In this example, we will need to also install `@libp2p/websockets`: ```bash -> npm install libp2p-websockets +> 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 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: @@ -188,7 +189,7 @@ const createNode = async (transports, addresses = []) => { addresses: { listen: addresses }, - transport: transports, + transports: transports, connectionEncryption: [new Noise()], streamMuxers: [new Mplex()] }) @@ -207,9 +208,9 @@ import { WebSockets } from '@libp2p/websockets' import { TCP } from '@libp2p/tcp' const [node1, node2, node3] = await Promise.all([ - createNode([TCP], '/ip4/0.0.0.0/tcp/0'), - createNode([TCP, WebSockets], ['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws']), - createNode([WebSockets], '/ip4/127.0.0.1/tcp/20000/ws') + createNode([new TCP()], '/ip4/0.0.0.0/tcp/0'), + createNode([new TCP(), new WebSockets()], ['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws']), + createNode([new WebSockets()], '/ip4/127.0.0.1/tcp/20000/ws') ]) printAddrs(node1, '1') @@ -220,21 +221,21 @@ node1.handle('/print', print) node2.handle('/print', print) node3.handle('/print', print) -await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) -await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) -await node3.peerStore.addressBook.set(node1.peerId, node1.multiaddrs) +await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs()) +await node2.peerStore.addressBook.set(node3.peerId, node3.getMultiaddrs()) +await node3.peerStore.addressBook.set(node1.peerId, node1.getMultiaddrs()) // node 1 (TCP) dials to node 2 (TCP+WebSockets) const stream = await node1.dialProtocol(node2.peerId, '/print') await pipe( - ['node 1 dialed to node 2 successfully'], + ['node 1 dialed to node 2 successfully'].map(str => uint8ArrayFromString(str)), stream ) // node 2 (TCP+WebSockets) dials to node 3 (WebSockets) const stream2 = await node2.dialProtocol(node3.peerId, '/print') await pipe( - ['node 2 dialed to node 3 successfully'], + ['node 2 dialed to node 3 successfully'].map(str => uint8ArrayFromString(str)), stream2 ) @@ -254,7 +255,7 @@ function print ({ stream }) { stream, async function (source) { for await (const msg of source) { - console.log(msg.toString()) + console.log(uint8ArrayToString(msg.subarray())) } } ) diff --git a/examples/webrtc-direct/README.md b/examples/webrtc-direct/README.md index 529316a8a5..ea04fd3465 100644 --- a/examples/webrtc-direct/README.md +++ b/examples/webrtc-direct/README.md @@ -1,6 +1,6 @@ ### Webrtc-direct example -An example that uses [js-libp2p-webrtc-direct](https://github.com/libp2p/js-libp2p-webrtc-direct) for connecting +An example that uses [@libp2p/webrtc-direct](https://github.com/libp2p/js-libp2p-webrtc-direct) for connecting nodejs libp2p and browser libp2p clients. To run the example: ## 0. Run a nodejs libp2p listener From 3f57edaf3b472daf8ea6e914f38ff9ad6cf9b49c Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 23 Sep 2022 09:31:04 +0100 Subject: [PATCH 423/447] fix: yield only final peers from dht getClosestPeers (#1380) * fix: yield final peers from dht getClosestPeers `PEER_RESPONSE` is an intermediate event, we should only yield from `FINAL_PEER` events as we'll only get `K` of those. * chore: fix test --- src/dht/dht-peer-routing.ts | 4 ++-- test/peer-routing/peer-routing.node.ts | 27 ++++++++++---------------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/dht/dht-peer-routing.ts b/src/dht/dht-peer-routing.ts index 86b6eb574e..e1d5ddafb3 100644 --- a/src/dht/dht-peer-routing.ts +++ b/src/dht/dht-peer-routing.ts @@ -27,8 +27,8 @@ export class DHTPeerRouting implements PeerRouting { async * getClosestPeers (key: Uint8Array, options: AbortOptions = {}) { for await (const event of this.dht.getClosestPeers(key, options)) { - if (event.name === 'PEER_RESPONSE') { - yield * event.closer + if (event.name === 'FINAL_PEER') { + yield event.peer } } } diff --git a/test/peer-routing/peer-routing.node.ts b/test/peer-routing/peer-routing.node.ts index 204dba11eb..1cffc5d6cb 100644 --- a/test/peer-routing/peer-routing.node.ts +++ b/test/peer-routing/peer-routing.node.ts @@ -112,16 +112,15 @@ describe('peer-routing', () => { const dhtGetClosestPeersStub = sinon.stub(nodes[0].dht, 'getClosestPeers').callsFake(async function * () { yield { from: nodes[2].peerId, - type: EventTypes.PEER_RESPONSE, - name: 'PEER_RESPONSE', + type: EventTypes.FINAL_PEER, + name: 'FINAL_PEER', messageName: 'FIND_NODE', messageType: MessageType.FIND_NODE, - closer: [{ + peer: { id: nodes[1].peerId, multiaddrs: [], protocols: [] - }], - providers: [] + } } }) @@ -589,26 +588,20 @@ describe('peer-routing', () => { const peerStoreAddressBookAddStub = sinon.spy(node.peerStore.addressBook, 'add') const dhtGetClosestPeersStub = sinon.stub(node.dht, 'getClosestPeers').callsFake(async function * () { yield { - name: 'PEER_RESPONSE', - type: EventTypes.PEER_RESPONSE, + name: 'FINAL_PEER', + type: EventTypes.FINAL_PEER, messageName: 'FIND_NODE', messageType: MessageType.FIND_NODE, from: peerIds[0], - closer: [ - results[0] - ], - providers: [] + peer: results[0] } yield { - name: 'PEER_RESPONSE', - type: EventTypes.PEER_RESPONSE, + name: 'FINAL_PEER', + type: EventTypes.FINAL_PEER, messageName: 'FIND_NODE', messageType: MessageType.FIND_NODE, from: peerIds[0], - closer: [ - results[1] - ], - providers: [] + peer: results[1] } }) From ec02351e65d0627872e6a53894c060a593b9e66e Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 23 Sep 2022 09:33:42 +0100 Subject: [PATCH 424/447] fix: when creating dial targets, encapsulate PeerIds last (#1389) It turns out because `Multiaddr.encapsulate` stringifies the `Multiaddr` it's a [suprisingly expensive operation](https://github.com/multiformats/js-multiaddr/pull/275#issuecomment-1254981709) so here we switch the order of our `Multiaddr` pipeline around so we filter undialable addresses (e.g. unsupported transports etc) before encapsulating the `PeerId` onto a `Multiaddr` we'd then just ignore. --- src/connection-manager/dialer/index.ts | 29 +++++++++++++------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/connection-manager/dialer/index.ts b/src/connection-manager/dialer/index.ts index 7a731c945b..fd2a6ba8d7 100644 --- a/src/connection-manager/dialer/index.ts +++ b/src/connection-manager/dialer/index.ts @@ -232,15 +232,23 @@ export class DefaultDialer implements Startable, Dialer { * Multiaddrs not supported by the available transports will be filtered out. */ async _createDialTarget (peer: PeerId, options: AbortOptions): Promise { - const knownAddrs = await pipe( + const _resolve = this._resolve.bind(this) + + const addrs = await pipe( await this.components.getPeerStore().addressBook.get(peer), (source) => filter(source, async (address) => { return !(await this.components.getConnectionGater().denyDialMultiaddr(peer, address.multiaddr)) }), + // Sort addresses so, for example, we try certified public address first (source) => sort(source, this.addressSorter), - (source) => map(source, (address) => { - const ma = address.multiaddr - + async function * resolve (source) { + for await (const a of source) { + yield * await _resolve(a.multiaddr, options) + } + }, + // Multiaddrs not supported by the available transports will be filtered out. + (source) => filter(source, (ma) => Boolean(this.components.getTransportManager().transportForMultiaddr(ma))), + (source) => map(source, (ma) => { if (peer.toString() === ma.getPeerId()) { return ma } @@ -250,23 +258,14 @@ export class DefaultDialer implements Startable, Dialer { async (source) => await all(source) ) - const addrs: Multiaddr[] = [] - for (const a of knownAddrs) { - const resolvedAddrs = await this._resolve(a, options) - resolvedAddrs.forEach(ra => addrs.push(ra)) - } - - // Multiaddrs not supported by the available transports will be filtered out. - const supportedAddrs = addrs.filter(a => this.components.getTransportManager().transportForMultiaddr(a)) - - if (supportedAddrs.length > this.maxAddrsToDial) { + if (addrs.length > this.maxAddrsToDial) { await this.components.getPeerStore().delete(peer) throw errCode(new Error('dial with more addresses than allowed'), codes.ERR_TOO_MANY_ADDRESSES) } return { id: peer.toString(), - addrs: supportedAddrs + addrs } } From 0ecc02b2a426b6dfec7b6f46d565fde41ad66954 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 23 Sep 2022 09:34:54 +0100 Subject: [PATCH 425/447] docs: update delegated routing example readme (#1390) --- examples/delegated-routing/README.md | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/examples/delegated-routing/README.md b/examples/delegated-routing/README.md index e72e512e86..4a633f0753 100644 --- a/examples/delegated-routing/README.md +++ b/examples/delegated-routing/README.md @@ -1,31 +1,23 @@ -❗❗Outdated: This example is still not refactored with the `0.27.*` release. -WIP on [libp2p/js-libp2p#507](https://github.com/libp2p/js-libp2p/pull/507) -====== - # Delegated Routing with Libp2p and IPFS -This example shows how to use delegated peer and content routing. The [Peer and Content Routing Example](../peer-and-content-routing) focuses -on the DHT implementation. This example takes that a step further and introduces delegated routing. Delegated routing is -especially useful when your libp2p node will have limited resources, making running a DHT impractical. It's -also highly useful if your node is generating content, but can't reliably be on the network. You can use delegate nodes -to provide content on your behalf. +This example shows how to use delegated peer and content routing. The [Peer and Content Routing Example](../peer-and-content-routing) focuses on the DHT implementation. This example takes that a step further and introduces delegated routing. Delegated routing is especially useful when your libp2p node will have limited resources, making running a DHT impractical. It's also highly useful if your node is generating content, but can't reliably be on the network. You can use delegate nodes to provide content on your behalf. The starting [Libp2p Bundle](./src/libp2p-bundle.js) in this example starts by disabling the DHT and adding the Delegated Peer and Content Routers. -Once you've completed the example, you should try enabled the DHT and see what kind of results you get! You can also enable the -various Peer Discovery modules and see the impact it has on your Peer count. +Once you've completed the example, you should try enabled the DHT and see what kind of results you get! You can also enable the various Peer Discovery modules and see the impact it has on your Peer count. ## Prerequisite -**NOTE**: This example is currently dependent on a clone of the [delegated routing support branch of go-ipfs](https://github.com/ipfs/go-ipfs/pull/4595). + +This example uses a publicly known delegated routing node. This aims to ease experimentation, but you should not rely on this in production. ## Running this example 1. Install IPFS locally if you dont already have it. [Install Guide](https://docs.ipfs.io/introduction/install/) 2. Run the IPFS daemon: `ipfs daemon` -3. The daemon will output a line about its API address, like `API server listening on /ip4/127.0.0.1/tcp/8080` -4. In another window output the addresses of the node: `ipfs id`. Make note of the websocket address, it will contain `/ws/` in the address. -5. In `./src/libp2p-bundle.js` check if the host and port of your node are correct, according to the previous step. If they are different, replace them. -6. In `./src/App.js` replace `BootstrapNode` with your nodes Websocket address from step 4. -7. Start this example: +3. In another window output the addresses of the node: `ipfs id`. Make note of the websocket address, it will contain `/ws/` in the address. + - If there is no websocket address, you will need to add it in the ipfs config file (`~/.ipfs/config`) + - Add to Swarm Addresses something like: `"/ip4/127.0.0.1/tcp/4010/ws"` +4. In `./src/App.js` replace `BootstrapNode` with your nodes Websocket address from the step above. +5. Start this example ```sh npm install @@ -34,7 +26,7 @@ npm start This should open your browser to http://localhost:3000. If it does not, go ahead and do that now. -8. Your browser should show you connected to at least 1 peer. +6. Your browser should show you connected to at least 1 peer. ### Finding Content via the Delegate 1. Add a file to your IPFS node. From this example root you can do `ipfs add ./README.md` to add the example readme. From 806804ac888b435417853921c2b05744bbdc1d94 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Sep 2022 13:51:16 +0100 Subject: [PATCH 426/447] chore(deps-dev): bump @libp2p/bootstrap from 2.0.1 to 3.0.0 (#1391) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ad4b4c93b5..7597207d60 100644 --- a/package.json +++ b/package.json @@ -170,7 +170,7 @@ "devDependencies": { "@chainsafe/libp2p-noise": "^8.0.1", "@chainsafe/libp2p-yamux": "^1.0.0", - "@libp2p/bootstrap": "^2.0.1", + "@libp2p/bootstrap": "^3.0.0", "@libp2p/daemon-client": "^3.0.1", "@libp2p/daemon-server": "^3.0.1", "@libp2p/floodsub": "^3.0.0", From 6ac62da025b5bf20f559cf241deb0fb9020e1418 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 3 Oct 2022 15:07:18 +0100 Subject: [PATCH 427/447] chore: release 0.39.3 (#1392) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 226b67a65d..ece921b27d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,14 @@ +### [0.39.3](https://www.github.com/libp2p/js-libp2p/compare/v0.39.2...v0.39.3) (2022-09-28) + + +### Bug Fixes + +* when creating dial targets, encapsulate PeerIds last ([#1389](https://www.github.com/libp2p/js-libp2p/issues/1389)) ([ec02351](https://www.github.com/libp2p/js-libp2p/commit/ec02351e65d0627872e6a53894c060a593b9e66e)) +* yield only final peers from dht getClosestPeers ([#1380](https://www.github.com/libp2p/js-libp2p/issues/1380)) ([3f57eda](https://www.github.com/libp2p/js-libp2p/commit/3f57edaf3b472daf8ea6e914f38ff9ad6cf9b49c)) + ### [0.39.2](https://www.github.com/libp2p/js-libp2p/compare/v0.39.1...v0.39.2) (2022-09-21) diff --git a/package.json b/package.json index 7597207d60..5ef0c330d7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.39.2", + "version": "0.39.3", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p#readme", From 12a2c75efc0fc730976652b3ead79f8332476149 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 4 Oct 2022 15:26:30 +0100 Subject: [PATCH 428/447] fix: update insecure connection encrypter (#1400) Update API to make peer id optional. --- examples/delegated-routing/package.json | 2 +- examples/libp2p-in-the-browser/package.json | 2 +- examples/webrtc-direct/package.json | 2 +- package.json | 2 +- src/insecure/index.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/delegated-routing/package.json b/examples/delegated-routing/package.json index 0a2590243e..fc3cae70c8 100644 --- a/examples/delegated-routing/package.json +++ b/examples/delegated-routing/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { - "@chainsafe/libp2p-noise": "^8.0.1", + "@chainsafe/libp2p-noise": "^8.0.2", "ipfs-core": "^0.15.4", "libp2p": "../../", "@libp2p/delegated-content-routing": "^2.0.1", diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json index 8758c8fa6d..2abd84345f 100644 --- a/examples/libp2p-in-the-browser/package.json +++ b/examples/libp2p-in-the-browser/package.json @@ -9,7 +9,7 @@ }, "license": "ISC", "dependencies": { - "@chainsafe/libp2p-noise": "^8.0.1", + "@chainsafe/libp2p-noise": "^8.0.2", "@libp2p/bootstrap": "^2.0.1", "@libp2p/mplex": "^5.2.3", "@libp2p/webrtc-star": "^3.0.3", diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index b53e5b3ca0..37c08976b2 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -10,7 +10,7 @@ "license": "ISC", "dependencies": { "@libp2p/webrtc-direct": "^2.0.0", - "@chainsafe/libp2p-noise": "^8.0.1", + "@chainsafe/libp2p-noise": "^8.0.2", "@libp2p/bootstrap": "^2.0.1", "@libp2p/mplex": "^5.2.3", "libp2p": "../../", diff --git a/package.json b/package.json index 5ef0c330d7..539c936417 100644 --- a/package.json +++ b/package.json @@ -168,7 +168,7 @@ "xsalsa20": "^1.1.0" }, "devDependencies": { - "@chainsafe/libp2p-noise": "^8.0.1", + "@chainsafe/libp2p-noise": "^8.0.2", "@chainsafe/libp2p-yamux": "^1.0.0", "@libp2p/bootstrap": "^3.0.0", "@libp2p/daemon-client": "^3.0.1", diff --git a/src/insecure/index.ts b/src/insecure/index.ts index d773415666..341b407e1d 100644 --- a/src/insecure/index.ts +++ b/src/insecure/index.ts @@ -100,7 +100,7 @@ export class Plaintext implements ConnectionEncrypter { return await encrypt(localId, conn, remoteId) } - async secureOutbound (localId: PeerId, conn: Duplex, remoteId: PeerId): Promise { + async secureOutbound (localId: PeerId, conn: Duplex, remoteId?: PeerId): Promise { return await encrypt(localId, conn, remoteId) } } From 01749a952b98a1ae28001cfecf79e78b3ebc1d18 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 4 Oct 2022 15:40:03 +0100 Subject: [PATCH 429/447] chore: release 0.39.4 (#1401) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ece921b27d..df6ebe5307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,13 @@ +### [0.39.4](https://www.github.com/libp2p/js-libp2p/compare/v0.39.3...v0.39.4) (2022-10-04) + + +### Bug Fixes + +* update insecure connection encrypter ([#1400](https://www.github.com/libp2p/js-libp2p/issues/1400)) ([12a2c75](https://www.github.com/libp2p/js-libp2p/commit/12a2c75efc0fc730976652b3ead79f8332476149)) + ### [0.39.3](https://www.github.com/libp2p/js-libp2p/compare/v0.39.2...v0.39.3) (2022-09-28) diff --git a/package.json b/package.json index 539c936417..41dfc92080 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.39.3", + "version": "0.39.4", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p#readme", From 5ad175cb3918da0956f6c1c336f5423a551c78a8 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 5 Oct 2022 10:31:46 +0100 Subject: [PATCH 430/447] fix: stub new connection manager accept incoming connection method (#1404) Temporary fix until #1398 is merged. --- src/connection-manager/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index c3d0de88d7..1b9b7e7ce6 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -10,7 +10,7 @@ import type { Startable } from '@libp2p/interfaces/startable' import { codes } from '../errors.js' import { isPeerId, PeerId } from '@libp2p/interface-peer-id' import { setMaxListeners } from 'events' -import type { Connection } from '@libp2p/interface-connection' +import type { Connection, MultiaddrConnection } from '@libp2p/interface-connection' import type { ConnectionManager } from '@libp2p/interface-connection-manager' import { Components, Initializable } from '@libp2p/components' import * as STATUS from '@libp2p/interface-connection/status' @@ -675,4 +675,8 @@ export class DefaultConnectionManager extends EventEmitter { + return true + } } From 2ff4bf2a0051a09018bdb9ad664b722633aec7fd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 5 Oct 2022 10:44:39 +0100 Subject: [PATCH 431/447] chore: release 0.39.5 (#1405) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df6ebe5307..da5af2cb2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,13 @@ +### [0.39.5](https://www.github.com/libp2p/js-libp2p/compare/v0.39.4...v0.39.5) (2022-10-05) + + +### Bug Fixes + +* stub new connection manager accept incoming connection method ([#1404](https://www.github.com/libp2p/js-libp2p/issues/1404)) ([5ad175c](https://www.github.com/libp2p/js-libp2p/commit/5ad175cb3918da0956f6c1c336f5423a551c78a8)) + ### [0.39.4](https://www.github.com/libp2p/js-libp2p/compare/v0.39.3...v0.39.4) (2022-10-04) diff --git a/package.json b/package.json index 41dfc92080..5afd0466f3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.39.4", + "version": "0.39.5", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p#readme", From 6a8aead489bd00ad4c08f47c1adb7185e1a087e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Oct 2022 11:54:49 +0100 Subject: [PATCH 432/447] chore(deps-dev): bump @libp2p/interface-mocks from 4.0.3 to 5.1.0 (#1407) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5afd0466f3..5d36052939 100644 --- a/package.json +++ b/package.json @@ -176,7 +176,7 @@ "@libp2p/floodsub": "^3.0.0", "@libp2p/interface-compliance-tests": "^3.0.2", "@libp2p/interface-connection-encrypter-compliance-tests": "^2.0.2", - "@libp2p/interface-mocks": "^4.0.3", + "@libp2p/interface-mocks": "^5.1.0", "@libp2p/interop": "^3.0.1", "@libp2p/kad-dht": "^3.0.5", "@libp2p/mdns": "^3.0.1", From 90d3528ab36fb30b545f7e7c9e89a9813d1068f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Oct 2022 13:11:17 +0100 Subject: [PATCH 433/447] chore(deps): bump @libp2p/interface-connection-encrypter from 2.0.2 to 3.0.1 (#1406) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: achingbrain --- package.json | 2 +- src/insecure/index.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 5d36052939..0cf2338843 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "@libp2p/crypto": "^1.0.4", "@libp2p/interface-address-manager": "^1.0.3", "@libp2p/interface-connection": "^3.0.2", - "@libp2p/interface-connection-encrypter": "^2.0.1", + "@libp2p/interface-connection-encrypter": "^3.0.1", "@libp2p/interface-connection-manager": "^1.1.1", "@libp2p/interface-content-routing": "^1.0.2", "@libp2p/interface-dht": "^1.0.1", diff --git a/src/insecure/index.ts b/src/insecure/index.ts index 341b407e1d..abd859afb0 100644 --- a/src/insecure/index.ts +++ b/src/insecure/index.ts @@ -88,8 +88,7 @@ async function encrypt (localId: PeerId, conn: Duplex, remoteId?: Pe sink: shake.stream.sink, source: map(shake.stream.source, (buf) => buf.subarray()) }, - remotePeer: peerId, - remoteEarlyData: new Uint8Array() + remotePeer: peerId } } From ca3019283497040314603d9ca7c0b65c64d1680c Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 6 Oct 2022 14:14:41 +0100 Subject: [PATCH 434/447] fix!: do not auto-dial peers (#1397) Currently whenever a new peer is discovered we automatically dial them if we are below the connection manager connection limit. This is incredibly resource intensive and does not necessarily result in high-quality connections (e.g. KAD close, relays or supporting protocols we are interested in). Now that we can search the DHT for peers, this is no longer necessary so remove the functionality. BREAKING CHANGE: the old behaviour was to dial any peer we discover, now we just add them to the peer store instead --- examples/libp2p-in-the-browser/index.js | 5 ++ examples/peer-and-content-routing/1.js | 2 +- examples/webrtc-direct/dialer.js | 5 ++ src/connection-manager/dialer/auto-dialer.ts | 65 -------------------- src/libp2p.ts | 15 ----- 5 files changed, 11 insertions(+), 81 deletions(-) delete mode 100644 src/connection-manager/dialer/auto-dialer.ts diff --git a/examples/libp2p-in-the-browser/index.js b/examples/libp2p-in-the-browser/index.js index 92213d9afe..eccd6953c7 100644 --- a/examples/libp2p-in-the-browser/index.js +++ b/examples/libp2p-in-the-browser/index.js @@ -54,6 +54,11 @@ document.addEventListener('DOMContentLoaded', async () => { libp2p.addEventListener('peer:discovery', (evt) => { const peer = evt.detail log(`Found peer ${peer.id.toString()}`) + + // dial them when we discover them + libp2p.dial(evt.detail.id).catch(err => { + log(`Could not dial ${evt.detail.id}`, err) + }) }) // Listen for new connections to peers diff --git a/examples/peer-and-content-routing/1.js b/examples/peer-and-content-routing/1.js index 12ecfd4be8..7208909826 100644 --- a/examples/peer-and-content-routing/1.js +++ b/examples/peer-and-content-routing/1.js @@ -38,7 +38,7 @@ const createNode = async () => { ]) // The DHT routing tables need a moment to populate - await delay(100) + await delay(1000) const peer = await node1.peerRouting.findPeer(node3.peerId) diff --git a/examples/webrtc-direct/dialer.js b/examples/webrtc-direct/dialer.js index 1663054cfb..d1dc0066ba 100644 --- a/examples/webrtc-direct/dialer.js +++ b/examples/webrtc-direct/dialer.js @@ -31,6 +31,11 @@ document.addEventListener('DOMContentLoaded', async () => { // Listen for new peers libp2p.addEventListener('peer:discovery', (evt) => { log(`Found peer ${evt.detail.id.toString()}`) + + // dial them when we discover them + libp2p.dial(evt.detail.id).catch(err => { + log(`Could not dial ${evt.detail.id}`, err) + }) }) // Listen for new connections to peers diff --git a/src/connection-manager/dialer/auto-dialer.ts b/src/connection-manager/dialer/auto-dialer.ts deleted file mode 100644 index 0c572647ca..0000000000 --- a/src/connection-manager/dialer/auto-dialer.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { PeerInfo } from '@libp2p/interface-peer-info' -import { logger } from '@libp2p/logger' -import type { Components } from '@libp2p/components' -import { TimeoutController } from 'timeout-abort-controller' -import { setMaxListeners } from 'events' - -const log = logger('libp2p:dialer:auto-dialer') - -export interface AutoDialerInit { - enabled: boolean - minConnections: number - dialTimeout: number -} - -export class AutoDialer { - private readonly components: Components - private readonly enabled: boolean - private readonly minConnections: number - private readonly dialTimeout: number - - constructor (components: Components, init: AutoDialerInit) { - this.components = components - this.enabled = init.enabled - this.minConnections = init.minConnections - this.dialTimeout = init.dialTimeout - } - - public handle (evt: CustomEvent) { - const { detail: peer } = evt - - if (!this.enabled) { - return - } - - const connections = this.components.getConnectionManager().getConnections(peer.id) - - // If auto dialing is on and we have no connection to the peer, check if we should dial - if (connections.length === 0) { - const minConnections = this.minConnections ?? 0 - - const allConnections = this.components.getConnectionManager().getConnections() - - if (minConnections > allConnections.length) { - log('auto-dialing discovered peer %p with timeout %d', peer.id, this.dialTimeout) - - const controller = new TimeoutController(this.dialTimeout) - - try { - // fails on node < 15.4 - setMaxListeners?.(Infinity, controller.signal) - } catch {} - - void this.components.getConnectionManager().openConnection(peer.id, { - signal: controller.signal - }) - .catch(err => { - log.error('could not connect to discovered peer %p with %o', peer.id, err) - }) - .finally(() => { - controller.clear() - }) - } - } - } -} diff --git a/src/libp2p.ts b/src/libp2p.ts index 0bc0c9959c..00a9748881 100644 --- a/src/libp2p.ts +++ b/src/libp2p.ts @@ -26,7 +26,6 @@ import { PeerRecordUpdater } from './peer-record-updater.js' import { DHTPeerRouting } from './dht/dht-peer-routing.js' import { PersistentPeerStore } from '@libp2p/peer-store' import { DHTContentRouting } from './dht/dht-content-routing.js' -import { AutoDialer } from './connection-manager/dialer/auto-dialer.js' import { Initializable, Components, isInitializable } from '@libp2p/components' import type { PeerId } from '@libp2p/interface-peer-id' import type { Connection } from '@libp2p/interface-connection' @@ -238,20 +237,6 @@ export class Libp2pNode extends EventEmitter implements Libp2p { ...init.ping })) - const autoDialer = this.configureComponent(new AutoDialer(this.components, { - enabled: init.connectionManager.autoDial !== false, - minConnections: init.connectionManager.minConnections, - dialTimeout: init.connectionManager.dialTimeout ?? 30000 - })) - - this.addEventListener('peer:discovery', evt => { - if (!this.isStarted()) { - return - } - - autoDialer.handle(evt) - }) - // Discovery modules for (const service of init.peerDiscovery ?? []) { this.configureComponent(service) From c185ef549f599510f258d5d67883f7062c1c944b Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 6 Oct 2022 15:07:05 +0100 Subject: [PATCH 435/447] feat: deny incoming connections and add allow/deny lists (#1398) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When we reach our connection limit, deny incoming connections before the peer ids are exchanged instead of accepting them and trying to apply the limits after the exchange. Adds a allow and deny lists of multiaddr prefixes to ensure we can always accept connections from a given network host even when we have reached out connection limit. Outgoing behaviour remains the same, that is, you can exceed the limit while opening new connections and the connection manager will try to close 'low value' connections after the new connection has been made. Co-authored-by: Marin Petrunić --- .aegir.js | 3 + package.json | 1 + src/connection-manager/index.ts | 81 +++++++++++++-- src/errors.ts | 3 +- src/upgrader.ts | 7 ++ test/connection-manager/index.spec.ts | 139 +++++++++++++++++++++++++- test/upgrading/upgrader.spec.ts | 8 +- 7 files changed, 225 insertions(+), 17 deletions(-) diff --git a/.aegir.js b/.aegir.js index c0a745f8e1..c6f77fcacd 100644 --- a/.aegir.js +++ b/.aegir.js @@ -20,6 +20,9 @@ export default { // Use the last peer const peerId = await createFromJSON(Peers[Peers.length - 1]) const libp2p = await createLibp2p({ + connectionManager: { + inboundConnectionThreshold: Infinity + }, addresses: { listen: [MULTIADDRS_WEBSOCKETS[0]] }, diff --git a/package.json b/package.json index 0cf2338843..4ce25eeb36 100644 --- a/package.json +++ b/package.json @@ -158,6 +158,7 @@ "p-settle": "^5.0.0", "private-ip": "^2.3.3", "protons-runtime": "^3.0.1", + "rate-limiter-flexible": "^2.3.11", "retimer": "^3.0.0", "sanitize-filename": "^1.6.3", "set-delayed-interval": "^1.0.0", diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index 1b9b7e7ce6..ca11013f2f 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -15,10 +15,11 @@ import type { ConnectionManager } from '@libp2p/interface-connection-manager' import { Components, Initializable } from '@libp2p/components' import * as STATUS from '@libp2p/interface-connection/status' import type { AddressSorter } from '@libp2p/interface-peer-store' -import type { Resolver } from '@multiformats/multiaddr' +import { multiaddr, Multiaddr, Resolver } from '@multiformats/multiaddr' import { PeerMap } from '@libp2p/peer-collections' import { TimeoutController } from 'timeout-abort-controller' import { KEEP_ALIVE } from '@libp2p/interface-peer-store/tags' +import { RateLimiterMemory } from 'rate-limiter-flexible' const log = logger('libp2p:connection-manager') @@ -31,7 +32,8 @@ const defaultOptions: Partial = { maxEventLoopDelay: Infinity, pollInterval: 2000, autoDialInterval: 10000, - movingAverageInterval: 60000 + movingAverageInterval: 60000, + inboundConnectionThreshold: 5 } const METRICS_SYSTEM = 'libp2p' @@ -132,6 +134,24 @@ export interface ConnectionManagerInit { * tagged with KEEP_ALIVE up to this timeout in ms. (default: 60000) */ startupReconnectTimeout?: number + + /** + * A list of multiaddrs that will always be allowed (except if they are in the + * deny list) to open connections to this node even if we've reached maxConnections + */ + allow?: string[] + + /** + * A list of multiaddrs that will never be allowed to open connections to + * this node under any circumstances + */ + deny?: string[] + + /** + * If more than this many connections are opened per second by a single + * host, reject subsequent connections + */ + inboundConnectionThreshold?: number } export interface ConnectionManagerEvents { @@ -152,6 +172,9 @@ export class DefaultConnectionManager extends EventEmitter multiaddr(ma)) + this.deny = (init.deny ?? []).map(ma => multiaddr(ma)) + + this.inboundConnectionRateLimiter = new RateLimiterMemory({ + points: this.opts.inboundConnectionThreshold, + duration: 1 + }) } init (components: Components): void { @@ -598,7 +629,7 @@ export class DefaultConnectionManager extends EventEmitter limit) { log('%s: limit exceeded: %p, %d/%d, pruning %d connection(s)', this.components.getPeerId(), name, value, limit, toPrune) - await this._maybePruneConnections(toPrune) + await this._pruneConnections(toPrune) } } @@ -606,13 +637,8 @@ export class DefaultConnectionManager extends EventEmitter() // work out peer values @@ -677,6 +703,41 @@ export class DefaultConnectionManager extends EventEmitter { - return true + // check deny list + const denyConnection = this.deny.some(ma => { + return maConn.remoteAddr.toString().startsWith(ma.toString()) + }) + + if (denyConnection) { + log('connection from %s refused - connection remote address was in deny list', maConn.remoteAddr) + return false + } + + // check allow list + const allowConnection = this.allow.some(ma => { + return maConn.remoteAddr.toString().startsWith(ma.toString()) + }) + + if (allowConnection) { + return true + } + + if (maConn.remoteAddr.isThinWaistAddress()) { + const host = maConn.remoteAddr.nodeAddress().address + + try { + await this.inboundConnectionRateLimiter.consume(host, 1) + } catch { + log('connection from %s refused - inboundConnectionThreshold exceeded by host %s', host, maConn.remoteAddr) + return false + } + } + + if (this.getConnections().length < this.opts.maxConnections) { + return true + } + + log('connection from %s refused - maxConnections exceeded', maConn.remoteAddr) + return false } } diff --git a/src/errors.ts b/src/errors.ts index 50e2441d1a..99ecc45600 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -73,5 +73,6 @@ export enum codes { ERR_ALREADY_SUCCEEDED = 'ERR_ALREADY_SUCCEEDED', ERR_NO_HANDLER_FOR_PROTOCOL = 'ERR_NO_HANDLER_FOR_PROTOCOL', ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS = 'ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS', - ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS = 'ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS' + ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS = 'ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS', + ERR_CONNECTION_DENIED = 'ERR_CONNECTION_DENIED' } diff --git a/src/upgrader.ts b/src/upgrader.ts index 6b12304721..4057bd3117 100644 --- a/src/upgrader.ts +++ b/src/upgrader.ts @@ -123,6 +123,13 @@ export class DefaultUpgrader extends EventEmitter implements Upg * Upgrades an inbound connection */ async upgradeInbound (maConn: MultiaddrConnection): Promise { + const accept = await this.components.getConnectionManager().acceptIncomingConnection(maConn) + + if (!accept) { + await maConn.close() + throw errCode(new Error('connection denied'), codes.ERR_CONNECTION_DENIED) + } + let encryptedConn let remotePeer let upgradedConn: Duplex diff --git a/test/connection-manager/index.spec.ts b/test/connection-manager/index.spec.ts index e1ad443643..79a557f3d3 100644 --- a/test/connection-manager/index.spec.ts +++ b/test/connection-manager/index.spec.ts @@ -5,12 +5,24 @@ import sinon from 'sinon' import { createNode } from '../utils/creators/peer.js' import { createBaseOptions } from '../utils/base-options.browser.js' import type { Libp2pNode } from '../../src/libp2p.js' -import type { DefaultConnectionManager } from '../../src/connection-manager/index.js' +import { DefaultConnectionManager } from '../../src/connection-manager/index.js' import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-mocks' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { CustomEvent } from '@libp2p/interfaces/events' import { KEEP_ALIVE } from '@libp2p/interface-peer-store/tags' import pWaitFor from 'p-wait-for' +import { multiaddr } from '@multiformats/multiaddr' +import { Components } from '@libp2p/components' +import { stubInterface } from 'ts-sinon' +import type { Dialer } from '@libp2p/interface-connection-manager' +import type { Connection } from '@libp2p/interface-connection' + +const defaultOptions = { + maxConnections: 10, + minConnections: 1, + autoDialInterval: Infinity, + inboundUpgradeTimeout: 10000 +} describe('Connection Manager', () => { let libp2p: Libp2pNode @@ -68,7 +80,7 @@ describe('Connection Manager', () => { await libp2p.start() const connectionManager = libp2p.components.getConnectionManager() as DefaultConnectionManager - const connectionManagerMaybeDisconnectOneSpy = sinon.spy(connectionManager, '_maybePruneConnections') + const connectionManagerMaybeDisconnectOneSpy = sinon.spy(connectionManager, '_pruneConnections') const spies = new Map>>() // Add 1 connection too many @@ -118,7 +130,7 @@ describe('Connection Manager', () => { await libp2p.start() const connectionManager = libp2p.components.getConnectionManager() as DefaultConnectionManager - const connectionManagerMaybeDisconnectOneSpy = sinon.spy(connectionManager, '_maybePruneConnections') + const connectionManagerMaybeDisconnectOneSpy = sinon.spy(connectionManager, '_pruneConnections') // Add 1 too many connections const spy = sinon.spy() @@ -171,4 +183,125 @@ describe('Connection Manager', () => { expect(connectionManagerOpenConnectionSpy.called).to.be.true('Did not attempt to connect to important peer') expect(connectionManagerOpenConnectionSpy.getCall(0).args[0].toString()).to.equal(peerId.toString(), 'Attempted to connect to the wrong peer') }) + + it('should deny connections from denylist multiaddrs', async () => { + const remoteAddr = multiaddr('/ip4/83.13.55.32/tcp/59283') + const connectionManager = new DefaultConnectionManager({ + ...defaultOptions, + deny: [ + '/ip4/83.13.55.32' + ] + }) + + const remotePeer = await createEd25519PeerId() + const maConn = mockMultiaddrConnection({ + remoteAddr, + source: [], + sink: async () => {} + }, remotePeer) + + await expect(connectionManager.acceptIncomingConnection(maConn)) + .to.eventually.be.false() + }) + + it('should deny connections when maxConnections is exceeded', async () => { + const connectionManager = new DefaultConnectionManager({ + ...defaultOptions, + maxConnections: 1 + }) + + const dialer = stubInterface() + dialer.dial.resolves(stubInterface()) + + const components = new Components({ + dialer + }) + + // set mocks + connectionManager.init(components) + + // max out the connection limit + await connectionManager.openConnection(await createEd25519PeerId()) + expect(connectionManager.getConnections()).to.have.lengthOf(1) + + // an inbound connection is opened + const remotePeer = await createEd25519PeerId() + const maConn = mockMultiaddrConnection({ + source: [], + sink: async () => {} + }, remotePeer) + + await expect(connectionManager.acceptIncomingConnection(maConn)) + .to.eventually.be.false() + }) + + it('should deny connections from peers that connect too frequently', async () => { + const connectionManager = new DefaultConnectionManager({ + ...defaultOptions, + inboundConnectionThreshold: 1 + }) + + const dialer = stubInterface() + dialer.dial.resolves(stubInterface()) + + const components = new Components({ + dialer + }) + + // set mocks + connectionManager.init(components) + + // an inbound connection is opened + const remotePeer = await createEd25519PeerId() + const maConn = mockMultiaddrConnection({ + source: [], + sink: async () => {}, + // has to be thin waist, which it will be since we've not done the peer id handshake + // yet in the code being exercised by this test + remoteAddr: multiaddr('/ip4/34.4.63.125/tcp/4001') + }, remotePeer) + + await expect(connectionManager.acceptIncomingConnection(maConn)) + .to.eventually.be.true() + + // connect again within a second + await expect(connectionManager.acceptIncomingConnection(maConn)) + .to.eventually.be.false() + }) + + it('should allow connections from allowlist multiaddrs', async () => { + const remoteAddr = multiaddr('/ip4/83.13.55.32/tcp/59283') + const connectionManager = new DefaultConnectionManager({ + ...defaultOptions, + maxConnections: 1, + allow: [ + '/ip4/83.13.55.32' + ] + }) + + const dialer = stubInterface() + dialer.dial.resolves(stubInterface()) + + const components = new Components({ + dialer + }) + + // set mocks + connectionManager.init(components) + + // max out the connection limit + await connectionManager.openConnection(await createEd25519PeerId()) + expect(connectionManager.getConnections()).to.have.lengthOf(1) + + // an inbound connection is opened from an address in the allow list + const remotePeer = await createEd25519PeerId() + const maConn = mockMultiaddrConnection({ + remoteAddr, + source: [], + sink: async () => {} + }, remotePeer) + + await expect(connectionManager.acceptIncomingConnection(maConn)) + .to.eventually.be.true() + }) }) diff --git a/test/upgrading/upgrader.spec.ts b/test/upgrading/upgrader.spec.ts index c01c050c9e..1a0751267e 100644 --- a/test/upgrading/upgrader.spec.ts +++ b/test/upgrading/upgrader.spec.ts @@ -13,7 +13,7 @@ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import swarmKey from '../fixtures/swarm.key.js' import { DefaultUpgrader } from '../../src/upgrader.js' import { codes } from '../../src/errors.js' -import { mockConnectionGater, mockMultiaddrConnPair, mockRegistrar, mockStream } from '@libp2p/interface-mocks' +import { mockConnectionGater, mockConnectionManager, mockMultiaddrConnPair, mockRegistrar, mockStream } from '@libp2p/interface-mocks' import Peers from '../fixtures/peers.js' import type { Upgrader } from '@libp2p/interface-transport' import type { PeerId } from '@libp2p/interface-peer-id' @@ -61,7 +61,8 @@ describe('Upgrader', () => { connectionGater: mockConnectionGater(), registrar: mockRegistrar(), peerStore: new PersistentPeerStore(), - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + connectionManager: mockConnectionManager() }) localMuxerFactory = new Mplex() localUpgrader = new DefaultUpgrader(localComponents, { @@ -79,7 +80,8 @@ describe('Upgrader', () => { connectionGater: mockConnectionGater(), registrar: mockRegistrar(), peerStore: new PersistentPeerStore(), - datastore: new MemoryDatastore() + datastore: new MemoryDatastore(), + connectionManager: mockConnectionManager() }) remoteUpgrader = new DefaultUpgrader(remoteComponents, { connectionEncryption: [ From 6615efa683f55425f90c70815467ec5ddfed1fcb Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Fri, 7 Oct 2022 15:38:48 -0700 Subject: [PATCH 436/447] feat: allow skipping encryption and custom muxer factory in upgrader (#1411) Updates the `DefaultUpgrader` to use the new `UpgraderOptions` which allows skipping encryption and/or adding a custom muxer factory for transports which inherently support encryption and muxing. Also updates all deps. Co-authored-by: Chinmay Kousik Co-authored-by: achingbrain --- examples/delegated-routing/package.json | 14 +++--- examples/libp2p-in-the-browser/package.json | 8 +-- examples/package.json | 2 +- examples/webrtc-direct/package.json | 8 +-- package.json | 36 ++++++------- src/address-manager/index.ts | 17 +++++++ src/upgrader.ts | 56 +++++++++++++-------- 7 files changed, 85 insertions(+), 56 deletions(-) diff --git a/examples/delegated-routing/package.json b/examples/delegated-routing/package.json index fc3cae70c8..a136e8433a 100644 --- a/examples/delegated-routing/package.json +++ b/examples/delegated-routing/package.json @@ -3,15 +3,15 @@ "version": "0.1.0", "private": true, "dependencies": { - "@chainsafe/libp2p-noise": "^8.0.2", + "@chainsafe/libp2p-noise": "^9.0.0", "ipfs-core": "^0.15.4", "libp2p": "../../", - "@libp2p/delegated-content-routing": "^2.0.1", - "@libp2p/delegated-peer-routing": "^2.0.1", - "@libp2p/kad-dht": "^3.0.0", - "@libp2p/mplex": "^5.2.3", - "@libp2p/webrtc-star": "^3.0.3", - "@libp2p/websockets": "^3.0.4", + "@libp2p/delegated-content-routing": "^2.0.2", + "@libp2p/delegated-peer-routing": "^2.0.2", + "@libp2p/kad-dht": "^4.0.0", + "@libp2p/mplex": "^6.0.2", + "@libp2p/webrtc-star": "^4.0.1", + "@libp2p/websockets": "^4.0.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "5.0.0" diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json index 2abd84345f..3e24693520 100644 --- a/examples/libp2p-in-the-browser/package.json +++ b/examples/libp2p-in-the-browser/package.json @@ -9,10 +9,10 @@ }, "license": "ISC", "dependencies": { - "@chainsafe/libp2p-noise": "^8.0.2", - "@libp2p/bootstrap": "^2.0.1", - "@libp2p/mplex": "^5.2.3", - "@libp2p/webrtc-star": "^3.0.3", + "@chainsafe/libp2p-noise": "^9.0.0", + "@libp2p/bootstrap": "^4.0.0", + "@libp2p/mplex": "^6.0.2", + "@libp2p/webrtc-star": "^4.0.1", "@libp2p/websockets": "^3.0.4", "libp2p": "../../" }, diff --git a/examples/package.json b/examples/package.json index c3baa68c9d..a43e2a9df1 100644 --- a/examples/package.json +++ b/examples/package.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@libp2p/pubsub-peer-discovery": "^6.0.2", - "@libp2p/floodsub": "^3.0.3", + "@libp2p/floodsub": "^4.0.0", "@nodeutils/defaults-deep": "^1.1.0", "execa": "^6.1.0", "fs-extra": "^10.1.0", diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index 37c08976b2..b42ffa5d4a 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -9,10 +9,10 @@ }, "license": "ISC", "dependencies": { - "@libp2p/webrtc-direct": "^2.0.0", - "@chainsafe/libp2p-noise": "^8.0.2", - "@libp2p/bootstrap": "^2.0.1", - "@libp2p/mplex": "^5.2.3", + "@libp2p/webrtc-direct": "^2.0.3", + "@chainsafe/libp2p-noise": "^9.0.0", + "@libp2p/bootstrap": "^4.0.0", + "@libp2p/mplex": "^6.0.2", "libp2p": "../../", "wrtc": "^0.4.7" }, diff --git a/package.json b/package.json index 4ce25eeb36..2ad2304a60 100644 --- a/package.json +++ b/package.json @@ -98,10 +98,10 @@ }, "dependencies": { "@achingbrain/nat-port-mapper": "^1.0.3", - "@libp2p/components": "^2.1.0", + "@libp2p/components": "^3.0.0", "@libp2p/connection": "^4.0.2", "@libp2p/crypto": "^1.0.4", - "@libp2p/interface-address-manager": "^1.0.3", + "@libp2p/interface-address-manager": "^2.0.0", "@libp2p/interface-connection": "^3.0.2", "@libp2p/interface-connection-encrypter": "^3.0.1", "@libp2p/interface-connection-manager": "^1.1.1", @@ -115,8 +115,8 @@ "@libp2p/interface-peer-store": "^1.2.2", "@libp2p/interface-pubsub": "^2.1.0", "@libp2p/interface-registrar": "^2.0.3", - "@libp2p/interface-stream-muxer": "^2.0.2", - "@libp2p/interface-transport": "^1.0.4", + "@libp2p/interface-stream-muxer": "^3.0.0", + "@libp2p/interface-transport": "^2.0.0", "@libp2p/interfaces": "^3.0.3", "@libp2p/logger": "^2.0.1", "@libp2p/multistream-select": "^3.0.0", @@ -124,7 +124,7 @@ "@libp2p/peer-id": "^1.1.15", "@libp2p/peer-id-factory": "^1.0.18", "@libp2p/peer-record": "^4.0.3", - "@libp2p/peer-store": "^3.1.5", + "@libp2p/peer-store": "^4.0.0", "@libp2p/tracked-map": "^2.0.1", "@libp2p/utils": "^3.0.2", "@multiformats/mafmt": "^11.0.2", @@ -169,24 +169,24 @@ "xsalsa20": "^1.1.0" }, "devDependencies": { - "@chainsafe/libp2p-noise": "^8.0.2", - "@chainsafe/libp2p-yamux": "^1.0.0", - "@libp2p/bootstrap": "^3.0.0", + "@chainsafe/libp2p-noise": "^9.0.0", + "@chainsafe/libp2p-yamux": "^2.0.0", + "@libp2p/bootstrap": "^4.0.0", "@libp2p/daemon-client": "^3.0.1", "@libp2p/daemon-server": "^3.0.1", - "@libp2p/floodsub": "^3.0.0", + "@libp2p/floodsub": "^4.0.0", "@libp2p/interface-compliance-tests": "^3.0.2", "@libp2p/interface-connection-encrypter-compliance-tests": "^2.0.2", - "@libp2p/interface-mocks": "^5.1.0", + "@libp2p/interface-mocks": "^6.0.1", "@libp2p/interop": "^3.0.1", - "@libp2p/kad-dht": "^3.0.5", - "@libp2p/mdns": "^3.0.1", - "@libp2p/mplex": "^5.2.3", - "@libp2p/pubsub": "^3.1.3", - "@libp2p/tcp": "^3.1.1", + "@libp2p/kad-dht": "^4.0.0", + "@libp2p/mdns": "^4.0.0", + "@libp2p/mplex": "^6.0.2", + "@libp2p/pubsub": "^4.0.0", + "@libp2p/tcp": "^4.0.0", "@libp2p/topology": "^3.0.1", - "@libp2p/webrtc-star": "^3.0.3", - "@libp2p/websockets": "^3.0.4", + "@libp2p/webrtc-star": "^4.0.1", + "@libp2p/websockets": "^4.0.0", "@types/node-forge": "^1.0.0", "@types/p-fifo": "^1.0.0", "@types/varint": "^6.0.0", @@ -211,4 +211,4 @@ "browser": { "nat-api": false } -} +} \ No newline at end of file diff --git a/src/address-manager/index.ts b/src/address-manager/index.ts index b6726994c3..907f30a8c5 100644 --- a/src/address-manager/index.ts +++ b/src/address-manager/index.ts @@ -76,6 +76,23 @@ export class DefaultAddressManager extends EventEmitter { return Array.from(this.observed).map((a) => multiaddr(a)) } + /** + * Add peer observed addresses + * Signal that we have confidence an observed multiaddr is publicly dialable - + * this will make it appear in the output of getAddresses() + */ + confirmObservedAddr (addr: Multiaddr): void { + + } + + /** + * Signal that we do not have confidence an observed multiaddr is publicly dialable - + * this will remove it from the output of getObservedAddrs() + */ + removeObservedAddr (addr: Multiaddr): void { + + } + /** * Add peer observed addresses */ diff --git a/src/upgrader.ts b/src/upgrader.ts index 4057bd3117..6f2ba74c84 100644 --- a/src/upgrader.ts +++ b/src/upgrader.ts @@ -12,7 +12,7 @@ import type { MultiaddrConnection, Connection, Stream } from '@libp2p/interface- import type { ConnectionEncrypter, SecuredConnection } from '@libp2p/interface-connection-encrypter' import type { StreamMuxer, StreamMuxerFactory } from '@libp2p/interface-stream-muxer' import type { PeerId } from '@libp2p/interface-peer-id' -import type { Upgrader, UpgraderEvents } from '@libp2p/interface-transport' +import type { Upgrader, UpgraderEvents, UpgraderOptions } from '@libp2p/interface-transport' import type { Duplex } from 'it-stream-types' import { Components, isInitializable } from '@libp2p/components' import type { AbortOptions } from '@libp2p/interfaces' @@ -235,7 +235,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg /** * Upgrades an outbound connection */ - async upgradeOutbound (maConn: MultiaddrConnection): Promise { + async upgradeOutbound (maConn: MultiaddrConnection, opts?: UpgraderOptions): Promise { const idStr = maConn.remoteAddr.getPeerId() if (idStr == null) { throw errCode(new Error('outbound connection must have a peer id'), codes.ERR_INVALID_MULTIADDR) @@ -265,39 +265,51 @@ export class DefaultUpgrader extends EventEmitter implements Upg log('Starting the outbound connection upgrade') + // If the transport natively supports encryption, skip connection + // protector and encryption + // Protect let protectedConn = maConn - const protector = this.components.getConnectionProtector() + if (opts?.skipProtection !== true) { + const protector = this.components.getConnectionProtector() - if (protector != null) { - protectedConn = await protector.protect(maConn) + if (protector != null) { + protectedConn = await protector.protect(maConn) + } } try { // Encrypt the connection - ({ - conn: encryptedConn, - remotePeer, - protocol: cryptoProtocol - } = await this._encryptOutbound(protectedConn, remotePeerId)) + encryptedConn = protectedConn + if (opts?.skipEncryption !== true) { + ({ + conn: encryptedConn, + remotePeer, + protocol: cryptoProtocol + } = await this._encryptOutbound(protectedConn, remotePeerId)) - if (await this.components.getConnectionGater().denyOutboundEncryptedConnection(remotePeer, { - ...protectedConn, - ...encryptedConn - })) { - throw errCode(new Error('The multiaddr connection is blocked by gater.acceptEncryptedConnection'), codes.ERR_CONNECTION_INTERCEPTED) + if (await this.components.getConnectionGater().denyOutboundEncryptedConnection(remotePeer, { + ...protectedConn, + ...encryptedConn + })) { + throw errCode(new Error('The multiaddr connection is blocked by gater.acceptEncryptedConnection'), codes.ERR_CONNECTION_INTERCEPTED) + } + } else { + cryptoProtocol = 'native' + remotePeer = remotePeerId } - // Multiplex the connection - if (this.muxers.size > 0) { + upgradedConn = encryptedConn + if (opts?.muxerFactory != null) { + muxerFactory = opts.muxerFactory + } else if (this.muxers.size > 0) { + // Multiplex the connection const multiplexed = await this._multiplexOutbound({ ...protectedConn, ...encryptedConn }, this.muxers) muxerFactory = multiplexed.muxerFactory upgradedConn = multiplexed.stream - } else { - upgradedConn = encryptedConn } } catch (err: any) { log.error('Failed to upgrade outbound connection', err) @@ -418,7 +430,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg } log('%s: starting new stream on %s', direction, protocols) - const muxedStream = muxer.newStream() + const muxedStream = await muxer.newStream() const metrics = this.components.getMetrics() let controller: TimeoutController | undefined @@ -616,7 +628,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg * Selects one of the given muxers via multistream-select. That * muxer will be used for all future streams on the connection. */ - async _multiplexOutbound (connection: MultiaddrConnection, muxers: Map): Promise<{ stream: Duplex, muxerFactory?: StreamMuxerFactory}> { + async _multiplexOutbound (connection: MultiaddrConnection, muxers: Map): Promise<{stream: Duplex, muxerFactory?: StreamMuxerFactory}> { const protocols = Array.from(muxers.keys()) log('outbound selecting muxer %s', protocols) try { @@ -636,7 +648,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg * Registers support for one of the given muxers via multistream-select. The * selected muxer will be used for all future streams on the connection. */ - async _multiplexInbound (connection: MultiaddrConnection, muxers: Map): Promise<{ stream: Duplex, muxerFactory?: StreamMuxerFactory}> { + async _multiplexInbound (connection: MultiaddrConnection, muxers: Map): Promise<{stream: Duplex, muxerFactory?: StreamMuxerFactory}> { const protocols = Array.from(muxers.keys()) log('inbound handling muxers %s', protocols) try { From 25d935e0b1a93ab70ceea1b0d8083dc2b56e4ab3 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 11 Oct 2022 12:16:20 +0100 Subject: [PATCH 437/447] docs: add limit configuration documentation (#1421) To help people better configure their libp2p nodes to defend against malicious peers, document all the limit settings and protections in one place. --- README.md | 5 + doc/CONFIGURATION.md | 53 ++++----- doc/LIMITS.md | 251 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 284 insertions(+), 25 deletions(-) create mode 100644 doc/LIMITS.md diff --git a/README.md b/README.md index ffc66e2bec..bfffa25a74 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ If you are looking for the documentation of the latest release, you can view the - [Install](#install) - [Usage](#usage) - [Configuration](#configuration) + - [Limits](#limits) - [API](#api) - [Getting started](#getting-started) - [Tutorials and Examples](#tutorials-and-examples) @@ -85,6 +86,10 @@ npm install libp2p For all the information on how you can configure libp2p see [CONFIGURATION.md](./doc/CONFIGURATION.md). +### Limits + +For help configuring your node to resist malicious network peers, see [LIMITS.md](./doc/LIMITS.md) + ### API The specification is available on [API.md](./doc/API.md). diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index fc3cab847c..b7e77f0fb8 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -1,4 +1,4 @@ -# +# Configuration - [Overview](#overview) - [Modules](#modules) @@ -35,6 +35,7 @@ - [UPnP and NAT-PMP](#upnp-and-nat-pmp) - [Configuring protocol name](#configuring-protocol-name) - [Configuration examples](#configuration-examples) +- [Limits](#limits) ## Overview @@ -67,13 +68,11 @@ Bear in mind that a **transport** and **connection encryption** module are **req Some available transports are: -- [libp2p/js-libp2p-tcp](https://github.com/libp2p/js-libp2p-tcp) -- [libp2p/js-libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star) -- [libp2p/js-libp2p-webrtc-direct](https://github.com/libp2p/js-libp2p-webrtc-direct) -- [libp2p/js-libp2p-websockets](https://github.com/libp2p/js-libp2p-websockets) -- [libp2p/js-libp2p-utp](https://github.com/libp2p/js-libp2p-utp) (Work in Progress) - -You should take into consideration that `js-libp2p-tcp` and `js-libp2p-utp` are not available in a **browser** environment. +- [@libp2p/tcp](https://github.com/libp2p/js-libp2p-tcp) (not available in browsers) +- [@libp2p/webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star) +- [@libp2p/webrtc-direct](https://github.com/libp2p/js-libp2p-webrtc-direct) +- [@libp2p/websockets](https://github.com/libp2p/js-libp2p-websockets) +- [@libp2p/webtransport](https://github.com/libp2p/js-libp2p-webtransport) (Work in Progress) If none of the available transports fulfills your needs, you can create a libp2p compatible transport. A libp2p transport just needs to be compliant with the [Transport Interface](https://github.com/libp2p/js-interfaces/tree/master/src/transport). @@ -88,7 +87,8 @@ If you want to know more about libp2p transports, you should read the following Some available stream multiplexers are: -- [libp2p/js-libp2p-mplex](https://github.com/libp2p/js-libp2p-mplex) +- [@libp2p/mplex](https://github.com/libp2p/js-libp2p-mplex) +- [@chainsafe/libp2p-yamux](https://github.com/chainsafe/js-libp2p-yamux) If none of the available stream multiplexers fulfills your needs, you can create a libp2p compatible stream multiplexer. A libp2p multiplexer just needs to be compliant with the [Stream Muxer Interface](https://github.com/libp2p/js-interfaces/tree/master/src/stream-muxer). @@ -104,8 +104,8 @@ 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) ⚠️ [DEPRECATED](https://blog.ipfs.io/2020-08-07-deprecating-secio) +- [@chainsafe/libp2p-noise](https://github.com/chainsafe/js-libp2p-noise) +- [Plaintext](https://github.com/libp2p/js-libp2p/blob/master/src/insecure/index.ts) (Not for production use) 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). @@ -120,11 +120,11 @@ If you want to know more about libp2p connection encryption, you should read the Some available peer discovery modules are: -- [js-libp2p-mdns](https://github.com/libp2p/js-libp2p-mdns) -- [js-libp2p-bootstrap](https://github.com/libp2p/js-libp2p-bootstrap) -- [js-libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht) -- [js-libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star) -- [discv5](https://github.com/chainsafe/discv5) +- [@libp2p/mdns](https://github.com/libp2p/js-libp2p-mdns) +- [@libp2p/bootstrap](https://github.com/libp2p/js-libp2p-bootstrap) +- [@libp2p/kad-dht](https://github.com/libp2p/js-libp2p-kad-dht) +- [@libp2p/webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star) +- [@chainsafe/discv5](https://github.com/chainsafe/discv5) **Note**: `peer-discovery` services within transports (such as `js-libp2p-webrtc-star`) are automatically gathered from the `transport`, via it's `discovery` property. As such, they do not need to be added in the discovery modules. However, these transports can also be configured and disabled as the other ones. @@ -140,8 +140,8 @@ If you want to know more about libp2p peer discovery, you should read the follow Some available content routing modules are: -- [js-libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht) -- [js-libp2p-delegated-content-routing](https://github.com/libp2p/js-libp2p-delegated-content-routing) +- [@libp2p/kad-dht](https://github.com/libp2p/js-libp2p-kad-dht) +- [@libp2p/delegated-content-routing](https://github.com/libp2p/js-libp2p-delegated-content-routing) If none of the available content routing protocols fulfills your needs, you can create a libp2p compatible one. A libp2p content routing protocol just needs to be compliant with the [Content Routing Interface](https://github.com/libp2p/js-interfaces/tree/master/src/content-routing). **(WIP: This module is not yet implemented)** @@ -155,8 +155,8 @@ If you want to know more about libp2p content routing, you should read the follo Some available peer routing modules are: -- [js-libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht) -- [js-libp2p-delegated-peer-routing](https://github.com/libp2p/js-libp2p-delegated-peer-routing) +- [@libp2p/kad-dht](https://github.com/libp2p/js-libp2p-kad-dht) +- [@libp2p/delegated-peer-routing](https://github.com/libp2p/js-libp2p-delegated-peer-routing) If none of the available peer routing protocols fulfills your needs, you can create a libp2p compatible one. A libp2p peer routing protocol just needs to be compliant with the [Peer Routing Interface](https://github.com/libp2p/js-interfaces/tree/master/src/peer-routing). **(WIP: This module is not yet implemented)** @@ -168,7 +168,7 @@ If you want to know more about libp2p peer routing, you should read the followin > A DHT can provide content and peer routing capabilities in a p2p system, as well as peer discovery capabilities. -The DHT implementation currently available is [libp2p/js-libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht). This implementation is largely based on the Kademlia whitepaper, augmented with notions from S/Kademlia, Coral and mainlineDHT. +The DHT implementation currently available is [@libp2p/kad-dht](https://github.com/libp2p/js-libp2p-kad-dht). This implementation is largely based on the Kademlia whitepaper, augmented with notions from S/Kademlia, Coral and mainlineDHT. If this DHT implementation does not fulfill your needs and you want to create or use your own implementation, please get in touch with us through a github issue. We plan to work on improving the ability to bring your own DHT in a future release. @@ -183,10 +183,10 @@ If you want to know more about libp2p DHT, you should read the following content Some available pubsub routers are: -- [libp2p/js-libp2p-floodsub](https://github.com/libp2p/js-libp2p-floodsub) -- [ChainSafe/js-libp2p-gossipsub](https://github.com/ChainSafe/js-libp2p-gossipsub) +- [@chainsafe/libp2p-gossipsub](https://github.com/ChainSafe/js-libp2p-gossipsub) +- [@libp2p/floodsub](https://github.com/libp2p/js-libp2p-floodsub) (Not for production use) -If none of the available pubsub routers fulfills your needs, you can create a libp2p compatible one. A libp2p pubsub router just needs to be created on top of [libp2p/js-libp2p-pubsub](https://github.com/libp2p/js-libp2p-pubsub), which ensures `js-libp2p` API expectations. +If none of the available pubsub routers fulfills your needs, you can create a libp2p compatible one. A libp2p pubsub router just needs to be created on top of [@libp2p/pubsub](https://github.com/libp2p/js-libp2p-pubsub), which ensures `js-libp2p` API expectations. If you want to know more about libp2p pubsub, you should read the following content: @@ -380,7 +380,6 @@ import { create as ipfsHttpClient } from 'ipfs-http-client' import { DelegatedPeerRouting } from '@libp2p/delegated-peer-routing' import { DelegatedContentRouting} from '@libp2p/delegated-content-routing' - // create a peerId const peerId = await PeerId.create() @@ -909,3 +908,7 @@ As libp2p is designed to be a modular networking library, its usage will vary ba If you have developed a project using `js-libp2p`, please consider submitting your configuration to this list so that it can be found easily by other users. The [examples](../examples) are also a good source of help for finding a configuration that suits your needs. + +## Limits + +Configuring the various limits of your node is important to protect it when it is part of hostile of adversarial networks. See [LIMITS.md](./LIMITS.md) for a full breakdown of the various built in protections and safeguards. diff --git a/doc/LIMITS.md b/doc/LIMITS.md new file mode 100644 index 0000000000..838ffcb206 --- /dev/null +++ b/doc/LIMITS.md @@ -0,0 +1,251 @@ +# Limits + +In order to prevent excessive resource consumption by a libp2p node it's important to understand limits are applied and how to tune them to the needs of your application. + +## Table of contents + +- [Connection limits](#connection-limits) +- [Inbound connection threshold](#inbound-connection-threshold) +- [Data transfer and Event Loop limits](#data-transfer-and-event-loop-limits) +- [Stream limits](#stream-limits) + - [Mplex](#mplex) + - [Yamux](#yamux) +- [Closing connections](#closing-connections) +- [Transport specific limits](#transport-specific-limits) + - [TCP](#tcp) +- [Allow/deny lists](#allowdeny-lists) + +## Connection limits + +It's possible to limit the amount of incoming and outgoing connections a node is able to make. When this limit is reached and an attempt to open a new connection is made, existing connections may be closed to make room for the new connection. + +```js +const node = await createLibp2pNode({ + connectionManager: { + /** + * The total number of connections allowed to be open at one time + */ + maxConnections: 200, + + /** + * If the number of open connections goes below this number, the node + * will try to connect to nearby peers from the peer store + */ + minConnections: 20 + } +}) +``` + +## Inbound connection threshold + +To prevent individual peers from opening multiple connections to a node, an `inboundConnectionThreshold` is configurable. This is the number of connections per second an individual remote host can open to a node, once this threshold is crossed all further connections opened by that host will be rejected. + + +```js +const node = await createLibp2pNode({ + connectionManager: { + /** + * A remote peer may attempt to open up to this many connections per second, + * any more than that will be automatically rejected + */ + inboundConnectionThreshold: 5 + } +}) +``` + +## Data transfer and Event Loop limits + +If metrics are enabled the node will track the amount of data being sent to and from the network. If the amount sent is over the threshold connections will be trimmed to free up resources. The default amount is `Ininity` so this must be explicitly enabled. + +Connections may also be trimmed if [event loop](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick) latency exceeds the configured limit. + +```js +const node = await createLibp2pNode({ + metrics: { + enabled: true + }, + connectionManager: { + /** + * If the node transfers more than this amount of data in bytes/second + * low value connections may be closed + */ + maxData: 1024 * 1024, + + /** + * If the node sends more than this amount of data in bytes/second + * low value connections may be closed + */ + maxSentData: 1024 * 1024 + + /** + * If the node receives more than this amount of data in bytes/second + * low value connections may be closed + */ + maxReceivedData: 1024 * 1024, + + /** + * If the event loop takes longer than this many ms to run, low value + * connections may be closed + */ + maxEventLoopDelay: 1000 + } +}) +``` + +## Stream limits + +libp2p stream multiplexers impose limits on the amount of streams that can be opened per connection, and also the amount of data that will be buffered for a given stream. The data should be consumed as fast as possible - if a stream's input buffer exceeds the limits set the stream will be reset. + +These settings are done on a per-muxer basis, please see the README of the relevant muxer you are using. + +### Mplex + +[@libp2p/mplex](https://github.com/libp2p/js-libp2p-mplex) supports the following: + +```js +const node = await createLibp2pNode({ + muxers: [ + new Mplex({ + /** + * The total number of inbound protocol streams that can be opened on a given connection + */ + maxInboundStreams: 1024, + + /** + * The total number of outbound protocol streams that can be opened on a given connection + */ + maxOutboundStreams: 1024, + + /** + * How much incoming data to buffer before resetting the stream + */ + maxStreamBufferSize: 4 * 1024 * 1024, + + /** + * Mplex does not support backpressure so to protect ourselves, if `maxInboundStreams` is + * hit and the remote opens more than this many streams per second, close the connection + */ + disconnectThreshold: 5 + }) + ] +}) +``` + +### Yamux + +[@chainsafe/libp2p-yamux](https://github.com/Chainsafe/js-libp2p-yamux) supports the following: + +```js +const node = await createLibp2pNode({ + muxers: [ + new Yamux({ + /** + * The total number of inbound protocol streams that can be opened on a given connection + */ + maxInboundStreams: 1024, + + /** + * The total number of outbound protocol streams that can be opened on a given connection + */ + maxOutboundStreams: 1024 + }) + ] +}) +``` + +### Protocol limits + +When registering listeners for custom protocols, the maximum number of simultaneously open inbound and outbound streams per-connection can be specified. If not specified these will default to 32 inbound streams and 64 outbound streams. + +If more than this number of streams for the given protocol are opened on a single connection, subsequent new streams for that protocol will be immediately reset. + +Since incoming stream data is buffered until it is comsumed, you should attempt to specify the minimum amount of streams required to keep memory usage to a minimum. + +```js +libp2p.handle('/my-protocol/1.0.0', (streamData) => { + // ..handle stream +}, { + maxInboundStreams: 10, // defaults to 32 + maxOutboundStreams: 10, // defaults to 64 +}) +``` + +## Closing connections + +When choosing connections to close the connection manager sorts the list of connections by the value derived from the tags given to each peer. The values of all tags are summed and connections with lower valued peers are elibible for closing first. + +```js +// tag a peer +await libp2p.peerStore.tagPeer(peerId, 'my-tag', { + value: 50, // 0-100 is the typical value range + ttl: 1000 // optional field, this tag will be deleted after this many ms +}) +``` + +## Transport specific limits + +Some transports allow configuring additional limits, please see their READMEs for full config options. + +A non-exhaustive list follows: + +### TCP + +The [@libp2p/tcp](https://github.com/libp2p/js-libp2p-tcp) transport allows additional limits to be configured + +```js +const node = await createLibp2pNode({ + transports: [ + new TCP({ + /** + * Inbound connections with no activity in this timeframe (ms) will be closed + */ + inboundSocketInactivityTimeout: 30000, + + /** + * Outbound connections with no activity in this timeframe (ms) will be closed + */ + outboundSocketInactivityTimeout: 60000, + + /** + * Once this many connections are open on this listener any further connections + * will be rejected - this will have no effect if it is larger than the value + * configured for the ConnectionManager maxConnections parameter + */ + maxConnections: 200 + }) + ] +}) +``` + +## Allow/deny lists + +It is possible to configure some hosts to always accept connections from and some to always reject connections from. + +```js +const node = await createLibp2pNode({ + connectionManager: { + /** + * A list of multiaddrs, any connection with a `remoteAddress` property + * that has any of these addresses as a prefix will be accepted ignoring + * all connection limits + */ + allow: [ + '/ip4/43.123.5.23/tcp/3984', + '/ip4/234.243.64.2', + '/ip4/52.55', + // etc + ], + + /** + * Any connection with a `remoteAddress` property that has any of these + * addresses as a prefix will be immediately rejected + */ + deny: [ + '/ip4/132.14.52.64/tcp/3984', + '/ip4/234.243.64.2', + '/ip4/34.42', + // etc + ] + } +}) +``` From 14acff5b3d9375f7d200c12eda4855407a6a2368 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Tue, 11 Oct 2022 12:20:58 +0100 Subject: [PATCH 438/447] docs: fix whitespace --- doc/LIMITS.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/LIMITS.md b/doc/LIMITS.md index 838ffcb206..6cbd20b32a 100644 --- a/doc/LIMITS.md +++ b/doc/LIMITS.md @@ -2,7 +2,7 @@ In order to prevent excessive resource consumption by a libp2p node it's important to understand limits are applied and how to tune them to the needs of your application. -## Table of contents +## Table of contents - [Connection limits](#connection-limits) - [Inbound connection threshold](#inbound-connection-threshold) @@ -10,6 +10,7 @@ In order to prevent excessive resource consumption by a libp2p node it's importa - [Stream limits](#stream-limits) - [Mplex](#mplex) - [Yamux](#yamux) + - [Protocol limits](#protocol-limits) - [Closing connections](#closing-connections) - [Transport specific limits](#transport-specific-limits) - [TCP](#tcp) @@ -153,7 +154,7 @@ const node = await createLibp2pNode({ }) ``` -### Protocol limits +### Protocol limits When registering listeners for custom protocols, the maximum number of simultaneously open inbound and outbound streams per-connection can be specified. If not specified these will default to 32 inbound streams and 64 outbound streams. From 487b94240e244e31ebadb2f8229c1465717454eb Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 11 Oct 2022 14:45:49 +0100 Subject: [PATCH 439/447] fix: add after upgrade inbound method (#1422) --- src/connection-manager/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index ca11013f2f..7717cb4241 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -740,4 +740,8 @@ export class DefaultConnectionManager extends EventEmitter Date: Tue, 11 Oct 2022 18:34:39 +0100 Subject: [PATCH 440/447] fix: add pending connection limit (#1423) Adds a `maxIncomingPendingConnections` option to the Connection Manager that limits how many connections can be open but not yet upgraded. --- doc/LIMITS.md | 9 +++++- examples/package.json | 2 +- package.json | 6 ++-- src/connection-manager/index.ts | 24 +++++++++++++-- src/pubsub/dummy-pubsub.ts | 4 ++- src/upgrader.ts | 3 +- test/connection-manager/index.spec.ts | 43 +++++++++++++++++++++++++++ 7 files changed, 81 insertions(+), 10 deletions(-) diff --git a/doc/LIMITS.md b/doc/LIMITS.md index 6cbd20b32a..f1e34c5d2f 100644 --- a/doc/LIMITS.md +++ b/doc/LIMITS.md @@ -20,6 +20,8 @@ In order to prevent excessive resource consumption by a libp2p node it's importa It's possible to limit the amount of incoming and outgoing connections a node is able to make. When this limit is reached and an attempt to open a new connection is made, existing connections may be closed to make room for the new connection. +We can also limit the number of connections in a "pending" state. These connections have been opened by a remote peer but peer IDs have yet to be exchanged and/or connection encryption and multiplexing negotiated. Once this limit is hit further connections will be closed unless the remote peer has an address in the [allow list](#allowdeny-lists). + ```js const node = await createLibp2pNode({ connectionManager: { @@ -32,7 +34,12 @@ const node = await createLibp2pNode({ * If the number of open connections goes below this number, the node * will try to connect to nearby peers from the peer store */ - minConnections: 20 + minConnections: 20, + + /** + * How many connections can be open but not yet upgraded + */ + maxIncomingPendingConnections: 10 } }) ``` diff --git a/examples/package.json b/examples/package.json index a43e2a9df1..4c17cf07fa 100644 --- a/examples/package.json +++ b/examples/package.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@libp2p/pubsub-peer-discovery": "^6.0.2", - "@libp2p/floodsub": "^4.0.0", + "@libp2p/floodsub": "^4.0.1", "@nodeutils/defaults-deep": "^1.1.0", "execa": "^6.1.0", "fs-extra": "^10.1.0", diff --git a/package.json b/package.json index 2ad2304a60..35b705beaa 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "@libp2p/interface-peer-info": "^1.0.3", "@libp2p/interface-peer-routing": "^1.0.1", "@libp2p/interface-peer-store": "^1.2.2", - "@libp2p/interface-pubsub": "^2.1.0", + "@libp2p/interface-pubsub": "^3.0.0", "@libp2p/interface-registrar": "^2.0.3", "@libp2p/interface-stream-muxer": "^3.0.0", "@libp2p/interface-transport": "^2.0.0", @@ -174,7 +174,7 @@ "@libp2p/bootstrap": "^4.0.0", "@libp2p/daemon-client": "^3.0.1", "@libp2p/daemon-server": "^3.0.1", - "@libp2p/floodsub": "^4.0.0", + "@libp2p/floodsub": "^4.0.1", "@libp2p/interface-compliance-tests": "^3.0.2", "@libp2p/interface-connection-encrypter-compliance-tests": "^2.0.2", "@libp2p/interface-mocks": "^6.0.1", @@ -211,4 +211,4 @@ "browser": { "nat-api": false } -} \ No newline at end of file +} diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index 7717cb4241..9223c7d695 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -33,7 +33,8 @@ const defaultOptions: Partial = { pollInterval: 2000, autoDialInterval: 10000, movingAverageInterval: 60000, - inboundConnectionThreshold: 5 + inboundConnectionThreshold: 5, + maxIncomingPendingConnections: 10 } const METRICS_SYSTEM = 'libp2p' @@ -152,6 +153,12 @@ export interface ConnectionManagerInit { * host, reject subsequent connections */ inboundConnectionThreshold?: number + + /** + * The maximum number of parallel incoming connections allowed that have yet to + * complete the connection upgrade - e.g. choosing connection encryption, muxer, etc + */ + maxIncomingPendingConnections?: number } export interface ConnectionManagerEvents { @@ -175,6 +182,7 @@ export class DefaultConnectionManager extends EventEmitter implements PubSub { + public topicValidators = new Map() + isStarted (): boolean { return false } diff --git a/src/upgrader.ts b/src/upgrader.ts index 6f2ba74c84..c23070cd31 100644 --- a/src/upgrader.ts +++ b/src/upgrader.ts @@ -126,7 +126,6 @@ export class DefaultUpgrader extends EventEmitter implements Upg const accept = await this.components.getConnectionManager().acceptIncomingConnection(maConn) if (!accept) { - await maConn.close() throw errCode(new Error('connection denied'), codes.ERR_CONNECTION_DENIED) } @@ -201,7 +200,6 @@ export class DefaultUpgrader extends EventEmitter implements Upg } } catch (err: any) { log.error('Failed to upgrade inbound connection', err) - await maConn.close(err) throw err } @@ -228,6 +226,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg remotePeer }) } finally { + this.components.getConnectionManager().afterUpgradeInbound() timeoutController.clear() } } diff --git a/test/connection-manager/index.spec.ts b/test/connection-manager/index.spec.ts index 79a557f3d3..86b6ba8480 100644 --- a/test/connection-manager/index.spec.ts +++ b/test/connection-manager/index.spec.ts @@ -304,4 +304,47 @@ describe('Connection Manager', () => { await expect(connectionManager.acceptIncomingConnection(maConn)) .to.eventually.be.true() }) + + it('should limit the number of inbound pending connections', async () => { + const connectionManager = new DefaultConnectionManager({ + ...defaultOptions, + maxIncomingPendingConnections: 1 + }) + + const dialer = stubInterface() + dialer.dial.resolves(stubInterface()) + + const components = new Components({ + dialer + }) + + // set mocks + connectionManager.init(components) + + // start the upgrade + const maConn1 = mockMultiaddrConnection({ + source: [], + sink: async () => {} + }, await createEd25519PeerId()) + + await expect(connectionManager.acceptIncomingConnection(maConn1)) + .to.eventually.be.true() + + // start the upgrade + const maConn2 = mockMultiaddrConnection({ + source: [], + sink: async () => {} + }, await createEd25519PeerId()) + + // should be false because we have not completed the upgrade of maConn1 + await expect(connectionManager.acceptIncomingConnection(maConn2)) + .to.eventually.be.false() + + // finish the maConn1 pending upgrade + connectionManager.afterUpgradeInbound() + + // should be true because we have now completed the upgrade of maConn1 + await expect(connectionManager.acceptIncomingConnection(maConn2)) + .to.eventually.be.true() + }) }) From a3847f2d1725b1c92d5e0ef7bcdf840ea8428a75 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 14 Oct 2022 11:45:19 +0100 Subject: [PATCH 441/447] fix!: remove @libp2p/components (#1427) `@libp2p/components` is a choke-point for our dependency graph as it depends on every interface, meaning when one interface revs a major `@libp2p/components` major has to change too which means every module depending on it also needs a major. Switch instead to constructor injection of simple objects that let modules declare their dependencies on interfaces directly instead of indirectly via `@libp2p/components` Fixes libp2p/js-libp2p-components#6 BREAKING CHANGE: modules no longer implement `Initializable` instead switching to constructor injection --- .aegir.js | 14 +- doc/migrations/v0.37-v0.40.md | 141 ++++++++ examples/auto-relay/dialer.js | 10 +- examples/auto-relay/listener.js | 10 +- examples/auto-relay/relay.js | 10 +- examples/chat/src/libp2p.js | 14 +- examples/connection-encryption/1.js | 10 +- examples/delegated-routing/package.json | 13 +- .../delegated-routing/src/libp2p-bundle.js | 32 +- examples/discovery-mechanisms/1.js | 14 +- examples/discovery-mechanisms/2.js | 14 +- examples/discovery-mechanisms/3.js | 32 +- examples/echo/src/libp2p.js | 14 +- examples/libp2p-in-the-browser/index.js | 22 +- examples/libp2p-in-the-browser/package.json | 8 +- examples/package.json | 6 +- examples/peer-and-content-routing/1.js | 14 +- examples/peer-and-content-routing/2.js | 14 +- examples/pnet/libp2p-node.js | 14 +- examples/pnet/utils.js | 28 -- examples/protocol-and-stream-muxing/1.js | 10 +- examples/protocol-and-stream-muxing/2.js | 10 +- examples/protocol-and-stream-muxing/3.js | 10 +- examples/pubsub/1.js | 14 +- examples/pubsub/message-filtering/1.js | 14 +- examples/transports/1.js | 6 +- examples/transports/2.js | 10 +- examples/transports/3.js | 16 +- examples/transports/4.js | 14 +- examples/webrtc-direct/dialer.js | 14 +- examples/webrtc-direct/listener.js | 6 +- examples/webrtc-direct/package.json | 6 +- package.json | 35 +- src/address-manager/index.ts | 22 +- src/circuit/auto-relay.ts | 30 +- src/circuit/index.ts | 21 +- src/circuit/transport.ts | 69 ++-- src/components.ts | 332 ++++++++++++++++++ src/connection-manager/auto-dialler.ts | 26 +- src/connection-manager/dialer/index.ts | 38 +- src/connection-manager/index.ts | 81 +++-- src/content-routing/index.ts | 20 +- src/fetch/index.ts | 19 +- src/identify/index.ts | 93 ++--- src/index.ts | 19 +- src/insecure/index.ts | 6 +- src/keychain/index.ts | 49 +-- src/libp2p.ts | 98 +++--- src/nat-manager.ts | 20 +- src/peer-record-updater.ts | 36 +- src/peer-routing.ts | 19 +- src/ping/index.ts | 19 +- src/pnet/index.ts | 6 +- src/registrar.ts | 27 +- src/transport-manager.ts | 23 +- src/upgrader.ts | 68 ++-- test/addresses/address-manager.spec.ts | 33 +- test/addresses/addresses.node.ts | 30 +- test/addresses/utils.ts | 8 +- test/configuration/pubsub.spec.ts | 4 +- test/configuration/utils.ts | 24 +- test/connection-manager/auto-dialler.spec.ts | 5 +- test/connection-manager/index.node.ts | 39 +- test/connection-manager/index.spec.ts | 94 ++--- test/content-routing/content-routing.node.ts | 6 +- test/content-routing/dht/operation.node.ts | 14 +- test/content-routing/dht/utils.ts | 4 +- test/content-routing/utils.ts | 4 +- test/core/consume-peer-record.spec.ts | 12 +- test/core/encryption.spec.ts | 12 +- test/core/get-public-key.spec.ts | 12 +- test/core/listening.node.ts | 10 +- test/dialing/dial-request.spec.ts | 54 ++- test/dialing/direct.node.ts | 134 +++---- test/dialing/direct.spec.ts | 122 +++---- test/dialing/resolver.spec.ts | 7 +- test/fetch/fetch.node.ts | 12 +- test/fetch/index.spec.ts | 40 +-- test/identify/index.spec.ts | 76 ++-- test/identify/push.spec.ts | 81 +++-- test/insecure/compliance.spec.ts | 4 +- test/insecure/plaintext.spec.ts | 14 +- test/interop.ts | 65 ++-- test/keychain/cms-interop.spec.ts | 9 +- test/keychain/keychain.spec.ts | 91 +++-- test/metrics/index.node.ts | 8 +- test/nat-manager/nat-manager.node.ts | 57 ++- test/peer-discovery/index.node.ts | 14 +- test/peer-discovery/index.spec.ts | 41 +-- test/peer-routing/peer-routing.node.ts | 12 +- test/peer-routing/utils.ts | 4 +- test/ping/index.spec.ts | 42 +-- test/ping/ping.node.ts | 4 +- test/pnet/index.spec.ts | 28 +- test/registrar/registrar.spec.ts | 68 ++-- test/relay/auto-relay.node.ts | 8 +- test/relay/relay.node.ts | 26 +- test/transports/transport-manager.node.ts | 28 +- test/transports/transport-manager.spec.ts | 42 +-- test/upgrading/upgrader.spec.ts | 142 ++++---- test/utils/base-options.browser.ts | 12 +- test/utils/base-options.ts | 12 +- test/utils/creators/peer.ts | 2 +- 103 files changed, 1998 insertions(+), 1312 deletions(-) create mode 100644 doc/migrations/v0.37-v0.40.md delete mode 100644 examples/pnet/utils.js create mode 100644 src/components.ts diff --git a/.aegir.js b/.aegir.js index c6f77fcacd..ee369a8db0 100644 --- a/.aegir.js +++ b/.aegir.js @@ -1,5 +1,5 @@ -import { WebSockets } from '@libp2p/websockets' -import { Mplex } from '@libp2p/mplex' +import { webSockets } from '@libp2p/websockets' +import { mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' import { pipe } from 'it-pipe' import { createFromJSON } from '@libp2p/peer-id-factory' @@ -14,7 +14,7 @@ export default { // use dynamic import because we only want to reference these files during the test run, e.g. after building const { createLibp2p } = await import('./dist/src/index.js') const { MULTIADDRS_WEBSOCKETS } = await import('./dist/test/fixtures/browser.js') - const { Plaintext } = await import('./dist/src/insecure/index.js') + const { plaintext } = await import('./dist/src/insecure/index.js') const { default: Peers } = await import('./dist/test/fixtures/peers.js') // Use the last peer @@ -28,14 +28,14 @@ export default { }, peerId, transports: [ - new WebSockets() + webSockets() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Noise(), - new Plaintext() + () => () => new Noise(), + plaintext() ], relay: { enabled: true, diff --git a/doc/migrations/v0.37-v0.40.md b/doc/migrations/v0.37-v0.40.md new file mode 100644 index 0000000000..4b0b0099f3 --- /dev/null +++ b/doc/migrations/v0.37-v0.40.md @@ -0,0 +1,141 @@ +# Migrating to libp2p@40 + +A migration guide for refactoring your application code from libp2p v0.37.x to v0.40.0. + +## Table of Contents + +- [Modules](#modules) +- [Autodial](#autodial) + +## Modules + +Modules in the libp2p ecosystem no longer export constructors. This is because internally libp2p has switched to using constructor dependency injection so needs to control the lifecycle of the various components. + +These modules now export factory functions that take the same arguments as the older constructors so migration should be relatively straightforward. + +The most affected place will be where you configure your libp2p node: + +**Before** + +```js +import { createLibp2p } from 'libp2p' +import { TCP } from '@libp2p/tcp' +import { WebSockets } from '@libp2p/websockets' +import { WebRTCStar } from '@libp2p/webrtc-star' +import { WebRTCDirect } from '@libp2p/webrtc-direct' +import { KadDHT } from '@libp2p/kad-dht' +import Gossipsub from '@chainsafe/libp2p-gossipsub' +import { Bootstrap } from '@libp2p/bootstrap' +import { MulticastDNS } from '@libp2p/mdns' +import { Noise } from '@chainsafe/libp2p-noise' +import { Yamux } from '@chainsafe/libp2p-yamux' +import { Mplex } from '@libp2p/mplex' +import { DelegatedContentRouting } from '@libp2p/delegated-content-routing' +import { DelegatedPeerRouting } from '@libp2p/delegated-peer-routing' +import { create as createIpfsHttpClient } from 'ipfs-http-client' + + const star = new WebRTCStar() + const ipfsHttpClinet = createIpfsHttpClient(new URL('https://node0.delegate.ipfs.io')) + +const node = await createLibp2p({ + addresses: { + listen: [ + '/ip4/0.0.0.0/tcp/0' + ] + }, + transports: [ + new TCP(), + new WebSockets(), + new WebRTCDirect(), + star + ], + peerDiscovery: [ + new Bootstrap({ list: [ ... ] }), + new MulticastDNS(), + star.discovery + ], + connectionEncryption: [ + new Noise() + ], + streamMuxers: [ + new Yamux(), + new Mplex() + ], + contentRouting: [ + new DelegatedContentRouting(ipfsHttpClinet) + ], + peerRouting: [ + new DelegatedPeerRouting(ipfsHttpClinet) + ], + dht: new KadDHT(), + pubsub: new Gossipsub() +}) + +await node.start() +// ... +``` + +**After** + +```js +import { createLibp2p } from 'libp2p' +import { tcp } from '@libp2p/tcp' +import { webSockets } from '@libp2p/websockets' +import { webRTCStar } from '@libp2p/webrtc-star' +import { webRTCDirect } from '@libp2p/webrtc-direct' +import { kadDHT } from '@libp2p/kad-dht' +import { gossipsub } from '@chainsafe/libp2p-gossipsub' +import { bootstrap } from '@libp2p/bootstrap' +import { mdns } from '@libp2p/mdns' +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' +import { mplex } from '@libp2p/mplex' +import { delegatedContentRouting } from '@libp2p/delegated-content-routing' +import { delegatedPeerRouting } from '@libp2p/delegated-peer-routing' +import { create as createIpfsHttpClient } from 'ipfs-http-client' + +const star = webRTCStar() +const ipfsHttpClinet = createIpfsHttpClient(new URL('https://node0.delegate.ipfs.io')) + +const node = await createLibp2p({ + addresses: { + listen: [ + '/ip4/0.0.0.0/tcp/0' + ] + }, + transports: [ + tcp(), + webSockets(), + webRTCDirect(), + star + ], + peerDiscovery: [ + bootstrap({ list: [ ... ] }), + mdns(), + star.discovery + ], + connectionEncryption: [ + noise() + ], + streamMuxers: [ + yamux(), + mplex() + ], + contentRouting: [ + delegatedContentRouting(ipfsHttpClinet) + ], + peerRouting: [ + delegatedPeerRouting(ipfsHttpClinet) + ], + dht: kadDHT(), + pubsub: gossipsub() +}) + +await node.start() +``` + +## Autodial + +In versions of libp2p prior to 0.40.x, when the peer discovery subsystem announced a new peer, that peer would automatically be dialed and a connection held open, subject to [the normal rules on closing connections](https://github.com/libp2p/js-libp2p/blob/master/doc/LIMITS.md#closing-connections). Prior to having a working DHT this was necessary to increase the chances that you would be connected to a peer that could service any network request you might make. The downside to this is that connections are expensive so nodes displayed high CPU usage even when idle. Now that the DHT is in place this is no longer necessary as we can discover nearby peers using that mechanism. + +When dialing a remote peer the [identify protocol](https://docs.libp2p.io/concepts/protocols/#identify) is run, during which we find out which protocols the remote peer supports. This in turn affects the nodes added to [topologies](https://github.com/libp2p/js-libp2p-topology#readme) for a given protocol. Topologies may see fewer nodes added to them as we are now dialing and running identify on fewer peers, though the peers that do get added are likely to be higher value - their PeerIds will be KAD-closer to ours, they would have been dialed directly rather than being random peers on the network, etc. diff --git a/examples/auto-relay/dialer.js b/examples/auto-relay/dialer.js index e358e09959..347c00d973 100644 --- a/examples/auto-relay/dialer.js +++ b/examples/auto-relay/dialer.js @@ -1,7 +1,7 @@ import { createLibp2p } from 'libp2p' -import { WebSockets } from '@libp2p/websockets' +import { webSockets } from '@libp2p/websockets' import { Noise } from '@chainsafe/libp2p-noise' -import { Mplex } from '@libp2p/mplex' +import { mplex } from '@libp2p/mplex' async function main () { const autoRelayNodeAddr = process.argv[2] @@ -11,13 +11,13 @@ async function main () { const node = await createLibp2p({ transports: [ - new WebSockets() + webSockets() ], connectionEncryption: [ - new Noise() + () => new Noise() ], streamMuxers: [ - new Mplex() + mplex() ] }) diff --git a/examples/auto-relay/listener.js b/examples/auto-relay/listener.js index 50aae8bb09..cf2d59caf4 100644 --- a/examples/auto-relay/listener.js +++ b/examples/auto-relay/listener.js @@ -1,7 +1,7 @@ import { createLibp2p } from 'libp2p' -import { WebSockets } from '@libp2p/websockets' +import { webSockets } from '@libp2p/websockets' import { Noise } from '@chainsafe/libp2p-noise' -import { Mplex } from '@libp2p/mplex' +import { mplex } from '@libp2p/mplex' async function main () { const relayAddr = process.argv[2] @@ -11,13 +11,13 @@ async function main () { const node = await createLibp2p({ transports: [ - new WebSockets() + webSockets() ], connectionEncryption: [ - new Noise() + () => new Noise() ], streamMuxers: [ - new Mplex() + mplex() ], relay: { enabled: true, diff --git a/examples/auto-relay/relay.js b/examples/auto-relay/relay.js index 94ff3aceb4..37533d630e 100644 --- a/examples/auto-relay/relay.js +++ b/examples/auto-relay/relay.js @@ -1,7 +1,7 @@ import { createLibp2p } from 'libp2p' -import { WebSockets } from '@libp2p/websockets' +import { webSockets } from '@libp2p/websockets' import { Noise } from '@chainsafe/libp2p-noise' -import { Mplex } from '@libp2p/mplex' +import { mplex } from '@libp2p/mplex' async function main () { const node = await createLibp2p({ @@ -11,13 +11,13 @@ async function main () { // announce: ['/dns4/auto-relay.libp2p.io/tcp/443/wss/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3'] }, transports: [ - new WebSockets() + webSockets() ], connectionEncryption: [ - new Noise() + () => new Noise() ], streamMuxers: [ - new Mplex() + mplex() ], relay: { enabled: true, diff --git a/examples/chat/src/libp2p.js b/examples/chat/src/libp2p.js index 610dae0735..25ea1b59c4 100644 --- a/examples/chat/src/libp2p.js +++ b/examples/chat/src/libp2p.js @@ -1,6 +1,6 @@ -import { TCP } from '@libp2p/tcp' -import { WebSockets } from '@libp2p/websockets' -import { Mplex } from '@libp2p/mplex' +import { tcp } from '@libp2p/tcp' +import { webSockets } from '@libp2p/websockets' +import { mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' import defaultsDeep from '@nodeutils/defaults-deep' import { createLibp2p as create } from 'libp2p' @@ -8,14 +8,14 @@ import { createLibp2p as create } from 'libp2p' export async function createLibp2p(_options) { const defaults = { transports: [ - new TCP(), - new WebSockets() + tcp(), + webSockets() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Noise() + () => new Noise() ] } diff --git a/examples/connection-encryption/1.js b/examples/connection-encryption/1.js index 4f03ff6d57..d0e0cee95d 100644 --- a/examples/connection-encryption/1.js +++ b/examples/connection-encryption/1.js @@ -1,6 +1,6 @@ import { createLibp2p } from '../../dist/src/index.js' -import { TCP } from '@libp2p/tcp' -import { Mplex } from '@libp2p/mplex' +import { tcp } from '@libp2p/tcp' +import { mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' import { pipe } from 'it-pipe' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' @@ -11,9 +11,9 @@ const createNode = async () => { addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - transports: [new TCP()], - streamMuxers: [new Mplex()], - connectionEncryption: [new Noise()] + transports: [tcp()], + streamMuxers: [mplex()], + connectionEncryption: [() => new Noise()] }) await node.start() diff --git a/examples/delegated-routing/package.json b/examples/delegated-routing/package.json index a136e8433a..cbcc0010a9 100644 --- a/examples/delegated-routing/package.json +++ b/examples/delegated-routing/package.json @@ -5,13 +5,14 @@ "dependencies": { "@chainsafe/libp2p-noise": "^9.0.0", "ipfs-core": "^0.15.4", + "ipfs-http-client": "^58.0.1", "libp2p": "../../", - "@libp2p/delegated-content-routing": "^2.0.2", - "@libp2p/delegated-peer-routing": "^2.0.2", - "@libp2p/kad-dht": "^4.0.0", - "@libp2p/mplex": "^6.0.2", - "@libp2p/webrtc-star": "^4.0.1", - "@libp2p/websockets": "^4.0.0", + "@libp2p/delegated-content-routing": "^3.0.0", + "@libp2p/delegated-peer-routing": "^3.0.0", + "@libp2p/kad-dht": "^5.0.1", + "@libp2p/mplex": "^7.0.0", + "@libp2p/webrtc-star": "^5.0.2", + "@libp2p/websockets": "^5.0.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "5.0.0" diff --git a/examples/delegated-routing/src/libp2p-bundle.js b/examples/delegated-routing/src/libp2p-bundle.js index 515c2d1c3c..3a32073801 100644 --- a/examples/delegated-routing/src/libp2p-bundle.js +++ b/examples/delegated-routing/src/libp2p-bundle.js @@ -2,20 +2,21 @@ 'use strict' import { createLibp2p } from 'libp2p' -import { WebSockets } from '@libp2p/websockets' -import { WebRTCStar } from '@libp2p/webrtc-star' -import { Mplex } from '@libp2p/mplex' +import { webSockets } from '@libp2p/websockets' +import { webRTCStar } from '@libp2p/webrtc-star' +import { mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' -import { DelegatedPeerRouting } from '@libp2p/delegated-peer-routing' -import { DelegatedContentRouting } from '@libp2p/delegated-content-routing' +import { delegatedPeerRouting } from '@libp2p/delegated-peer-routing' +import { delegatedContentRouting } from '@libp2p/delegated-content-routing' +import { create as createIpfsHttpClient } from 'ipfs-http-client' export default function Libp2pBundle ({peerInfo, peerBook}) { - const wrtcstar = new WebRTCStar() - const delegatedApiOptions = { + const wrtcstar = new webRTCStar() + const client = createIpfsHttpClient({ host: '0.0.0.0', protocol: 'http', port: '8080' - } + }) return createLibp2p({ peerInfo, @@ -26,20 +27,23 @@ export default function Libp2pBundle ({peerInfo, peerBook}) { pollInterval: 5000 }, contentRouting: [ - new DelegatedPeerRouting(peerInfo.id, delegatedApiOptions) + delegatedPeerRouting(client) ], peerRouting: [ - new DelegatedContentRouting(delegatedApiOptions) + delegatedContentRouting(client) ], transports: [ - wrtcstar, - new WebSockets() + wrtcstar.transport, + webSockets() ], streamMuxers: [ - new Mplex() + mplex() + ], + peerDiscovery: [ + wrtcstar.discovery ], connectionEncryption: [ - new Noise() + () => new Noise() ], connectionManager: { autoDial: false diff --git a/examples/discovery-mechanisms/1.js b/examples/discovery-mechanisms/1.js index 6b099bbf2c..9f8b183639 100644 --- a/examples/discovery-mechanisms/1.js +++ b/examples/discovery-mechanisms/1.js @@ -1,10 +1,10 @@ /* eslint-disable no-console */ import { createLibp2p } from 'libp2p' -import { TCP } from '@libp2p/tcp' -import { Mplex } from '@libp2p/mplex' +import { tcp } from '@libp2p/tcp' +import { mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' -import { Bootstrap } from '@libp2p/bootstrap' +import { bootstrap } from '@libp2p/bootstrap' import bootstrapers from './bootstrappers.js' ;(async () => { @@ -12,11 +12,11 @@ import bootstrapers from './bootstrappers.js' addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - transports: [new TCP()], - streamMuxers: [new Mplex()], - connectionEncryption: [new Noise()], + transports: [tcp()], + streamMuxers: [mplex()], + connectionEncryption: [() => new Noise()], peerDiscovery: [ - new Bootstrap({ + bootstrap({ interval: 60e3, list: bootstrapers }) diff --git a/examples/discovery-mechanisms/2.js b/examples/discovery-mechanisms/2.js index bad5a13f50..dd98fcc2a9 100644 --- a/examples/discovery-mechanisms/2.js +++ b/examples/discovery-mechanisms/2.js @@ -1,10 +1,10 @@ /* eslint-disable no-console */ import { createLibp2p } from 'libp2p' -import { TCP } from '@libp2p/tcp' -import { Mplex } from '@libp2p/mplex' +import { tcp } from '@libp2p/tcp' +import { mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' -import { MulticastDNS } from '@libp2p/mdns' +import { mdns } from '@libp2p/mdns' const createNode = async () => { const node = await createLibp2p({ @@ -12,16 +12,16 @@ const createNode = async () => { listen: ['/ip4/0.0.0.0/tcp/0'] }, transports: [ - new TCP() + tcp() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Noise() + () => new Noise() ], peerDiscovery: [ - new MulticastDNS({ + mdns({ interval: 20e3 }) ] diff --git a/examples/discovery-mechanisms/3.js b/examples/discovery-mechanisms/3.js index 1b3d18b9e8..cae8d4de60 100644 --- a/examples/discovery-mechanisms/3.js +++ b/examples/discovery-mechanisms/3.js @@ -1,27 +1,27 @@ /* eslint-disable no-console */ import { createLibp2p } from 'libp2p' -import { TCP } from '@libp2p/tcp' -import { Mplex } from '@libp2p/mplex' +import { tcp } from '@libp2p/tcp' +import { mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' -import { FloodSub } from '@libp2p/floodsub' -import { Bootstrap } from '@libp2p/bootstrap' -import { PubSubPeerDiscovery } from '@libp2p/pubsub-peer-discovery' +import { floodsub } from '@libp2p/floodsub' +import { bootstrap } from '@libp2p/bootstrap' +import { pubsubPeerDiscovery } from '@libp2p/pubsub-peer-discovery' const createNode = async (bootstrappers) => { const node = await createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - transports: [new TCP()], - streamMuxers: [new Mplex()], - connectionEncryption: [new Noise()], - pubsub: new FloodSub(), + transports: [tcp()], + streamMuxers: [mplex()], + connectionEncryption: [() => new Noise()], + pubsub: floodsub(), peerDiscovery: [ - new Bootstrap({ + bootstrap({ list: bootstrappers }), - new PubSubPeerDiscovery({ + pubsubPeerDiscovery({ interval: 1000 }) ] @@ -37,12 +37,12 @@ const createNode = async (bootstrappers) => { '/ip4/0.0.0.0/tcp/0' ] }, - transports: [new TCP()], - streamMuxers: [new Mplex()], - connectionEncryption: [new Noise()], - pubsub: new FloodSub(), + transports: [tcp()], + streamMuxers: [mplex()], + connectionEncryption: [() => new Noise()], + pubsub: floodsub(), peerDiscovery: [ - new PubSubPeerDiscovery({ + pubsubPeerDiscovery({ interval: 1000 }) ], diff --git a/examples/echo/src/libp2p.js b/examples/echo/src/libp2p.js index b875da7238..9ce8aa5d61 100644 --- a/examples/echo/src/libp2p.js +++ b/examples/echo/src/libp2p.js @@ -1,6 +1,6 @@ -import { TCP } from '@libp2p/tcp' -import { WebSockets } from '@libp2p/websockets' -import { Mplex } from '@libp2p/mplex' +import { tcp } from '@libp2p/tcp' +import { webSockets } from '@libp2p/websockets' +import { mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' import defaultsDeep from '@nodeutils/defaults-deep' import { createLibp2p as createNode } from 'libp2p' @@ -8,14 +8,14 @@ import { createLibp2p as createNode } from 'libp2p' export async function createLibp2p(_options) { const defaults = { transports: [ - new TCP(), - new WebSockets() + tcp(), + webSockets() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Noise() + () => new Noise() ] } diff --git a/examples/libp2p-in-the-browser/index.js b/examples/libp2p-in-the-browser/index.js index eccd6953c7..1be61992c9 100644 --- a/examples/libp2p-in-the-browser/index.js +++ b/examples/libp2p-in-the-browser/index.js @@ -1,12 +1,12 @@ import { createLibp2p } from 'libp2p' -import { WebSockets } from '@libp2p/websockets' -import { WebRTCStar } from '@libp2p/webrtc-star' +import { webSockets } from '@libp2p/websockets' +import { webRTCStar } from '@libp2p/webrtc-star' import { Noise } from '@chainsafe/libp2p-noise' -import { Mplex } from '@libp2p/mplex' -import { Bootstrap } from '@libp2p/bootstrap' +import { mplex } from '@libp2p/mplex' +import { bootstrap } from '@libp2p/bootstrap' document.addEventListener('DOMContentLoaded', async () => { - const webRtcStar = new WebRTCStar() + const wrtcStar = webRTCStar() // Create our libp2p node const libp2p = await createLibp2p({ @@ -20,14 +20,14 @@ document.addEventListener('DOMContentLoaded', async () => { ] }, transports: [ - new WebSockets(), - webRtcStar + webSockets(), + wrtcStar.transport ], - connectionEncryption: [new Noise()], - streamMuxers: [new Mplex()], + connectionEncryption: [() => new Noise()], + streamMuxers: [mplex()], peerDiscovery: [ - webRtcStar.discovery, - new Bootstrap({ + wrtcStar.discovery, + bootstrap({ list: [ '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', '/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb', diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json index 3e24693520..57977bc96e 100644 --- a/examples/libp2p-in-the-browser/package.json +++ b/examples/libp2p-in-the-browser/package.json @@ -10,10 +10,10 @@ "license": "ISC", "dependencies": { "@chainsafe/libp2p-noise": "^9.0.0", - "@libp2p/bootstrap": "^4.0.0", - "@libp2p/mplex": "^6.0.2", - "@libp2p/webrtc-star": "^4.0.1", - "@libp2p/websockets": "^3.0.4", + "@libp2p/bootstrap": "^5.0.0", + "@libp2p/mplex": "^7.0.0", + "@libp2p/webrtc-star": "^5.0.2", + "@libp2p/websockets": "^5.0.0", "libp2p": "../../" }, "devDependencies": { diff --git a/examples/package.json b/examples/package.json index 4c17cf07fa..9c5ba40457 100644 --- a/examples/package.json +++ b/examples/package.json @@ -9,14 +9,14 @@ }, "license": "MIT", "dependencies": { - "@libp2p/pubsub-peer-discovery": "^6.0.2", - "@libp2p/floodsub": "^4.0.1", + "@libp2p/pubsub-peer-discovery": "^7.0.0", + "@libp2p/floodsub": "^5.0.0", "@nodeutils/defaults-deep": "^1.1.0", "execa": "^6.1.0", "fs-extra": "^10.1.0", "libp2p": "../", "p-defer": "^4.0.0", - "uint8arrays": "^3.0.0", + "uint8arrays": "^4.0.0", "which": "^2.0.1" }, "devDependencies": { diff --git a/examples/peer-and-content-routing/1.js b/examples/peer-and-content-routing/1.js index 7208909826..bab07dbb00 100644 --- a/examples/peer-and-content-routing/1.js +++ b/examples/peer-and-content-routing/1.js @@ -1,10 +1,10 @@ /* eslint-disable no-console */ import { createLibp2p } from 'libp2p' -import { TCP } from '@libp2p/tcp' -import { Mplex } from '@libp2p/mplex' +import { tcp } from '@libp2p/tcp' +import { mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' -import { KadDHT } from '@libp2p/kad-dht' +import { kadDHT } from '@libp2p/kad-dht' import delay from 'delay' const createNode = async () => { @@ -12,10 +12,10 @@ const createNode = async () => { addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - transports: [new TCP()], - streamMuxers: [new Mplex()], - connectionEncryption: [new Noise()], - dht: new KadDHT() + transports: [tcp()], + streamMuxers: [mplex()], + connectionEncryption: [() => new Noise()], + dht: kadDHT() }) await node.start() diff --git a/examples/peer-and-content-routing/2.js b/examples/peer-and-content-routing/2.js index d9245c48ff..54486aeedc 100644 --- a/examples/peer-and-content-routing/2.js +++ b/examples/peer-and-content-routing/2.js @@ -1,11 +1,11 @@ /* eslint-disable no-console */ import { createLibp2p } from 'libp2p' -import { TCP } from '@libp2p/tcp' -import { Mplex } from '@libp2p/mplex' +import { tcp } from '@libp2p/tcp' +import { mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' import { CID } from 'multiformats/cid' -import { KadDHT } from '@libp2p/kad-dht' +import { kadDHT } from '@libp2p/kad-dht' import all from 'it-all' import delay from 'delay' @@ -14,10 +14,10 @@ const createNode = async () => { addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - transports: [new TCP()], - streamMuxers: [new Mplex()], - connectionEncryption: [new Noise()], - dht: new KadDHT() + transports: [tcp()], + streamMuxers: [mplex()], + connectionEncryption: [() => new Noise()], + dht: kadDHT() }) await node.start() diff --git a/examples/pnet/libp2p-node.js b/examples/pnet/libp2p-node.js index 662edb056d..ee6c58ca7c 100644 --- a/examples/pnet/libp2p-node.js +++ b/examples/pnet/libp2p-node.js @@ -1,8 +1,8 @@ import { createLibp2p } from 'libp2p' -import { TCP } from '@libp2p/tcp' -import { Mplex } from '@libp2p/mplex' +import { tcp } from '@libp2p/tcp' +import { mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' -import { PreSharedKeyConnectionProtector } from 'libp2p/pnet' +import { preSharedKey } from 'libp2p/pnet' /** * privateLibp2pNode returns a libp2p node function that will use the swarm @@ -13,16 +13,16 @@ export async function privateLibp2pNode (swarmKey) { addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - transports: [new TCP()], // We're only using the TCP transport for this example - streamMuxers: [new Mplex()], // We're only using mplex muxing + transports: [tcp()], // We're only using the TCP transport for this example + streamMuxers: [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 - connectionEncryption: [new Noise()], + connectionEncryption: [() => new 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 peerDiscovery: [], - connectionProtector: new PreSharedKeyConnectionProtector({ + connectionProtector: preSharedKey({ psk: swarmKey }) }) diff --git a/examples/pnet/utils.js b/examples/pnet/utils.js deleted file mode 100644 index 16d89de702..0000000000 --- a/examples/pnet/utils.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict' -const fs from 'fs') -import path from 'path' - -/** - * mkdirp recursively creates needed folders for the given dir path - * @param {string} dir - * @returns {string} The path that was created - */ -module.exports.mkdirp = (dir) => { - return path - .resolve(dir) - .split(path.sep) - .reduce((acc, cur) => { - const currentPath = path.normalize(acc + path.sep + cur) - - try { - fs.statSync(currentPath) - } catch (e) { - if (e.code === 'ENOENT') { - fs.mkdirSync(currentPath) - } else { - throw e - } - } - return currentPath - }, '') -} diff --git a/examples/protocol-and-stream-muxing/1.js b/examples/protocol-and-stream-muxing/1.js index 024aaaab80..cb74df25e8 100644 --- a/examples/protocol-and-stream-muxing/1.js +++ b/examples/protocol-and-stream-muxing/1.js @@ -1,6 +1,6 @@ import { createLibp2p } from 'libp2p' -import { TCP } from '@libp2p/tcp' -import { Mplex } from '@libp2p/mplex' +import { tcp } from '@libp2p/tcp' +import { mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' import { pipe } from 'it-pipe' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' @@ -11,9 +11,9 @@ const createNode = async () => { addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - transports: [new TCP()], - streamMuxers: [new Mplex()], - connectionEncryption: [new Noise()] + transports: [tcp()], + streamMuxers: [mplex()], + connectionEncryption: [() => new Noise()] }) await node.start() diff --git a/examples/protocol-and-stream-muxing/2.js b/examples/protocol-and-stream-muxing/2.js index 0b7a332bf6..27c7c2c4c9 100644 --- a/examples/protocol-and-stream-muxing/2.js +++ b/examples/protocol-and-stream-muxing/2.js @@ -1,6 +1,6 @@ import { createLibp2p } from 'libp2p' -import { TCP } from '@libp2p/tcp' -import { Mplex } from '@libp2p/mplex' +import { tcp } from '@libp2p/tcp' +import { mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' import { pipe } from 'it-pipe' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' @@ -11,9 +11,9 @@ const createNode = async () => { addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - transports: [new TCP()], - streamMuxers: [new Mplex()], - connectionEncryption: [new Noise()] + transports: [tcp()], + streamMuxers: [mplex()], + connectionEncryption: [() => new Noise()] }) await node.start() diff --git a/examples/protocol-and-stream-muxing/3.js b/examples/protocol-and-stream-muxing/3.js index 84de6b1b25..770e088ce9 100644 --- a/examples/protocol-and-stream-muxing/3.js +++ b/examples/protocol-and-stream-muxing/3.js @@ -1,8 +1,8 @@ /* eslint-disable no-console */ import { createLibp2p } from 'libp2p' -import { TCP } from '@libp2p/tcp' -import { Mplex } from '@libp2p/mplex' +import { tcp } from '@libp2p/tcp' +import { mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' import { pipe } from 'it-pipe' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' @@ -13,9 +13,9 @@ const createNode = async () => { addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - transports: [new TCP()], - streamMuxers: [new Mplex()], - connectionEncryption: [new Noise()] + transports: [tcp()], + streamMuxers: [mplex()], + connectionEncryption: [() => new Noise()] }) await node.start() diff --git a/examples/pubsub/1.js b/examples/pubsub/1.js index bb89977e2a..193f2b3740 100644 --- a/examples/pubsub/1.js +++ b/examples/pubsub/1.js @@ -1,10 +1,10 @@ /* eslint-disable no-console */ import { createLibp2p } from 'libp2p' -import { TCP } from '@libp2p/tcp' -import { Mplex } from '@libp2p/mplex' +import { tcp } from '@libp2p/tcp' +import { mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' -import { FloodSub } from '@libp2p/floodsub' +import { floodsub } from '@libp2p/floodsub' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' @@ -13,10 +13,10 @@ const createNode = async () => { addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - transports: [new TCP()], - streamMuxers: [new Mplex()], - connectionEncryption: [new Noise()], - pubsub: new FloodSub() + transports: [tcp()], + streamMuxers: [mplex()], + connectionEncryption: [() => new Noise()], + pubsub: floodsub() }) await node.start() diff --git a/examples/pubsub/message-filtering/1.js b/examples/pubsub/message-filtering/1.js index 86e43ec16a..8813c3241b 100644 --- a/examples/pubsub/message-filtering/1.js +++ b/examples/pubsub/message-filtering/1.js @@ -1,10 +1,10 @@ /* eslint-disable no-console */ import { createLibp2p } from 'libp2p' -import { TCP } from '@libp2p/tcp' -import { Mplex } from '@libp2p/mplex' +import { tcp } from '@libp2p/tcp' +import { mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' -import { FloodSub } from '@libp2p/floodsub' +import { floodsub } from '@libp2p/floodsub' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' @@ -13,10 +13,10 @@ const createNode = async () => { addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - transports: [new TCP()], - streamMuxers: [new Mplex()], - connectionEncryption: [new Noise()], - pubsub: new FloodSub() + transports: [tcp()], + streamMuxers: [mplex()], + connectionEncryption: [() => new Noise()], + pubsub: floodsub() }) await node.start() diff --git a/examples/transports/1.js b/examples/transports/1.js index 739acaa81c..5552c296cf 100644 --- a/examples/transports/1.js +++ b/examples/transports/1.js @@ -1,7 +1,7 @@ /* eslint-disable no-console */ import { createLibp2p } from 'libp2p' -import { TCP } from '@libp2p/tcp' +import { tcp } from '@libp2p/tcp' import { Noise } from '@chainsafe/libp2p-noise' const createNode = async () => { @@ -14,10 +14,10 @@ const createNode = async () => { ] }, transports: [ - new TCP() + tcp() ], connectionEncryption: [ - new Noise() + () => new Noise() ] }) diff --git a/examples/transports/2.js b/examples/transports/2.js index 2808c82049..61cf9b4583 100644 --- a/examples/transports/2.js +++ b/examples/transports/2.js @@ -1,9 +1,9 @@ /* eslint-disable no-console */ import { createLibp2p } from 'libp2p' -import { TCP } from '@libp2p/tcp' +import { tcp } from '@libp2p/tcp' import { Noise } from '@chainsafe/libp2p-noise' -import { Mplex } from '@libp2p/mplex' +import { mplex } from '@libp2p/mplex' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { pipe } from 'it-pipe' @@ -16,9 +16,9 @@ const createNode = async () => { // the multiaddr format, a self describable address listen: ['/ip4/0.0.0.0/tcp/0'] }, - transports: [new TCP()], - connectionEncryption: [new Noise()], - streamMuxers: [new Mplex()] + transports: [tcp()], + connectionEncryption: [() => new Noise()], + streamMuxers: [mplex()] }) await node.start() diff --git a/examples/transports/3.js b/examples/transports/3.js index 980570c7ef..5dfcc156f1 100644 --- a/examples/transports/3.js +++ b/examples/transports/3.js @@ -1,10 +1,10 @@ /* eslint-disable no-console */ import { createLibp2p } from 'libp2p' -import { TCP } from '@libp2p/tcp' -import { WebSockets } from '@libp2p/websockets' +import { tcp } from '@libp2p/tcp' +import { webSockets } from '@libp2p/websockets' import { Noise } from '@chainsafe/libp2p-noise' -import { Mplex } from '@libp2p/mplex' +import { mplex } from '@libp2p/mplex' import { pipe } from 'it-pipe' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' @@ -19,8 +19,8 @@ const createNode = async (transports, addresses = []) => { listen: addresses }, transports: transports, - connectionEncryption: [new Noise()], - streamMuxers: [new Mplex()] + connectionEncryption: [() => new Noise()], + streamMuxers: [mplex()] }) await node.start() @@ -45,9 +45,9 @@ function print ({ stream }) { (async () => { const [node1, node2, node3] = await Promise.all([ - createNode([new TCP()], '/ip4/0.0.0.0/tcp/0'), - createNode([new TCP(), new WebSockets()], ['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws']), - createNode([new WebSockets()], '/ip4/127.0.0.1/tcp/20000/ws') + createNode([tcp()], '/ip4/0.0.0.0/tcp/0'), + createNode([tcp(), webSockets()], ['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws']), + createNode([webSockets()], '/ip4/127.0.0.1/tcp/20000/ws') ]) printAddrs(node1, '1') diff --git a/examples/transports/4.js b/examples/transports/4.js index ad35181e75..35879db187 100644 --- a/examples/transports/4.js +++ b/examples/transports/4.js @@ -1,10 +1,10 @@ /* eslint-disable no-console */ import { createLibp2p } from 'libp2p' -import { TCP } from '@libp2p/tcp' -import { WebSockets } from '@libp2p/websockets' +import { tcp } from '@libp2p/tcp' +import { webSockets } from '@libp2p/websockets' import { Noise } from '@chainsafe/libp2p-noise' -import { Mplex } from '@libp2p/mplex' +import { mplex } from '@libp2p/mplex' import fs from 'fs' import https from 'https' import { pipe } from 'it-pipe' @@ -26,16 +26,16 @@ const createNode = async (addresses = []) => { listen: addresses }, transports: [ - new TCP(), - new WebSockets({ + tcp(), + webSockets({ server: httpServer, websocket: { rejectUnauthorized: false } }) ], - connectionEncryption: [new Noise()], - streamMuxers: [new Mplex()], + connectionEncryption: [() => new Noise()], + streamMuxers: [mplex()], connectionManager: { // Disable autoDial as it would fail because we are using a self-signed cert. // `dialProtocol` does not fail because we pass `rejectUnauthorized: false`. diff --git a/examples/webrtc-direct/dialer.js b/examples/webrtc-direct/dialer.js index d1dc0066ba..fb0930a3f5 100644 --- a/examples/webrtc-direct/dialer.js +++ b/examples/webrtc-direct/dialer.js @@ -1,18 +1,18 @@ import { createLibp2p } from 'libp2p' -import { WebRTCDirect } from '@libp2p/webrtc-direct' -import { Mplex } from '@libp2p/mplex' +import { webRTCDirect } from '@libp2p/webrtc-direct' +import { mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' -import { Bootstrap } from '@libp2p/bootstrap' +import { bootstrap } from '@libp2p/bootstrap' document.addEventListener('DOMContentLoaded', async () => { // use the same peer id as in `listener.js` to avoid copy-pasting of listener's peer id into `peerDiscovery` const hardcodedPeerId = '12D3KooWCuo3MdXfMgaqpLC5Houi1TRoFqgK9aoxok4NK5udMu8m' const libp2p = await createLibp2p({ - transports: [new WebRTCDirect()], - streamMuxers: [new Mplex()], - connectionEncryption: [new Noise()], + transports: [webRTCDirect()], + streamMuxers: [mplex()], + connectionEncryption: [() => new Noise()], peerDiscovery: [ - new Bootstrap({ + bootstrap({ list: [`/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct/p2p/${hardcodedPeerId}`] }) ] diff --git a/examples/webrtc-direct/listener.js b/examples/webrtc-direct/listener.js index 8a822d0b4e..1bb50f3381 100644 --- a/examples/webrtc-direct/listener.js +++ b/examples/webrtc-direct/listener.js @@ -1,6 +1,6 @@ import { createLibp2p } from 'libp2p' import { WebRTCDirect } from '@libp2p/webrtc-direct' -import { Mplex } from '@libp2p/mplex' +import { mplex } from '@libp2p/mplex' import { Noise } from '@chainsafe/libp2p-noise' import { createFromJSON } from '@libp2p/peer-id-factory' import wrtc from 'wrtc' @@ -19,8 +19,8 @@ import wrtc from 'wrtc' listen: ['/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct'] }, transports: [new WebRTCDirect({ wrtc })], - streamMuxers: [new Mplex()], - connectionEncryption: [new Noise()] + streamMuxers: [mplex()], + connectionEncryption: [() => new Noise()] }) node.connectionManager.addEventListener('peer:connect', (evt) => { diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index b42ffa5d4a..d72f56424c 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -9,10 +9,10 @@ }, "license": "ISC", "dependencies": { - "@libp2p/webrtc-direct": "^2.0.3", + "@libp2p/webrtc-direct": "^4.0.0", "@chainsafe/libp2p-noise": "^9.0.0", - "@libp2p/bootstrap": "^4.0.0", - "@libp2p/mplex": "^6.0.2", + "@libp2p/bootstrap": "^5.0.0", + "@libp2p/mplex": "^7.0.0", "libp2p": "../../", "wrtc": "^0.4.7" }, diff --git a/package.json b/package.json index 35b705beaa..9eb24ed4d8 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,6 @@ }, "dependencies": { "@achingbrain/nat-port-mapper": "^1.0.3", - "@libp2p/components": "^3.0.0", "@libp2p/connection": "^4.0.2", "@libp2p/crypto": "^1.0.4", "@libp2p/interface-address-manager": "^2.0.0", @@ -124,7 +123,7 @@ "@libp2p/peer-id": "^1.1.15", "@libp2p/peer-id-factory": "^1.0.18", "@libp2p/peer-record": "^4.0.3", - "@libp2p/peer-store": "^4.0.0", + "@libp2p/peer-store": "^5.0.0", "@libp2p/tracked-map": "^2.0.1", "@libp2p/utils": "^3.0.2", "@multiformats/mafmt": "^11.0.2", @@ -150,43 +149,43 @@ "it-sort": "^1.0.1", "it-stream-types": "^1.0.4", "merge-options": "^3.0.4", - "multiformats": "^9.6.3", + "multiformats": "^10.0.0", "mutable-proxy": "^1.0.0", "node-forge": "^1.3.1", "p-fifo": "^1.0.0", "p-retry": "^5.0.0", "p-settle": "^5.0.0", "private-ip": "^2.3.3", - "protons-runtime": "^3.0.1", + "protons-runtime": "^4.0.1", "rate-limiter-flexible": "^2.3.11", "retimer": "^3.0.0", "sanitize-filename": "^1.6.3", "set-delayed-interval": "^1.0.0", "timeout-abort-controller": "^3.0.0", "uint8arraylist": "^2.3.2", - "uint8arrays": "^3.0.0", + "uint8arrays": "^4.0.2", "wherearewe": "^2.0.0", "xsalsa20": "^1.1.0" }, "devDependencies": { "@chainsafe/libp2p-noise": "^9.0.0", - "@chainsafe/libp2p-yamux": "^2.0.0", - "@libp2p/bootstrap": "^4.0.0", + "@chainsafe/libp2p-yamux": "^3.0.0", + "@libp2p/bootstrap": "^5.0.0", "@libp2p/daemon-client": "^3.0.1", "@libp2p/daemon-server": "^3.0.1", - "@libp2p/floodsub": "^4.0.1", + "@libp2p/floodsub": "^5.0.0", "@libp2p/interface-compliance-tests": "^3.0.2", - "@libp2p/interface-connection-encrypter-compliance-tests": "^2.0.2", - "@libp2p/interface-mocks": "^6.0.1", + "@libp2p/interface-connection-encrypter-compliance-tests": "^3.0.0", + "@libp2p/interface-mocks": "^7.0.1", "@libp2p/interop": "^3.0.1", - "@libp2p/kad-dht": "^4.0.0", - "@libp2p/mdns": "^4.0.0", - "@libp2p/mplex": "^6.0.2", - "@libp2p/pubsub": "^4.0.0", - "@libp2p/tcp": "^4.0.0", + "@libp2p/kad-dht": "^5.0.1", + "@libp2p/mdns": "^5.0.0", + "@libp2p/mplex": "^7.0.0", + "@libp2p/pubsub": "^5.0.0", + "@libp2p/tcp": "^5.0.0", "@libp2p/topology": "^3.0.1", - "@libp2p/webrtc-star": "^4.0.1", - "@libp2p/websockets": "^4.0.0", + "@libp2p/webrtc-star": "^5.0.2", + "@libp2p/websockets": "^5.0.0", "@types/node-forge": "^1.0.0", "@types/p-fifo": "^1.0.0", "@types/varint": "^6.0.0", @@ -203,7 +202,7 @@ "p-event": "^5.0.1", "p-times": "^4.0.0", "p-wait-for": "^5.0.0", - "protons": "^5.0.0", + "protons": "^6.0.0", "rimraf": "^3.0.2", "sinon": "^14.0.0", "ts-sinon": "^2.0.2" diff --git a/src/address-manager/index.ts b/src/address-manager/index.ts index 907f30a8c5..62272fd5a3 100644 --- a/src/address-manager/index.ts +++ b/src/address-manager/index.ts @@ -3,7 +3,8 @@ import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events' import type { Multiaddr } from '@multiformats/multiaddr' import { multiaddr } from '@multiformats/multiaddr' import { peerIdFromString } from '@libp2p/peer-id' -import type { Components } from '@libp2p/components' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { TransportManager } from '@libp2p/interface-transport' export interface AddressManagerInit { announceFilter?: AddressFilter @@ -24,6 +25,11 @@ export interface AddressManagerInit { noAnnounce?: string[] } +export interface DefaultAddressManagerComponents { + peerId: PeerId + transportManager: TransportManager +} + export interface AddressFilter { (addrs: Multiaddr[]): Multiaddr[] } @@ -31,7 +37,7 @@ export interface AddressFilter { const defaultAddressFilter = (addrs: Multiaddr[]): Multiaddr[] => addrs export class DefaultAddressManager extends EventEmitter { - private readonly components: Components + private readonly components: DefaultAddressManagerComponents private readonly listen: Set private readonly announce: Set private readonly observed: Set @@ -43,7 +49,7 @@ export class DefaultAddressManager extends EventEmitter { * The listen addresses will be used by the libp2p transports to listen for new connections, * while the announce addresses will be used for the peer addresses' to other peers in the network. */ - constructor (components: Components, init: AddressManagerInit) { + constructor (components: DefaultAddressManagerComponents, init: AddressManagerInit) { super() const { listen = [], announce = [] } = init @@ -105,8 +111,8 @@ export class DefaultAddressManager extends EventEmitter { const remotePeerId = peerIdFromString(remotePeer) // use same encoding for comparison - if (remotePeerId.equals(this.components.getPeerId())) { - ma = ma.decapsulate(multiaddr(`/p2p/${this.components.getPeerId().toString()}`)) + if (remotePeerId.equals(this.components.peerId)) { + ma = ma.decapsulate(multiaddr(`/p2p/${this.components.peerId.toString()}`)) } } @@ -126,7 +132,7 @@ export class DefaultAddressManager extends EventEmitter { if (addrs.length === 0) { // no configured announce addrs, add configured listen addresses - addrs = this.components.getTransportManager().getAddrs().map(ma => ma.toString()) + addrs = this.components.transportManager.getAddrs().map(ma => ma.toString()) } addrs = addrs.concat(this.getObservedAddrs().map(ma => ma.toString())) @@ -138,11 +144,11 @@ export class DefaultAddressManager extends EventEmitter { return this.announceFilter(Array.from(addrSet) .map(str => multiaddr(str))) .map(ma => { - if (ma.getPeerId() === this.components.getPeerId().toString()) { + if (ma.getPeerId() === this.components.peerId.toString()) { return ma } - return ma.encapsulate(`/p2p/${this.components.getPeerId().toString()}`) + return ma.encapsulate(`/p2p/${this.components.peerId.toString()}`) }) } } diff --git a/src/circuit/auto-relay.ts b/src/circuit/auto-relay.ts index 16e1e39086..c6b2722cca 100644 --- a/src/circuit/auto-relay.ts +++ b/src/circuit/auto-relay.ts @@ -13,11 +13,11 @@ import { import type { PeerId } from '@libp2p/interface-peer-id' import type { AddressSorter, PeerProtocolsChangeData } from '@libp2p/interface-peer-store' import type { Connection } from '@libp2p/interface-connection' -import type { Components } from '@libp2p/components' import sort from 'it-sort' import all from 'it-all' import { pipe } from 'it-pipe' import { publicAddressesFirst } from '@libp2p/utils/address-sort' +import type { RelayComponents } from './index.js' const log = logger('libp2p:auto-relay') @@ -30,13 +30,13 @@ export interface AutoRelayInit { } export class AutoRelay { - private readonly components: Components + private readonly components: RelayComponents private readonly addressSorter: AddressSorter private readonly maxListeners: number private readonly listenRelays: Set private readonly onError: (error: Error, msg?: string) => void - constructor (components: Components, init: AutoRelayInit) { + constructor (components: RelayComponents, init: AutoRelayInit) { this.components = components this.addressSorter = init.addressSorter ?? publicAddressesFirst this.maxListeners = init.maxListeners ?? 1 @@ -46,12 +46,12 @@ export class AutoRelay { this._onProtocolChange = this._onProtocolChange.bind(this) this._onPeerDisconnected = this._onPeerDisconnected.bind(this) - this.components.getPeerStore().addEventListener('change:protocols', (evt) => { + this.components.peerStore.addEventListener('change:protocols', (evt) => { void this._onProtocolChange(evt).catch(err => { log.error(err) }) }) - this.components.getConnectionManager().addEventListener('peer:disconnect', this._onPeerDisconnected) + this.components.connectionManager.addEventListener('peer:disconnect', this._onPeerDisconnected) } /** @@ -85,7 +85,7 @@ export class AutoRelay { // If protocol, check if can hop, store info in the metadataBook and listen on it try { - const connections = this.components.getConnectionManager().getConnections(peerId) + const connections = this.components.connectionManager.getConnections(peerId) if (connections.length === 0) { return @@ -102,7 +102,7 @@ export class AutoRelay { const supportsHop = await canHop({ connection }) if (supportsHop) { - await this.components.getPeerStore().metadataBook.setValue(peerId, HOP_METADATA_KEY, uint8ArrayFromString(HOP_METADATA_VALUE)) + await this.components.peerStore.metadataBook.setValue(peerId, HOP_METADATA_KEY, uint8ArrayFromString(HOP_METADATA_VALUE)) await this._addListenRelay(connection, id) } } catch (err: any) { @@ -140,7 +140,7 @@ export class AutoRelay { // Get peer known addresses and sort them with public addresses first const remoteAddrs = await pipe( - await this.components.getPeerStore().addressBook.get(connection.remotePeer), + await this.components.peerStore.addressBook.get(connection.remotePeer), (source) => sort(source, this.addressSorter), async (source) => await all(source) ) @@ -158,7 +158,7 @@ export class AutoRelay { multiaddr = multiaddr.encapsulate('/p2p-circuit') // Announce multiaddrs will update on listen success by TransportManager event being triggered - await this.components.getTransportManager().listen([multiaddr]) + await this.components.transportManager.listen([multiaddr]) return true } catch (err: any) { log.error('error listening on circuit address', err) @@ -203,7 +203,7 @@ export class AutoRelay { } const knownHopsToDial = [] - const peers = await this.components.getPeerStore().all() + const peers = await this.components.peerStore.all() // Check if we have known hop peers to use and attempt to listen on the already connected for (const { id, metadata } of peers) { @@ -225,7 +225,7 @@ export class AutoRelay { continue } - const connections = this.components.getConnectionManager().getConnections(id) + const connections = this.components.connectionManager.getConnections(id) // If not connected, store for possible later use. if (connections.length === 0) { @@ -254,19 +254,19 @@ export class AutoRelay { // Try to find relays to hop on the network try { const cid = await namespaceToCid(RELAY_RENDEZVOUS_NS) - for await (const provider of this.components.getContentRouting().findProviders(cid)) { + for await (const provider of this.components.contentRouting.findProviders(cid)) { if (provider.multiaddrs.length === 0) { continue } const peerId = provider.id - if (peerId.equals(this.components.getPeerId())) { + if (peerId.equals(this.components.peerId)) { // Skip the provider if it's us as dialing will fail continue } - await this.components.getPeerStore().addressBook.add(peerId, provider.multiaddrs) + await this.components.peerStore.addressBook.add(peerId, provider.multiaddrs) await this._tryToListenOnRelay(peerId) @@ -282,7 +282,7 @@ export class AutoRelay { async _tryToListenOnRelay (peerId: PeerId) { try { - const connection = await this.components.getConnectionManager().openConnection(peerId) + const connection = await this.components.connectionManager.openConnection(peerId) await this._addListenRelay(connection, peerId.toString()) } catch (err: any) { log.error('Could not use %p as relay', peerId, err) diff --git a/src/circuit/index.ts b/src/circuit/index.ts index ed6aebbcf6..08dc700c9a 100644 --- a/src/circuit/index.ts +++ b/src/circuit/index.ts @@ -10,10 +10,13 @@ import { namespaceToCid } from './utils.js' import { RELAY_RENDEZVOUS_NS } from './constants.js' -import type { AddressSorter } from '@libp2p/interface-peer-store' +import type { AddressSorter, PeerStore } from '@libp2p/interface-peer-store' import type { Startable } from '@libp2p/interfaces/startable' -import type { Components } from '@libp2p/components' import type { RelayConfig } from '../index.js' +import type { ContentRouting } from '@libp2p/interface-content-routing' +import type { ConnectionManager } from '@libp2p/interface-connection-manager' +import type { TransportManager } from '@libp2p/interface-transport' +import type { PeerId } from '@libp2p/interface-peer-id' const log = logger('libp2p:relay') @@ -36,8 +39,16 @@ export interface RelayInit extends RelayConfig { addressSorter?: AddressSorter } +export interface RelayComponents { + peerId: PeerId + contentRouting: ContentRouting + peerStore: PeerStore + connectionManager: ConnectionManager + transportManager: TransportManager +} + export class Relay implements Startable { - private readonly components: Components + private readonly components: RelayComponents private readonly init: RelayInit // @ts-expect-error this field isn't used anywhere? private readonly autoRelay?: AutoRelay @@ -47,7 +58,7 @@ export class Relay implements Startable { /** * Creates an instance of Relay */ - constructor (components: Components, init: RelayInit) { + constructor (components: RelayComponents, init: RelayInit) { this.components = components // Create autoRelay if enabled this.autoRelay = init.autoRelay?.enabled !== false @@ -97,7 +108,7 @@ export class Relay implements Startable { async _advertiseService () { try { const cid = await namespaceToCid(RELAY_RENDEZVOUS_NS) - await this.components.getContentRouting().provide(cid) + await this.components.contentRouting.provide(cid) } catch (err: any) { if (err.code === codes.ERR_NO_ROUTERS_AVAILABLE) { log.error('a content router, such as a DHT, must be provided in order to advertise the relay service', err) diff --git a/src/circuit/transport.ts b/src/circuit/transport.ts index db21b29765..83ddff10da 100644 --- a/src/circuit/transport.ts +++ b/src/circuit/transport.ts @@ -11,11 +11,10 @@ import { createListener } from './listener.js' import { handleCanHop, handleHop, hop } from './circuit/hop.js' import { handleStop } from './circuit/stop.js' import { StreamHandler } from './circuit/stream-handler.js' -import { symbol } from '@libp2p/interface-transport' +import { symbol, Upgrader } from '@libp2p/interface-transport' import { peerIdFromString } from '@libp2p/peer-id' -import { Components, Initializable } from '@libp2p/components' import type { AbortOptions } from '@libp2p/interfaces' -import type { IncomingStreamData } from '@libp2p/interface-registrar' +import type { IncomingStreamData, Registrar } from '@libp2p/interface-registrar' import type { Listener, Transport, CreateListenerOptions, ConnectionHandler } from '@libp2p/interface-transport' import type { Connection } from '@libp2p/interface-connection' import type { RelayConfig } from '../index.js' @@ -24,21 +23,47 @@ import { TimeoutController } from 'timeout-abort-controller' import { setMaxListeners } from 'events' import type { Uint8ArrayList } from 'uint8arraylist' import type { Duplex } from 'it-stream-types' +import type { Startable } from '@libp2p/interfaces/startable' +import type { ConnectionManager } from '@libp2p/interface-connection-manager' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { PeerStore } from '@libp2p/interface-peer-store' +import type { AddressManager } from '@libp2p/interface-address-manager' const log = logger('libp2p:circuit') -export class Circuit implements Transport, Initializable { +export interface CircuitComponents { + peerId: PeerId + peerStore: PeerStore + registrar: Registrar + connectionManager: ConnectionManager + upgrader: Upgrader + addressManager: AddressManager +} + +export class Circuit implements Transport, Startable { private handler?: ConnectionHandler - private components: Components = new Components() + private readonly components: CircuitComponents private readonly _init: RelayConfig + private _started: boolean - constructor (init: RelayConfig) { + constructor (components: CircuitComponents, init: RelayConfig) { this._init = init + this.components = components + this._started = false } - init (components: Components): void { - this.components = components - void this.components.getRegistrar().handle(RELAY_CODEC, (data) => { + isStarted () { + return this._started + } + + async start (): Promise { + if (this._started) { + return + } + + this._started = true + + await this.components.registrar.handle(RELAY_CODEC, (data) => { void this._onProtocol(data).catch(err => { log.error(err) }) @@ -48,6 +73,10 @@ export class Circuit implements Transport, Initializable { }) } + async stop () { + await this.components.registrar.unhandle(RELAY_CODEC) + } + hopEnabled () { return true } @@ -108,7 +137,7 @@ export class Circuit implements Transport, Initializable { request, streamHandler, circuit: this, - connectionManager: this.components.getConnectionManager() + connectionManager: this.components.connectionManager }) break } @@ -145,7 +174,7 @@ export class Circuit implements Transport, Initializable { const type = request.type === CircuitPB.Type.HOP ? 'relay' : 'inbound' log('new %s connection %s', type, maConn.remoteAddr) - const conn = await this.components.getUpgrader().upgradeInbound(maConn) + const conn = await this.components.upgrader.upgradeInbound(maConn) log('%s connection %s upgraded', type, maConn.remoteAddr) if (this.handler != null) { @@ -178,12 +207,12 @@ export class Circuit implements Transport, Initializable { const destinationPeer = peerIdFromString(destinationId) let disconnectOnFailure = false - const relayConnections = this.components.getConnectionManager().getConnections(relayPeer) + const relayConnections = this.components.connectionManager.getConnections(relayPeer) let relayConnection = relayConnections[0] if (relayConnection == null) { - await this.components.getPeerStore().addressBook.add(relayPeer, [relayAddr]) - relayConnection = await this.components.getConnectionManager().openConnection(relayPeer, options) + await this.components.peerStore.addressBook.add(relayPeer, [relayAddr]) + relayConnection = await this.components.connectionManager.openConnection(relayPeer, options) disconnectOnFailure = true } @@ -194,8 +223,8 @@ export class Circuit implements Transport, Initializable { request: { type: CircuitPB.Type.HOP, srcPeer: { - id: this.components.getPeerId().toBytes(), - addrs: this.components.getAddressManager().getAddresses().map(addr => addr.bytes) + id: this.components.peerId.toBytes(), + addrs: this.components.addressManager.getAddresses().map(addr => addr.bytes) }, dstPeer: { id: destinationPeer.toBytes(), @@ -204,7 +233,7 @@ export class Circuit implements Transport, Initializable { } }) - const localAddr = relayAddr.encapsulate(`/p2p-circuit/p2p/${this.components.getPeerId().toString()}`) + const localAddr = relayAddr.encapsulate(`/p2p-circuit/p2p/${this.components.peerId.toString()}`) const maConn = streamToMaConnection({ stream: virtualConnection, remoteAddr: ma, @@ -212,7 +241,7 @@ export class Circuit implements Transport, Initializable { }) log('new outbound connection %s', maConn.remoteAddr) - return await this.components.getUpgrader().upgradeOutbound(maConn) + return await this.components.upgrader.upgradeOutbound(maConn) } catch (err: any) { log.error('Circuit relay dial failed', err) disconnectOnFailure && await relayConnection.close() @@ -228,8 +257,8 @@ export class Circuit implements Transport, Initializable { this.handler = options.handler return createListener({ - connectionManager: this.components.getConnectionManager(), - peerStore: this.components.getPeerStore() + connectionManager: this.components.connectionManager, + peerStore: this.components.peerStore }) } diff --git a/src/components.ts b/src/components.ts new file mode 100644 index 0000000000..0db9fed1a2 --- /dev/null +++ b/src/components.ts @@ -0,0 +1,332 @@ +import errCode from 'err-code' +import type { ConnectionGater, ConnectionProtector } from '@libp2p/interface-connection' +import type { ContentRouting } from '@libp2p/interface-content-routing' +import type { AddressManager } from '@libp2p/interface-address-manager' +import { isStartable, Startable } from '@libp2p/interfaces/startable' +import type { Metrics } from '@libp2p/interface-metrics' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { PeerRouting } from '@libp2p/interface-peer-routing' +import type { PeerStore } from '@libp2p/interface-peer-store' +import type { Registrar } from '@libp2p/interface-registrar' +import type { TransportManager, Upgrader } from '@libp2p/interface-transport' +import type { Datastore } from 'interface-datastore' +import type { PubSub } from '@libp2p/interface-pubsub' +import type { DualDHT } from '@libp2p/interface-dht' +import type { ConnectionManager, Dialer } from '@libp2p/interface-connection-manager' + +export interface Components { + peerId: PeerId + addressManager: AddressManager + peerStore: PeerStore + upgrader: Upgrader + registrar: Registrar + connectionManager: ConnectionManager + transportManager: TransportManager + connectionGater: ConnectionGater + contentRouting: ContentRouting + peerRouting: PeerRouting + datastore: Datastore + connectionProtector?: ConnectionProtector + dialer: Dialer + metrics?: Metrics + dht?: DualDHT + pubsub?: PubSub +} + +export interface ComponentsInit { + peerId?: PeerId + addressManager?: AddressManager + peerStore?: PeerStore + upgrader?: Upgrader + metrics?: Metrics + registrar?: Registrar + connectionManager?: ConnectionManager + transportManager?: TransportManager + connectionGater?: ConnectionGater + contentRouting?: ContentRouting + peerRouting?: PeerRouting + datastore?: Datastore + connectionProtector?: ConnectionProtector + dht?: DualDHT + pubsub?: PubSub + dialer?: Dialer +} + +export class DefaultComponents implements Components, Startable { + private _peerId?: PeerId + private _addressManager?: AddressManager + private _peerStore?: PeerStore + private _upgrader?: Upgrader + private _metrics?: Metrics + private _registrar?: Registrar + private _connectionManager?: ConnectionManager + private _transportManager?: TransportManager + private _connectionGater?: ConnectionGater + private _contentRouting?: ContentRouting + private _peerRouting?: PeerRouting + private _datastore?: Datastore + private _connectionProtector?: ConnectionProtector + private _dht?: DualDHT + private _pubsub?: PubSub + private _dialer?: Dialer + private _started = false + + constructor (init: ComponentsInit = {}) { + this._peerId = init.peerId + this._addressManager = init.addressManager + this._peerStore = init.peerStore + this._upgrader = init.upgrader + this._metrics = init.metrics + this._registrar = init.registrar + this._connectionManager = init.connectionManager + this._transportManager = init.transportManager + this._connectionGater = init.connectionGater + this._contentRouting = init.contentRouting + this._peerRouting = init.peerRouting + this._datastore = init.datastore + this._connectionProtector = init.connectionProtector + this._dht = init.dht + this._pubsub = init.pubsub + this._dialer = init.dialer + } + + isStarted () { + return this._started + } + + async beforeStart () { + await Promise.all( + Object.values(this).filter(obj => isStartable(obj)).map(async (startable: Startable) => { + if (startable.beforeStart != null) { + await startable.beforeStart() + } + }) + ) + } + + async start () { + await Promise.all( + Object.values(this).filter(obj => isStartable(obj)).map(async (startable: Startable) => { + await startable.start() + }) + ) + + this._started = true + } + + async afterStart () { + await Promise.all( + Object.values(this).filter(obj => isStartable(obj)).map(async (startable: Startable) => { + if (startable.afterStart != null) { + await startable.afterStart() + } + }) + ) + } + + async beforeStop () { + await Promise.all( + Object.values(this).filter(obj => isStartable(obj)).map(async (startable: Startable) => { + if (startable.beforeStop != null) { + await startable.beforeStop() + } + }) + ) + } + + async stop () { + await Promise.all( + Object.values(this).filter(obj => isStartable(obj)).map(async (startable: Startable) => { + await startable.stop() + }) + ) + + this._started = false + } + + async afterStop () { + await Promise.all( + Object.values(this).filter(obj => isStartable(obj)).map(async (startable: Startable) => { + if (startable.afterStop != null) { + await startable.afterStop() + } + }) + ) + } + + get peerId (): PeerId { + if (this._peerId == null) { + throw errCode(new Error('peerId not set'), 'ERR_SERVICE_MISSING') + } + + return this._peerId + } + + set peerId (peerId: PeerId) { + this._peerId = peerId + } + + get addressManager (): AddressManager { + if (this._addressManager == null) { + throw errCode(new Error('addressManager not set'), 'ERR_SERVICE_MISSING') + } + + return this._addressManager + } + + set addressManager (addressManager: AddressManager) { + this._addressManager = addressManager + } + + get peerStore (): PeerStore { + if (this._peerStore == null) { + throw errCode(new Error('peerStore not set'), 'ERR_SERVICE_MISSING') + } + + return this._peerStore + } + + set peerStore (peerStore: PeerStore) { + this._peerStore = peerStore + } + + get upgrader (): Upgrader { + if (this._upgrader == null) { + throw errCode(new Error('upgrader not set'), 'ERR_SERVICE_MISSING') + } + + return this._upgrader + } + + set upgrader (upgrader: Upgrader) { + this._upgrader = upgrader + } + + get registrar (): Registrar { + if (this._registrar == null) { + throw errCode(new Error('registrar not set'), 'ERR_SERVICE_MISSING') + } + + return this._registrar + } + + set registrar (registrar: Registrar) { + this._registrar = registrar + } + + get connectionManager (): ConnectionManager { + if (this._connectionManager == null) { + throw errCode(new Error('connectionManager not set'), 'ERR_SERVICE_MISSING') + } + + return this._connectionManager + } + + set connectionManager (connectionManager: ConnectionManager) { + this._connectionManager = connectionManager + } + + get transportManager (): TransportManager { + if (this._transportManager == null) { + throw errCode(new Error('transportManager not set'), 'ERR_SERVICE_MISSING') + } + + return this._transportManager + } + + set transportManager (transportManager: TransportManager) { + this._transportManager = transportManager + } + + get connectionGater (): ConnectionGater { + if (this._connectionGater == null) { + throw errCode(new Error('connectionGater not set'), 'ERR_SERVICE_MISSING') + } + + return this._connectionGater + } + + set connectionGater (connectionGater: ConnectionGater) { + this._connectionGater = connectionGater + } + + get contentRouting (): ContentRouting { + if (this._contentRouting == null) { + throw errCode(new Error('contentRouting not set'), 'ERR_SERVICE_MISSING') + } + + return this._contentRouting + } + + set contentRouting (contentRouting: ContentRouting) { + this._contentRouting = contentRouting + } + + get peerRouting (): PeerRouting { + if (this._peerRouting == null) { + throw errCode(new Error('peerRouting not set'), 'ERR_SERVICE_MISSING') + } + + return this._peerRouting + } + + set peerRouting (peerRouting: PeerRouting) { + this._peerRouting = peerRouting + } + + get datastore (): Datastore { + if (this._datastore == null) { + throw errCode(new Error('datastore not set'), 'ERR_SERVICE_MISSING') + } + + return this._datastore + } + + set datastore (datastore: Datastore) { + this._datastore = datastore + } + + get connectionProtector (): ConnectionProtector | undefined { + return this._connectionProtector + } + + set connectionProtector (connectionProtector: ConnectionProtector | undefined) { + this._connectionProtector = connectionProtector + } + + get dialer (): Dialer { + if (this._dialer == null) { + throw errCode(new Error('dialer not set'), 'ERR_SERVICE_MISSING') + } + + return this._dialer + } + + set dialer (dialer: Dialer) { + this._dialer = dialer + } + + get metrics (): Metrics | undefined { + return this._metrics + } + + set metrics (metrics: Metrics | undefined) { + this._metrics = metrics + } + + get dht (): DualDHT | undefined { + return this._dht + } + + set dht (dht: DualDHT | undefined) { + this._dht = dht + } + + get pubsub (): PubSub | undefined { + return this._pubsub + } + + set pubsub (pubsub: PubSub | undefined) { + this._pubsub = pubsub + } +} diff --git a/src/connection-manager/auto-dialler.ts b/src/connection-manager/auto-dialler.ts index cb6717ea87..2b26b00ad2 100644 --- a/src/connection-manager/auto-dialler.ts +++ b/src/connection-manager/auto-dialler.ts @@ -7,7 +7,9 @@ import { pipe } from 'it-pipe' import filter from 'it-filter' import sort from 'it-sort' import type { Startable } from '@libp2p/interfaces/startable' -import type { Components } from '@libp2p/components' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { ConnectionManager } from '@libp2p/interface-connection-manager' +import type { PeerStore } from '@libp2p/interface-peer-store' const log = logger('libp2p:connection-manager:auto-dialler') @@ -28,6 +30,12 @@ export interface AutoDiallerInit { autoDialInterval?: number } +export interface AutoDiallerComponents { + peerId: PeerId + connectionManager: ConnectionManager + peerStore: PeerStore +} + const defaultOptions: Partial = { enabled: true, minConnections: 0, @@ -35,7 +43,7 @@ const defaultOptions: Partial = { } export class AutoDialler implements Startable { - private readonly components: Components + private readonly components: AutoDiallerComponents private readonly options: Required private running: boolean private autoDialTimeout?: ReturnType @@ -45,7 +53,7 @@ export class AutoDialler implements Startable { * 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. */ - constructor (components: Components, init: AutoDiallerInit) { + constructor (components: AutoDiallerComponents, init: AutoDiallerInit) { this.components = components this.options = mergeOptions.call({ ignoreUndefined: true }, defaultOptions, init) this.running = false @@ -102,19 +110,19 @@ export class AutoDialler implements Startable { const minConnections = this.options.minConnections // Already has enough connections - if (this.components.getConnectionManager().getConnections().length >= minConnections) { + if (this.components.connectionManager.getConnections().length >= minConnections) { this.autoDialTimeout = retimer(this._autoDial, this.options.autoDialInterval) return } // Sort peers on whether we know protocols or public keys for them - const allPeers = await this.components.getPeerStore().all() + const allPeers = await this.components.peerStore.all() const peers = await pipe( // shuffle the peers allPeers.sort(() => Math.random() > 0.5 ? 1 : -1), - (source) => filter(source, (peer) => !peer.id.equals(this.components.getPeerId())), + (source) => filter(source, (peer) => !peer.id.equals(this.components.peerId)), (source) => sort(source, (a, b) => { if (b.protocols.length > a.protocols.length) { return 1 @@ -126,7 +134,7 @@ export class AutoDialler implements Startable { async (source) => await all(source) ) - for (let i = 0; this.running && i < peers.length && this.components.getConnectionManager().getConnections().length < minConnections; i++) { + for (let i = 0; this.running && i < peers.length && this.components.connectionManager.getConnections().length < minConnections; i++) { // Connection Manager was stopped during async dial if (!this.running) { return @@ -134,10 +142,10 @@ export class AutoDialler implements Startable { const peer = peers[i] - if (this.components.getConnectionManager().getConnections(peer.id).length === 0) { + if (this.components.connectionManager.getConnections(peer.id).length === 0) { log('connecting to a peerStore stored peer %p', peer.id) try { - await this.components.getConnectionManager().openConnection(peer.id) + await this.components.connectionManager.openConnection(peer.id) } catch (err: any) { log.error('could not connect to peerStore stored peer', err) } diff --git a/src/connection-manager/dialer/index.ts b/src/connection-manager/dialer/index.ts index fd2a6ba8d7..dd36f8a181 100644 --- a/src/connection-manager/dialer/index.ts +++ b/src/connection-manager/dialer/index.ts @@ -19,17 +19,17 @@ import { MAX_PER_PEER_DIALS, MAX_ADDRS_TO_DIAL } from '../../constants.js' -import type { Connection } from '@libp2p/interface-connection' +import type { Connection, ConnectionGater } from '@libp2p/interface-connection' import type { AbortOptions } from '@libp2p/interfaces' import type { Startable } from '@libp2p/interfaces/startable' import type { PeerId } from '@libp2p/interface-peer-id' import { getPeer } from '../../get-peer.js' import sort from 'it-sort' -import type { Components } from '@libp2p/components' import map from 'it-map' -import type { AddressSorter } from '@libp2p/interface-peer-store' -import type { ComponentMetricsTracker } from '@libp2p/interface-metrics' +import type { AddressSorter, PeerStore } from '@libp2p/interface-peer-store' +import type { ComponentMetricsTracker, Metrics } from '@libp2p/interface-metrics' import type { Dialer } from '@libp2p/interface-connection-manager' +import type { TransportManager } from '@libp2p/interface-transport' const log = logger('libp2p:dialer') @@ -87,8 +87,16 @@ export interface DialerInit { metrics?: ComponentMetricsTracker } +export interface DefaultDialerComponents { + peerId: PeerId + metrics?: Metrics + peerStore: PeerStore + transportManager: TransportManager + connectionGater: ConnectionGater +} + export class DefaultDialer implements Startable, Dialer { - private readonly components: Components + private readonly components: DefaultDialerComponents private readonly addressSorter: AddressSorter private readonly maxAddrsToDial: number private readonly timeout: number @@ -98,7 +106,7 @@ export class DefaultDialer implements Startable, Dialer { public pendingDialTargets: Map private started: boolean - constructor (components: Components, init: DialerInit = {}) { + constructor (components: DefaultDialerComponents, init: DialerInit = {}) { this.started = false this.addressSorter = init.addressSorter ?? publicAddressesFirst this.maxAddrsToDial = init.maxAddrsToDial ?? MAX_ADDRS_TO_DIAL @@ -114,7 +122,7 @@ export class DefaultDialer implements Startable, Dialer { this.pendingDialTargets = trackedMap({ component: METRICS_COMPONENT, metric: METRICS_PENDING_DIAL_TARGETS, - metrics: components.getMetrics() + metrics: components.metrics }) for (const [key, value] of Object.entries(init.resolvers ?? {})) { @@ -159,7 +167,7 @@ export class DefaultDialer implements Startable, Dialer { async dial (peer: PeerId | Multiaddr, options: AbortOptions = {}): Promise { const { id, multiaddrs } = getPeer(peer) - if (this.components.getPeerId().equals(id)) { + if (this.components.peerId.equals(id)) { throw errCode(new Error('Tried to dial self'), codes.ERR_DIALED_SELF) } @@ -167,10 +175,10 @@ export class DefaultDialer implements Startable, Dialer { if (multiaddrs != null && multiaddrs.length > 0) { log('storing multiaddrs %p', id, multiaddrs) - await this.components.getPeerStore().addressBook.add(id, multiaddrs) + await this.components.peerStore.addressBook.add(id, multiaddrs) } - if (await this.components.getConnectionGater().denyDialPeer(id)) { + if (await this.components.connectionGater.denyDialPeer(id)) { throw errCode(new Error('The dial request is blocked by gater.allowDialPeer'), codes.ERR_PEER_DIAL_INTERCEPTED) } @@ -235,9 +243,9 @@ export class DefaultDialer implements Startable, Dialer { const _resolve = this._resolve.bind(this) const addrs = await pipe( - await this.components.getPeerStore().addressBook.get(peer), + await this.components.peerStore.addressBook.get(peer), (source) => filter(source, async (address) => { - return !(await this.components.getConnectionGater().denyDialMultiaddr(peer, address.multiaddr)) + return !(await this.components.connectionGater.denyDialMultiaddr(peer, address.multiaddr)) }), // Sort addresses so, for example, we try certified public address first (source) => sort(source, this.addressSorter), @@ -247,7 +255,7 @@ export class DefaultDialer implements Startable, Dialer { } }, // Multiaddrs not supported by the available transports will be filtered out. - (source) => filter(source, (ma) => Boolean(this.components.getTransportManager().transportForMultiaddr(ma))), + (source) => filter(source, (ma) => Boolean(this.components.transportManager.transportForMultiaddr(ma))), (source) => map(source, (ma) => { if (peer.toString() === ma.getPeerId()) { return ma @@ -259,7 +267,7 @@ export class DefaultDialer implements Startable, Dialer { ) if (addrs.length > this.maxAddrsToDial) { - await this.components.getPeerStore().delete(peer) + await this.components.peerStore.delete(peer) throw errCode(new Error('dial with more addresses than allowed'), codes.ERR_TOO_MANY_ADDRESSES) } @@ -282,7 +290,7 @@ export class DefaultDialer implements Startable, Dialer { throw errCode(new Error('already aborted'), codes.ERR_ALREADY_ABORTED) } - return await this.components.getTransportManager().dial(addr, options).catch(err => { + return await this.components.transportManager.dial(addr, options).catch(err => { log.error('dial to %s failed', addr, err) throw err }) diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index 9223c7d695..f27241412c 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -11,15 +11,16 @@ import { codes } from '../errors.js' import { isPeerId, PeerId } from '@libp2p/interface-peer-id' import { setMaxListeners } from 'events' import type { Connection, MultiaddrConnection } from '@libp2p/interface-connection' -import type { ConnectionManager } from '@libp2p/interface-connection-manager' -import { Components, Initializable } from '@libp2p/components' +import type { ConnectionManager, Dialer } from '@libp2p/interface-connection-manager' import * as STATUS from '@libp2p/interface-connection/status' -import type { AddressSorter } from '@libp2p/interface-peer-store' +import type { AddressSorter, PeerStore } from '@libp2p/interface-peer-store' import { multiaddr, Multiaddr, Resolver } from '@multiformats/multiaddr' import { PeerMap } from '@libp2p/peer-collections' import { TimeoutController } from 'timeout-abort-controller' import { KEEP_ALIVE } from '@libp2p/interface-peer-store/tags' import { RateLimiterMemory } from 'rate-limiter-flexible' +import type { Metrics } from '@libp2p/interface-metrics' +import type { Upgrader } from '@libp2p/interface-transport' const log = logger('libp2p:connection-manager') @@ -166,11 +167,19 @@ export interface ConnectionManagerEvents { 'peer:disconnect': CustomEvent } +export interface DefaultConnectionManagerComponents { + peerId: PeerId + metrics?: Metrics + upgrader: Upgrader + peerStore: PeerStore + dialer: Dialer +} + /** * Responsible for managing known connections. */ -export class DefaultConnectionManager extends EventEmitter implements ConnectionManager, Startable, Initializable { - private components = new Components() +export class DefaultConnectionManager extends EventEmitter implements ConnectionManager, Startable { + private readonly components: DefaultConnectionManagerComponents private readonly opts: Required private readonly connections: Map private started: boolean @@ -184,7 +193,7 @@ export class DefaultConnectionManager extends EventEmitter { const keepAlivePeers: PeerId[] = [] - for (const peer of await this.components.getPeerStore().all()) { - const tags = await this.components.getPeerStore().getTags(peer.id) + for (const peer of await this.components.peerStore.all()) { + const tags = await this.components.peerStore.getTags(peer.id) const hasKeepAlive = tags.filter(tag => tag.name === KEEP_ALIVE).length > 0 if (hasKeepAlive) { @@ -392,8 +399,8 @@ export class DefaultConnectionManager extends EventEmitter('peer:disconnect', { detail: connection })) - this.components.getMetrics()?.onPeerDisconnected(connection.remotePeer) + this.components.metrics?.onPeerDisconnected(connection.remotePeer) } } @@ -559,7 +566,7 @@ export class DefaultConnectionManager extends EventEmitter limit) { - log('%s: limit exceeded: %p, %d/%d, pruning %d connection(s)', this.components.getPeerId(), name, value, limit, toPrune) + log('%s: limit exceeded: %p, %d/%d, pruning %d connection(s)', this.components.peerId, name, value, limit, toPrune) await this._pruneConnections(toPrune) } } @@ -659,7 +666,7 @@ export class DefaultConnectionManager extends EventEmitter { diff --git a/src/content-routing/index.ts b/src/content-routing/index.ts index 5bf28681e6..0834100048 100644 --- a/src/content-routing/index.ts +++ b/src/content-routing/index.ts @@ -12,18 +12,24 @@ import type { ContentRouting } from '@libp2p/interface-content-routing' import type { AbortOptions } from '@libp2p/interfaces' import type { Startable } from '@libp2p/interfaces/startable' import type { CID } from 'multiformats/cid' -import type { Components } from '@libp2p/components' +import type { PeerStore } from '@libp2p/interface-peer-store' +import type { DualDHT } from '@libp2p/interface-dht' export interface CompoundContentRoutingInit { routers: ContentRouting[] } +export interface CompoundContentRoutingComponents { + peerStore: PeerStore + dht?: DualDHT +} + export class CompoundContentRouting implements ContentRouting, Startable { private readonly routers: ContentRouting[] private started: boolean - private readonly components: Components + private readonly components: CompoundContentRoutingComponents - constructor (components: Components, init: CompoundContentRoutingInit) { + constructor (components: CompoundContentRoutingComponents, init: CompoundContentRoutingInit) { this.routers = init.routers ?? [] this.started = false this.components = components @@ -53,7 +59,7 @@ export class CompoundContentRouting implements ContentRouting, Startable { merge( ...this.routers.map(router => router.findProviders(key, options)) ), - (source) => storeAddresses(source, this.components.getPeerStore()), + (source) => storeAddresses(source, this.components.peerStore), (source) => uniquePeers(source), (source) => requirePeers(source) ) @@ -79,7 +85,7 @@ export class CompoundContentRouting implements ContentRouting, Startable { throw errCode(new Error(messages.NOT_STARTED_YET), codes.DHT_NOT_STARTED) } - const dht = this.components.getDHT() + const dht = this.components.dht if (dht != null) { await drain(dht.put(key, value, options)) @@ -95,7 +101,7 @@ export class CompoundContentRouting implements ContentRouting, Startable { throw errCode(new Error(messages.NOT_STARTED_YET), codes.DHT_NOT_STARTED) } - const dht = this.components.getDHT() + const dht = this.components.dht if (dht != null) { for await (const event of dht.get(key, options)) { @@ -121,7 +127,7 @@ export class CompoundContentRouting implements ContentRouting, Startable { } let gotValues = 0 - const dht = this.components.getDHT() + const dht = this.components.dht if (dht != null) { for await (const event of dht.get(key, options)) { diff --git a/src/fetch/index.ts b/src/fetch/index.ts index 23f616da51..92e7d75c28 100644 --- a/src/fetch/index.ts +++ b/src/fetch/index.ts @@ -7,14 +7,14 @@ import { PROTOCOL_NAME, PROTOCOL_VERSION } from './constants.js' import type { PeerId } from '@libp2p/interface-peer-id' import type { Startable } from '@libp2p/interfaces/startable' import type { Stream } from '@libp2p/interface-connection' -import type { IncomingStreamData } from '@libp2p/interface-registrar' -import type { Components } from '@libp2p/components' +import type { IncomingStreamData, Registrar } from '@libp2p/interface-registrar' import type { AbortOptions } from '@libp2p/interfaces' import { abortableDuplex } from 'abortable-iterator' import { pipe } from 'it-pipe' import first from 'it-first' import { TimeoutController } from 'timeout-abort-controller' import { setMaxListeners } from 'events' +import type { ConnectionManager } from '@libp2p/interface-connection-manager' const log = logger('libp2p:fetch') @@ -38,6 +38,11 @@ export interface LookupFunction { (key: string): Promise } +export interface FetchServiceComponents { + registrar: Registrar + connectionManager: ConnectionManager +} + /** * A simple libp2p protocol for requesting a value corresponding to a key from a peer. * Developers can register one or more lookup function for retrieving the value corresponding to @@ -46,12 +51,12 @@ export interface LookupFunction { */ export class FetchService implements Startable { public readonly protocol: string - private readonly components: Components + private readonly components: FetchServiceComponents private readonly lookupFunctions: Map private started: boolean private readonly init: FetchServiceInit - constructor (components: Components, init: FetchServiceInit) { + constructor (components: FetchServiceComponents, init: FetchServiceInit) { this.started = false this.components = components this.protocol = `/${init.protocolPrefix ?? 'libp2p'}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}` @@ -61,7 +66,7 @@ export class FetchService implements Startable { } async start () { - await this.components.getRegistrar().handle(this.protocol, (data) => { + await this.components.registrar.handle(this.protocol, (data) => { void this.handleMessage(data) .catch(err => { log.error(err) @@ -77,7 +82,7 @@ export class FetchService implements Startable { } async stop () { - await this.components.getRegistrar().unhandle(this.protocol) + await this.components.registrar.unhandle(this.protocol) this.started = false } @@ -91,7 +96,7 @@ export class FetchService implements Startable { async fetch (peer: PeerId, key: string, options: AbortOptions = {}): Promise { log('dialing %s to %p', this.protocol, peer) - const connection = await this.components.getConnectionManager().openConnection(peer, options) + const connection = await this.components.connectionManager.openConnection(peer, options) let timeoutController let signal = options.signal let stream: Stream | undefined diff --git a/src/identify/index.ts b/src/identify/index.ts index a90776add3..04da9c0600 100644 --- a/src/identify/index.ts +++ b/src/identify/index.ts @@ -18,15 +18,18 @@ import { MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION } from './consts.js' import { codes } from '../errors.js' -import type { IncomingStreamData } from '@libp2p/interface-registrar' +import type { IncomingStreamData, Registrar } from '@libp2p/interface-registrar' import type { Connection, Stream } from '@libp2p/interface-connection' import type { Startable } from '@libp2p/interfaces/startable' import { peerIdFromKeys } from '@libp2p/peer-id' -import type { Components } from '@libp2p/components' import { TimeoutController } from 'timeout-abort-controller' import type { AbortOptions } from '@libp2p/interfaces' import { abortableDuplex } from 'abortable-iterator' import { setMaxListeners } from 'events' +import type { ConnectionManager } from '@libp2p/interface-connection-manager' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { PeerStore } from '@libp2p/interface-peer-store' +import type { AddressManager } from '@libp2p/interface-address-manager' const log = logger('libp2p:identify') @@ -65,8 +68,16 @@ export interface IdentifyServiceInit { maxPushOutgoingStreams: number } +export interface IdentifyServiceComponents { + peerId: PeerId + peerStore: PeerStore + connectionManager: ConnectionManager + registrar: Registrar + addressManager: AddressManager +} + export class IdentifyService implements Startable { - private readonly components: Components + private readonly components: IdentifyServiceComponents private readonly identifyProtocolStr: string private readonly identifyPushProtocolStr: string private readonly host: { @@ -77,7 +88,7 @@ export class IdentifyService implements Startable { private readonly init: IdentifyServiceInit private started: boolean - constructor (components: Components, init: IdentifyServiceInit) { + constructor (components: IdentifyServiceComponents, init: IdentifyServiceInit) { this.components = components this.started = false this.init = init @@ -92,25 +103,25 @@ export class IdentifyService implements Startable { } // When a new connection happens, trigger identify - this.components.getConnectionManager().addEventListener('peer:connect', (evt) => { + this.components.connectionManager.addEventListener('peer:connect', (evt) => { const connection = evt.detail this.identify(connection).catch(log.error) }) // When self multiaddrs change, trigger identify-push - this.components.getPeerStore().addEventListener('change:multiaddrs', (evt) => { + this.components.peerStore.addEventListener('change:multiaddrs', (evt) => { const { peerId } = evt.detail - if (this.components.getPeerId().equals(peerId)) { + if (this.components.peerId.equals(peerId)) { void this.pushToPeerStore().catch(err => log.error(err)) } }) // When self protocols change, trigger identify-push - this.components.getPeerStore().addEventListener('change:protocols', (evt) => { + this.components.peerStore.addEventListener('change:protocols', (evt) => { const { peerId } = evt.detail - if (this.components.getPeerId().equals(peerId)) { + if (this.components.peerId.equals(peerId)) { void this.pushToPeerStore().catch(err => log.error(err)) } }) @@ -125,10 +136,10 @@ export class IdentifyService implements Startable { return } - await this.components.getPeerStore().metadataBook.setValue(this.components.getPeerId(), 'AgentVersion', uint8ArrayFromString(this.host.agentVersion)) - await this.components.getPeerStore().metadataBook.setValue(this.components.getPeerId(), 'ProtocolVersion', uint8ArrayFromString(this.host.protocolVersion)) + await this.components.peerStore.metadataBook.setValue(this.components.peerId, 'AgentVersion', uint8ArrayFromString(this.host.agentVersion)) + await this.components.peerStore.metadataBook.setValue(this.components.peerId, 'ProtocolVersion', uint8ArrayFromString(this.host.protocolVersion)) - await this.components.getRegistrar().handle(this.identifyProtocolStr, (data) => { + await this.components.registrar.handle(this.identifyProtocolStr, (data) => { void this._handleIdentify(data).catch(err => { log.error(err) }) @@ -136,7 +147,7 @@ export class IdentifyService implements Startable { maxInboundStreams: this.init.maxInboundStreams, maxOutboundStreams: this.init.maxOutboundStreams }) - await this.components.getRegistrar().handle(this.identifyPushProtocolStr, (data) => { + await this.components.registrar.handle(this.identifyPushProtocolStr, (data) => { void this._handlePush(data).catch(err => { log.error(err) }) @@ -149,8 +160,8 @@ export class IdentifyService implements Startable { } async stop () { - await this.components.getRegistrar().unhandle(this.identifyProtocolStr) - await this.components.getRegistrar().unhandle(this.identifyPushProtocolStr) + await this.components.registrar.unhandle(this.identifyProtocolStr) + await this.components.registrar.unhandle(this.identifyPushProtocolStr) this.started = false } @@ -159,9 +170,9 @@ export class IdentifyService implements Startable { * Send an Identify Push update to the list of connections */ async push (connections: Connection[]): Promise { - const signedPeerRecord = await this.components.getPeerStore().addressBook.getRawEnvelope(this.components.getPeerId()) - const listenAddrs = this.components.getAddressManager().getAddresses().map((ma) => ma.bytes) - const protocols = await this.components.getPeerStore().protoBook.get(this.components.getPeerId()) + const signedPeerRecord = await this.components.peerStore.addressBook.getRawEnvelope(this.components.peerId) + const listenAddrs = this.components.addressManager.getAddresses().map((ma) => ma.bytes) + const protocols = await this.components.peerStore.protoBook.get(this.components.peerId) const pushes = connections.map(async connection => { let stream: Stream | undefined @@ -216,9 +227,9 @@ export class IdentifyService implements Startable { const connections: Connection[] = [] - for (const conn of this.components.getConnectionManager().getConnections()) { + for (const conn of this.components.connectionManager.getConnections()) { const peerId = conn.remotePeer - const peer = await this.components.getPeerStore().get(peerId) + const peer = await this.components.peerStore.get(peerId) if (!peer.protocols.includes(this.identifyPushProtocolStr)) { continue @@ -311,7 +322,7 @@ export class IdentifyService implements Startable { throw errCode(new Error('identified peer does not match the expected peer'), codes.ERR_INVALID_PEER) } - if (this.components.getPeerId().equals(id)) { + if (this.components.peerId.equals(id)) { throw errCode(new Error('identified peer is our own peer id?'), codes.ERR_INVALID_PEER) } @@ -328,15 +339,15 @@ export class IdentifyService implements Startable { throw errCode(new Error('identified peer does not match the expected peer'), codes.ERR_INVALID_PEER) } - if (await this.components.getPeerStore().addressBook.consumePeerRecord(envelope)) { - await this.components.getPeerStore().protoBook.set(id, protocols) + if (await this.components.peerStore.addressBook.consumePeerRecord(envelope)) { + await this.components.peerStore.protoBook.set(id, protocols) if (agentVersion != null) { - await this.components.getPeerStore().metadataBook.setValue(id, 'AgentVersion', uint8ArrayFromString(agentVersion)) + await this.components.peerStore.metadataBook.setValue(id, 'AgentVersion', uint8ArrayFromString(agentVersion)) } if (protocolVersion != null) { - await this.components.getPeerStore().metadataBook.setValue(id, 'ProtocolVersion', uint8ArrayFromString(protocolVersion)) + await this.components.peerStore.metadataBook.setValue(id, 'ProtocolVersion', uint8ArrayFromString(protocolVersion)) } log('identify completed for peer %p and protocols %o', id, protocols) @@ -354,26 +365,26 @@ export class IdentifyService implements Startable { // LEGACY: Update peers data in PeerStore try { - await this.components.getPeerStore().addressBook.set(id, listenAddrs.map((addr) => multiaddr(addr))) + await this.components.peerStore.addressBook.set(id, listenAddrs.map((addr) => multiaddr(addr))) } catch (err: any) { log.error('received invalid addrs', err) } - await this.components.getPeerStore().protoBook.set(id, protocols) + await this.components.peerStore.protoBook.set(id, protocols) if (agentVersion != null) { - await this.components.getPeerStore().metadataBook.setValue(id, 'AgentVersion', uint8ArrayFromString(agentVersion)) + await this.components.peerStore.metadataBook.setValue(id, 'AgentVersion', uint8ArrayFromString(agentVersion)) } if (protocolVersion != null) { - await this.components.getPeerStore().metadataBook.setValue(id, 'ProtocolVersion', uint8ArrayFromString(protocolVersion)) + await this.components.peerStore.metadataBook.setValue(id, 'ProtocolVersion', uint8ArrayFromString(protocolVersion)) } log('identify completed for peer %p and protocols %o', id, protocols) // TODO: Add and score our observed addr log('received observed address of %s', cleanObservedAddr?.toString()) - // this.components.getAddressManager().addObservedAddr(observedAddr) + // this.components.addressManager.addObservedAddr(observedAddr) } /** @@ -390,19 +401,19 @@ export class IdentifyService implements Startable { } catch {} try { - const publicKey = this.components.getPeerId().publicKey ?? new Uint8Array(0) - const peerData = await this.components.getPeerStore().get(this.components.getPeerId()) - const multiaddrs = this.components.getAddressManager().getAddresses().map(ma => ma.decapsulateCode(protocols('p2p').code)) + const publicKey = this.components.peerId.publicKey ?? new Uint8Array(0) + const peerData = await this.components.peerStore.get(this.components.peerId) + const multiaddrs = this.components.addressManager.getAddresses().map(ma => ma.decapsulateCode(protocols('p2p').code)) let signedPeerRecord = peerData.peerRecordEnvelope if (multiaddrs.length > 0 && signedPeerRecord == null) { const peerRecord = new PeerRecord({ - peerId: this.components.getPeerId(), + peerId: this.components.peerId, multiaddrs }) - const envelope = await RecordEnvelope.seal(peerRecord, this.components.getPeerId()) - await this.components.getPeerStore().addressBook.consumePeerRecord(envelope) + const envelope = await RecordEnvelope.seal(peerRecord, this.components.peerId) + await this.components.peerStore.addressBook.consumePeerRecord(envelope) signedPeerRecord = envelope.marshal().subarray() } @@ -475,7 +486,7 @@ export class IdentifyService implements Startable { const id = connection.remotePeer - if (this.components.getPeerId().equals(id)) { + if (this.components.peerId.equals(id)) { log('received push from ourselves?') return } @@ -488,10 +499,10 @@ export class IdentifyService implements Startable { try { const envelope = await RecordEnvelope.openAndCertify(message.signedPeerRecord, PeerRecord.DOMAIN) - if (await this.components.getPeerStore().addressBook.consumePeerRecord(envelope)) { + if (await this.components.peerStore.addressBook.consumePeerRecord(envelope)) { log('consumed signedPeerRecord sent in push') - await this.components.getPeerStore().protoBook.set(id, message.protocols) + await this.components.peerStore.protoBook.set(id, message.protocols) return } else { log('failed to consume signedPeerRecord sent in push') @@ -505,7 +516,7 @@ export class IdentifyService implements Startable { // LEGACY: Update peers data in PeerStore try { - await this.components.getPeerStore().addressBook.set(id, + await this.components.peerStore.addressBook.set(id, message.listenAddrs.map((addr) => multiaddr(addr))) } catch (err: any) { log.error('received invalid addrs', err) @@ -513,7 +524,7 @@ export class IdentifyService implements Startable { // Update the protocols try { - await this.components.getPeerStore().protoBook.set(id, message.protocols) + await this.components.peerStore.protoBook.set(id, message.protocols) } catch (err: any) { log.error('received invalid protocols', err) } diff --git a/src/index.ts b/src/index.ts index 0ba5ad7ba6..fe12abba54 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,6 +26,7 @@ import type { KeyChain } from './keychain/index.js' import type { ConnectionManagerInit } from './connection-manager/index.js' import type { PingServiceInit } from './ping/index.js' import type { FetchServiceInit } from './fetch/index.js' +import type { Components } from './components.js' export interface PersistentPeerStoreOptions { threshold?: number @@ -113,15 +114,15 @@ export interface Libp2pInit { ping: PingServiceInit fetch: FetchServiceInit - transports: Transport[] - streamMuxers?: StreamMuxerFactory[] - connectionEncryption?: ConnectionEncrypter[] - peerDiscovery?: PeerDiscovery[] - peerRouters?: PeerRouting[] - contentRouters?: ContentRouting[] - dht?: DualDHT - pubsub?: PubSub - connectionProtector?: ConnectionProtector + transports: Array<(components: Components) => Transport> + streamMuxers?: Array<(components: Components) => StreamMuxerFactory> + connectionEncryption?: Array<(components: Components) => ConnectionEncrypter> + peerDiscovery?: Array<(components: Components) => PeerDiscovery> + peerRouters?: Array<(components: Components) => PeerRouting> + contentRouters?: Array<(components: Components) => ContentRouting> + dht?: (components: Components) => DualDHT + pubsub?: (components: Components) => PubSub + connectionProtector?: (components: Components) => ConnectionProtector } export interface Libp2pEvents { diff --git a/src/insecure/index.ts b/src/insecure/index.ts index abd859afb0..bdf3171660 100644 --- a/src/insecure/index.ts +++ b/src/insecure/index.ts @@ -92,7 +92,7 @@ async function encrypt (localId: PeerId, conn: Duplex, remoteId?: Pe } } -export class Plaintext implements ConnectionEncrypter { +class Plaintext implements ConnectionEncrypter { public protocol: string = PROTOCOL async secureInbound (localId: PeerId, conn: Duplex, remoteId?: PeerId): Promise { @@ -103,3 +103,7 @@ export class Plaintext implements ConnectionEncrypter { return await encrypt(localId, conn, remoteId) } } + +export function plaintext (): () => ConnectionEncrypter { + return () => new Plaintext() +} diff --git a/src/keychain/index.ts b/src/keychain/index.ts index df04486452..68ddf8b6a9 100644 --- a/src/keychain/index.ts +++ b/src/keychain/index.ts @@ -11,9 +11,9 @@ import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { generateKeyPair, importKey, unmarshalPrivateKey } from '@libp2p/crypto/keys' import type { PeerId } from '@libp2p/interface-peer-id' -import type { Components } from '@libp2p/components' import { pbkdf2, randomBytes } from '@libp2p/crypto' import type { Startable } from '@libp2p/interfaces/dist/src/startable' +import type { Datastore } from 'interface-datastore' const log = logger('libp2p:keychain') @@ -103,6 +103,11 @@ function DsInfoName (name: string) { return new Key(infoPrefix + name) } +export interface KeyChainComponents { + peerId: PeerId + datastore: Datastore +} + /** * Manages the lifecycle of a key. Keys are encrypted at rest using PKCS #8. * @@ -112,14 +117,14 @@ function DsInfoName (name: string) { * */ export class KeyChain implements Startable { - private readonly components: Components + private readonly components: KeyChainComponents private init: KeyChainInit private started: boolean /** * Creates a new instance of a key chain */ - constructor (components: Components, init: KeyChainInit) { + constructor (components: KeyChainComponents, init: KeyChainInit) { this.components = components this.init = mergeOptions(defaultOptions, init) @@ -157,8 +162,8 @@ export class KeyChain implements Startable { async start () { const dsname = DsInfoName('self') - if (!(await this.components.getDatastore().has(dsname))) { - await this.importPeer('self', this.components.getPeerId()) + if (!(await this.components.datastore.has(dsname))) { + await this.importPeer('self', this.components.peerId) } this.started = true @@ -229,7 +234,7 @@ export class KeyChain implements Startable { } const dsname = DsName(name) - const exists = await this.components.getDatastore().has(dsname) + const exists = await this.components.datastore.has(dsname) if (exists) { await randomDelay() throw errCode(new Error('Key name already exists'), codes.ERR_KEY_ALREADY_EXISTS) @@ -262,7 +267,7 @@ export class KeyChain implements Startable { name: name, id: kid } - const batch = this.components.getDatastore().batch() + const batch = this.components.datastore.batch() batch.put(dsname, uint8ArrayFromString(pem)) batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo))) @@ -286,7 +291,7 @@ export class KeyChain implements Startable { } const info = [] - for await (const value of this.components.getDatastore().query(query)) { + for await (const value of this.components.datastore.query(query)) { info.push(JSON.parse(uint8ArrayToString(value.value))) } @@ -320,7 +325,7 @@ export class KeyChain implements Startable { const dsname = DsInfoName(name) try { - const res = await this.components.getDatastore().get(dsname) + const res = await this.components.datastore.get(dsname) return JSON.parse(uint8ArrayToString(res)) } catch (err: any) { await randomDelay() @@ -342,7 +347,7 @@ export class KeyChain implements Startable { } const dsname = DsName(name) const keyInfo = await this.findKeyByName(name) - const batch = this.components.getDatastore().batch() + const batch = this.components.datastore.batch() batch.delete(dsname) batch.delete(DsInfoName(name)) await batch.commit() @@ -370,19 +375,19 @@ export class KeyChain implements Startable { const oldInfoName = DsInfoName(oldName) const newInfoName = DsInfoName(newName) - const exists = await this.components.getDatastore().has(newDsname) + const exists = await this.components.datastore.has(newDsname) if (exists) { await randomDelay() throw errCode(new Error(`Key '${newName}' already exists`), codes.ERR_KEY_ALREADY_EXISTS) } try { - const pem = await this.components.getDatastore().get(oldDsname) - const res = await this.components.getDatastore().get(oldInfoName) + const pem = await this.components.datastore.get(oldDsname) + const res = await this.components.datastore.get(oldInfoName) const keyInfo = JSON.parse(uint8ArrayToString(res)) keyInfo.name = newName - const batch = this.components.getDatastore().batch() + const batch = this.components.datastore.batch() batch.put(newDsname, pem) batch.put(newInfoName, uint8ArrayFromString(JSON.stringify(keyInfo))) batch.delete(oldDsname) @@ -410,7 +415,7 @@ export class KeyChain implements Startable { const dsname = DsName(name) try { - const res = await this.components.getDatastore().get(dsname) + const res = await this.components.datastore.get(dsname) const pem = uint8ArrayToString(res) const cached = privates.get(this) @@ -445,7 +450,7 @@ export class KeyChain implements Startable { throw errCode(new Error('PEM encoded key is required'), codes.ERR_PEM_REQUIRED) } const dsname = DsName(name) - const exists = await this.components.getDatastore().has(dsname) + const exists = await this.components.datastore.has(dsname) if (exists) { await randomDelay() throw errCode(new Error(`Key '${name}' already exists`), codes.ERR_KEY_ALREADY_EXISTS) @@ -479,7 +484,7 @@ export class KeyChain implements Startable { name: name, id: kid } - const batch = this.components.getDatastore().batch() + const batch = this.components.datastore.batch() batch.put(dsname, uint8ArrayFromString(pem)) batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo))) await batch.commit() @@ -505,7 +510,7 @@ export class KeyChain implements Startable { const privateKey = await unmarshalPrivateKey(peer.privateKey) const dsname = DsName(name) - const exists = await this.components.getDatastore().has(dsname) + const exists = await this.components.datastore.has(dsname) if (exists) { await randomDelay() throw errCode(new Error(`Key '${name}' already exists`), codes.ERR_KEY_ALREADY_EXISTS) @@ -523,7 +528,7 @@ export class KeyChain implements Startable { name: name, id: peer.toString() } - const batch = this.components.getDatastore().batch() + const batch = this.components.datastore.batch() batch.put(dsname, uint8ArrayFromString(pem)) batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo))) await batch.commit() @@ -545,7 +550,7 @@ export class KeyChain implements Startable { try { const dsname = DsName(name) - const res = await this.components.getDatastore().get(dsname) + const res = await this.components.datastore.get(dsname) return uint8ArrayToString(res) } catch (err: any) { await randomDelay() @@ -590,14 +595,14 @@ export class KeyChain implements Startable { privates.set(this, { dek: newDek }) const keys = await this.listKeys() for (const key of keys) { - const res = await this.components.getDatastore().get(DsName(key.name)) + const res = await this.components.datastore.get(DsName(key.name)) const pem = uint8ArrayToString(res) const privateKey = await importKey(pem, oldDek) const password = newDek.toString() const keyAsPEM = await privateKey.export(password) // Update stored key - const batch = this.components.getDatastore().batch() + const batch = this.components.datastore.batch() const keyInfo = { name: key.name, id: key.id diff --git a/src/libp2p.ts b/src/libp2p.ts index 00a9748881..fe42acfd2d 100644 --- a/src/libp2p.ts +++ b/src/libp2p.ts @@ -26,7 +26,8 @@ import { PeerRecordUpdater } from './peer-record-updater.js' import { DHTPeerRouting } from './dht/dht-peer-routing.js' import { PersistentPeerStore } from '@libp2p/peer-store' import { DHTContentRouting } from './dht/dht-content-routing.js' -import { Initializable, Components, isInitializable } from '@libp2p/components' +import { DefaultComponents } from './components.js' +import type { Components } from './components.js' import type { PeerId } from '@libp2p/interface-peer-id' import type { Connection } from '@libp2p/interface-connection' import type { PeerRouting } from '@libp2p/interface-peer-routing' @@ -70,15 +71,13 @@ export class Libp2pNode extends EventEmitter implements Libp2p { private started: boolean private readonly services: Startable[] - private readonly initializables: Initializable[] constructor (init: Libp2pInit) { super() - this.initializables = [] this.started = false this.peerId = init.peerId - this.components = new Components({ + const components = this.components = new DefaultComponents({ peerId: init.peerId, datastore: init.datastore ?? new MemoryDatastore(), connectionGater: { @@ -94,21 +93,21 @@ export class Libp2pNode extends EventEmitter implements Libp2p { ...init.connectionGater } }) - this.components.setPeerStore(new PersistentPeerStore({ - addressFilter: this.components.getConnectionGater().filterMultiaddrForPeer, + components.peerStore = new PersistentPeerStore(components, { + addressFilter: this.components.connectionGater.filterMultiaddrForPeer, ...init.peerStore - })) + }) this.services = [ - this.components + components ] // Create Metrics if (init.metrics.enabled) { - this.metrics = this.components.setMetrics(new DefaultMetrics(init.metrics)) + this.metrics = this.components.metrics = new DefaultMetrics(init.metrics) } - this.peerStore = this.components.getPeerStore() + this.peerStore = this.components.peerStore this.peerStore.addEventListener('peer', evt => { const { detail: peerData } = evt @@ -118,30 +117,30 @@ export class Libp2pNode extends EventEmitter implements Libp2p { // Set up connection protector if configured if (init.connectionProtector != null) { - this.components.setConnectionProtector(init.connectionProtector) + this.components.connectionProtector = init.connectionProtector(components) } // Set up the Upgrader - this.components.setUpgrader(new DefaultUpgrader(this.components, { - connectionEncryption: (init.connectionEncryption ?? []).map(component => this.configureComponent(component)), - muxers: (init.streamMuxers ?? []).map(component => this.configureComponent(component)), + this.components.upgrader = new DefaultUpgrader(this.components, { + connectionEncryption: (init.connectionEncryption ?? []).map(fn => this.configureComponent(fn(this.components))), + muxers: (init.streamMuxers ?? []).map(fn => this.configureComponent(fn(this.components))), inboundUpgradeTimeout: init.connectionManager.inboundUpgradeTimeout - })) + }) // Create the dialer - this.components.setDialer(new DefaultDialer(this.components, init.connectionManager)) + this.components.dialer = new DefaultDialer(this.components, init.connectionManager) // Create the Connection Manager - this.connectionManager = this.components.setConnectionManager(new DefaultConnectionManager(init.connectionManager)) + this.connectionManager = this.components.connectionManager = new DefaultConnectionManager(this.components, init.connectionManager) // Create the Registrar - this.registrar = this.components.setRegistrar(new DefaultRegistrar(this.components)) + this.registrar = this.components.registrar = new DefaultRegistrar(this.components) // Setup the transport manager - this.components.setTransportManager(new DefaultTransportManager(this.components, init.transportManager)) + this.components.transportManager = new DefaultTransportManager(this.components, init.transportManager) // Addresses {listen, announce, noAnnounce} - this.components.setAddressManager(new DefaultAddressManager(this.components, init.addresses)) + this.components.addressManager = new DefaultAddressManager(this.components, init.addresses) // update our peer record when addresses change this.configureComponent(new PeerRecordUpdater(this.components)) @@ -162,8 +161,8 @@ export class Libp2pNode extends EventEmitter implements Libp2p { // Create the Nat Manager this.services.push(new NatManager(this.components, init.nat)) - init.transports.forEach((transport) => { - this.components.getTransportManager().add(this.configureComponent(transport)) + init.transports.forEach((fn) => { + this.components.transportManager.add(this.configureComponent(fn(this.components))) }) // Attach stream multiplexers @@ -177,14 +176,14 @@ export class Libp2pNode extends EventEmitter implements Libp2p { // dht provided components (peerRouting, contentRouting, dht) if (init.dht != null) { - this.dht = this.components.setDHT(init.dht) + this.dht = this.components.dht = init.dht(this.components) } else { this.dht = new DummyDHT() } // Create pubsub if provided if (init.pubsub != null) { - this.pubsub = this.components.setPubSub(init.pubsub) + this.pubsub = this.components.pubsub = init.pubsub(this.components) } else { this.pubsub = new DummyPubSub() } @@ -192,7 +191,7 @@ export class Libp2pNode extends EventEmitter implements Libp2p { // Attach remaining APIs // peer and content routing will automatically get modules from _modules and _dht - const peerRouters: PeerRouting[] = (init.peerRouters ?? []).map(component => this.configureComponent(component)) + const peerRouters: PeerRouting[] = (init.peerRouters ?? []).map(fn => this.configureComponent(fn(this.components))) if (init.dht != null) { // add dht to routers @@ -204,24 +203,24 @@ export class Libp2pNode extends EventEmitter implements Libp2p { }) } - this.peerRouting = this.components.setPeerRouting(this.configureComponent(new DefaultPeerRouting(this.components, { + this.peerRouting = this.components.peerRouting = this.configureComponent(new DefaultPeerRouting(this.components, { ...init.peerRouting, routers: peerRouters - }))) + })) - const contentRouters: ContentRouting[] = (init.contentRouters ?? []).map(component => this.configureComponent(component)) + const contentRouters: ContentRouting[] = (init.contentRouters ?? []).map(fn => this.configureComponent(fn(this.components))) if (init.dht != null) { // add dht to routers contentRouters.push(this.configureComponent(new DHTContentRouting(this.dht))) } - this.contentRouting = this.components.setContentRouting(this.configureComponent(new CompoundContentRouting(this.components, { + this.contentRouting = this.components.contentRouting = this.configureComponent(new CompoundContentRouting(this.components, { routers: contentRouters - }))) + })) if (init.relay.enabled) { - this.components.getTransportManager().add(this.configureComponent(new Circuit(init.relay))) + this.components.transportManager.add(this.configureComponent(new Circuit(this.components, init.relay))) this.configureComponent(new Relay(this.components, { addressSorter: init.connectionManager.addressSorter, @@ -238,8 +237,8 @@ export class Libp2pNode extends EventEmitter implements Libp2p { })) // Discovery modules - for (const service of init.peerDiscovery ?? []) { - this.configureComponent(service) + for (const fn of init.peerDiscovery ?? []) { + const service = this.configureComponent(fn(this.components)) service.addEventListener('peer', (evt) => { this.onDiscoveryPeer(evt) @@ -252,10 +251,6 @@ export class Libp2pNode extends EventEmitter implements Libp2p { this.services.push(component) } - if (isInitializable(component)) { - this.initializables.push(component) - } - return component } @@ -272,11 +267,6 @@ export class Libp2pNode extends EventEmitter implements Libp2p { log('libp2p is starting') try { - // Set available components on all modules interested in components - this.initializables.forEach(obj => { - obj.init(this.components) - }) - await Promise.all( this.services.map(async service => { if (service.beforeStart != null) { @@ -346,13 +336,13 @@ export class Libp2pNode extends EventEmitter implements Libp2p { } getConnections (peerId?: PeerId): Connection[] { - return this.components.getConnectionManager().getConnections(peerId) + return this.components.connectionManager.getConnections(peerId) } getPeers (): PeerId[] { const peerSet = new PeerSet() - for (const conn of this.components.getConnectionManager().getConnections()) { + for (const conn of this.components.connectionManager.getConnections()) { peerSet.add(conn.remotePeer) } @@ -362,9 +352,9 @@ export class Libp2pNode extends EventEmitter implements Libp2p { async dial (peer: PeerId | Multiaddr, options: AbortOptions = {}): Promise { const { id, multiaddrs } = getPeer(peer) - await this.components.getPeerStore().addressBook.add(id, multiaddrs) + await this.components.peerStore.addressBook.add(id, multiaddrs) - return await this.components.getConnectionManager().openConnection(id, options) + return await this.components.connectionManager.openConnection(id, options) } async dialProtocol (peer: PeerId | Multiaddr, protocols: string | string[], options: AbortOptions = {}) { @@ -384,13 +374,13 @@ export class Libp2pNode extends EventEmitter implements Libp2p { } getMultiaddrs (): Multiaddr[] { - return this.components.getAddressManager().getAddresses() + return this.components.addressManager.getAddresses() } async hangUp (peer: PeerId | Multiaddr | string): Promise { const { id } = getPeer(peer) - await this.components.getConnectionManager().closeConnections(id) + await this.components.connectionManager.closeConnections(id) } /** @@ -436,7 +426,7 @@ export class Libp2pNode extends EventEmitter implements Libp2p { const { id, multiaddrs } = getPeer(peer) if (multiaddrs != null) { - await this.components.getPeerStore().addressBook.add(id, multiaddrs) + await this.components.peerStore.addressBook.add(id, multiaddrs) } return await this.fetchService.fetch(id, key, options) @@ -446,7 +436,7 @@ export class Libp2pNode extends EventEmitter implements Libp2p { const { id, multiaddrs } = getPeer(peer) if (multiaddrs.length > 0) { - await this.components.getPeerStore().addressBook.add(id, multiaddrs) + await this.components.peerStore.addressBook.add(id, multiaddrs) } return await this.pingService.ping(id, options) @@ -459,7 +449,7 @@ export class Libp2pNode extends EventEmitter implements Libp2p { await Promise.all( protocols.map(async protocol => { - await this.components.getRegistrar().handle(protocol, handler, options) + await this.components.registrar.handle(protocol, handler, options) }) ) } @@ -471,7 +461,7 @@ export class Libp2pNode extends EventEmitter implements Libp2p { await Promise.all( protocols.map(async protocol => { - await this.components.getRegistrar().unhandle(protocol) + await this.components.registrar.unhandle(protocol) }) ) } @@ -489,11 +479,11 @@ export class Libp2pNode extends EventEmitter implements Libp2p { } if (peer.multiaddrs.length > 0) { - void this.components.getPeerStore().addressBook.add(peer.id, peer.multiaddrs).catch(err => log.error(err)) + void this.components.peerStore.addressBook.add(peer.id, peer.multiaddrs).catch(err => log.error(err)) } if (peer.protocols.length > 0) { - void this.components.getPeerStore().protoBook.set(peer.id, peer.protocols).catch(err => log.error(err)) + void this.components.peerStore.protoBook.set(peer.id, peer.protocols).catch(err => log.error(err)) } this.dispatchEvent(new CustomEvent('peer:discovery', { detail: peer })) diff --git a/src/nat-manager.ts b/src/nat-manager.ts index 6da6639ec4..31a8f9900f 100644 --- a/src/nat-manager.ts +++ b/src/nat-manager.ts @@ -8,7 +8,9 @@ import errCode from 'err-code' import { codes } from './errors.js' import { isLoopback } from '@libp2p/utils/multiaddr/is-loopback' import type { Startable } from '@libp2p/interfaces/startable' -import type { Components } from '@libp2p/components' +import type { TransportManager } from '@libp2p/interface-transport' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { AddressManager } from '@libp2p/interface-address-manager' const log = logger('libp2p:nat') const DEFAULT_TTL = 7200 @@ -61,8 +63,14 @@ export interface NatManagerInit { gateway?: string } +export interface NatManagerComponents { + peerId: PeerId + transportManager: TransportManager + addressManager: AddressManager +} + export class NatManager implements Startable { - private readonly components: Components + private readonly components: NatManagerComponents private readonly enabled: boolean private readonly externalAddress?: string private readonly localAddress?: string @@ -73,14 +81,14 @@ export class NatManager implements Startable { private started: boolean private client?: NatAPI - constructor (components: Components, init: NatManagerInit) { + constructor (components: NatManagerComponents, init: NatManagerInit) { this.components = components this.started = false this.enabled = init.enabled this.externalAddress = init.externalAddress this.localAddress = init.localAddress - this.description = init.description ?? `${pkg.name}@${pkg.version} ${this.components.getPeerId().toString()}` + this.description = init.description ?? `${pkg.name}@${pkg.version} ${this.components.peerId.toString()}` this.ttl = init.ttl ?? DEFAULT_TTL this.keepAlive = init.keepAlive ?? true this.gateway = init.gateway @@ -116,7 +124,7 @@ export class NatManager implements Startable { } async _start () { - const addrs = this.components.getTransportManager().getAddrs() + const addrs = this.components.transportManager.getAddrs() for (const addr of addrs) { // try to open uPnP ports for each thin waist address @@ -157,7 +165,7 @@ export class NatManager implements Startable { protocol: transport.toUpperCase() === 'TCP' ? 'TCP' : 'UDP' }) - this.components.getAddressManager().addObservedAddr(fromNodeAddress({ + this.components.addressManager.addObservedAddr(fromNodeAddress({ family: 4, address: publicIp, port: publicPort diff --git a/src/peer-record-updater.ts b/src/peer-record-updater.ts index 0901c15d46..d93c69c63d 100644 --- a/src/peer-record-updater.ts +++ b/src/peer-record-updater.ts @@ -1,16 +1,26 @@ import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record' -import type { Components } from '@libp2p/components' import type { Startable } from '@libp2p/interfaces/startable' import { logger } from '@libp2p/logger' import { protocols } from '@multiformats/multiaddr' +import type { TransportManager } from '@libp2p/interface-transport' +import type { AddressManager } from '@libp2p/interface-address-manager' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { PeerStore } from '@libp2p/interface-peer-store' const log = logger('libp2p:peer-record-updater') +export interface PeerRecordUpdaterComponents { + peerId: PeerId + peerStore: PeerStore + transportManager: TransportManager + addressManager: AddressManager +} + export class PeerRecordUpdater implements Startable { - private readonly components: Components + private readonly components: PeerRecordUpdaterComponents private started: boolean - constructor (components: Components) { + constructor (components: PeerRecordUpdaterComponents) { this.components = components this.started = false this.update = this.update.bind(this) @@ -22,16 +32,16 @@ export class PeerRecordUpdater implements Startable { async start () { this.started = true - this.components.getTransportManager().addEventListener('listener:listening', this.update) - this.components.getTransportManager().addEventListener('listener:close', this.update) - this.components.getAddressManager().addEventListener('change:addresses', this.update) + this.components.transportManager.addEventListener('listener:listening', this.update) + this.components.transportManager.addEventListener('listener:close', this.update) + this.components.addressManager.addEventListener('change:addresses', this.update) } async stop () { this.started = false - this.components.getTransportManager().removeEventListener('listener:listening', this.update) - this.components.getTransportManager().removeEventListener('listener:close', this.update) - this.components.getAddressManager().removeEventListener('change:addresses', this.update) + this.components.transportManager.removeEventListener('listener:listening', this.update) + this.components.transportManager.removeEventListener('listener:close', this.update) + this.components.addressManager.removeEventListener('change:addresses', this.update) } /** @@ -41,12 +51,12 @@ export class PeerRecordUpdater implements Startable { Promise.resolve() .then(async () => { const peerRecord = new PeerRecord({ - peerId: this.components.getPeerId(), - multiaddrs: this.components.getAddressManager().getAddresses().map(ma => ma.decapsulateCode(protocols('p2p').code)) + peerId: this.components.peerId, + multiaddrs: this.components.addressManager.getAddresses().map(ma => ma.decapsulateCode(protocols('p2p').code)) }) - const envelope = await RecordEnvelope.seal(peerRecord, this.components.getPeerId()) - await this.components.getPeerStore().addressBook.consumePeerRecord(envelope) + const envelope = await RecordEnvelope.seal(peerRecord, this.components.peerId) + await this.components.peerStore.addressBook.consumePeerRecord(envelope) }) .catch(err => { log.error('Could not update self peer record: %o', err) diff --git a/src/peer-routing.ts b/src/peer-routing.ts index f380e0d76a..9fc29dffe9 100644 --- a/src/peer-routing.ts +++ b/src/peer-routing.ts @@ -23,7 +23,7 @@ import type { PeerRouting } from '@libp2p/interface-peer-routing' import type { AbortOptions } from '@libp2p/interfaces' import type { Startable } from '@libp2p/interfaces/startable' import type { PeerInfo } from '@libp2p/interface-peer-info' -import type { Components } from '@libp2p/components' +import type { PeerStore } from '@libp2p/interface-peer-store' const log = logger('libp2p:peer-routing') @@ -54,15 +54,20 @@ export interface PeerRoutingInit { refreshManager?: RefreshManagerInit } +export interface DefaultPeerRoutingComponents { + peerId: PeerId + peerStore: PeerStore +} + export class DefaultPeerRouting implements PeerRouting, Startable { - private readonly components: Components + private readonly components: DefaultPeerRoutingComponents private readonly routers: PeerRouting[] private readonly refreshManagerInit: RefreshManagerInit private timeoutId?: ReturnType private started: boolean private abortController?: TimeoutController - constructor (components: Components, init: PeerRoutingInit) { + constructor (components: DefaultPeerRoutingComponents, init: PeerRoutingInit) { this.components = components this.routers = init.routers this.refreshManagerInit = init.refreshManager ?? {} @@ -110,7 +115,7 @@ export class DefaultPeerRouting implements PeerRouting, Startable { } catch {} // nb getClosestPeers adds the addresses to the address book - await drain(this.getClosestPeers(this.components.getPeerId().toBytes(), { signal: this.abortController.signal })) + await drain(this.getClosestPeers(this.components.peerId.toBytes(), { signal: this.abortController.signal })) } catch (err: any) { log.error(err) } finally { @@ -139,7 +144,7 @@ export class DefaultPeerRouting implements PeerRouting, Startable { throw errCode(new Error('No peer routers available'), codes.ERR_NO_ROUTERS_AVAILABLE) } - if (id.toString() === this.components.getPeerId().toString()) { + if (id.toString() === this.components.peerId.toString()) { throw errCode(new Error('Should not try to find self'), codes.ERR_FIND_SELF) } @@ -154,7 +159,7 @@ export class DefaultPeerRouting implements PeerRouting, Startable { })()) ), (source) => filter(source, Boolean), - (source) => storeAddresses(source, this.components.getPeerStore()), + (source) => storeAddresses(source, this.components.peerStore), async (source) => await first(source) ) @@ -177,7 +182,7 @@ export class DefaultPeerRouting implements PeerRouting, Startable { merge( ...this.routers.map(router => router.getClosestPeers(key, options)) ), - (source) => storeAddresses(source, this.components.getPeerStore()), + (source) => storeAddresses(source, this.components.peerStore), (source) => uniquePeers(source), (source) => requirePeers(source) ) diff --git a/src/ping/index.ts b/src/ping/index.ts index e5f7386509..6e25da93f9 100644 --- a/src/ping/index.ts +++ b/src/ping/index.ts @@ -6,15 +6,15 @@ import { pipe } from 'it-pipe' import first from 'it-first' import { equals as uint8ArrayEquals } from 'uint8arrays/equals' import { PROTOCOL_NAME, PING_LENGTH, PROTOCOL_VERSION } from './constants.js' -import type { IncomingStreamData } from '@libp2p/interface-registrar' +import type { IncomingStreamData, Registrar } from '@libp2p/interface-registrar' import type { PeerId } from '@libp2p/interface-peer-id' import type { Startable } from '@libp2p/interfaces/startable' -import type { Components } from '@libp2p/components' import type { AbortOptions } from '@libp2p/interfaces' import { abortableDuplex } from 'abortable-iterator' import { TimeoutController } from 'timeout-abort-controller' import type { Stream } from '@libp2p/interface-connection' import { setMaxListeners } from 'events' +import type { ConnectionManager } from '@libp2p/interface-connection-manager' const log = logger('libp2p:ping') @@ -29,13 +29,18 @@ export interface PingServiceInit { timeout: number } +export interface PingServiceComponents { + registrar: Registrar + connectionManager: ConnectionManager +} + export class PingService implements Startable { public readonly protocol: string - private readonly components: Components + private readonly components: PingServiceComponents private started: boolean private readonly init: PingServiceInit - constructor (components: Components, init: PingServiceInit) { + constructor (components: PingServiceComponents, init: PingServiceInit) { this.components = components this.started = false this.protocol = `/${init.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}` @@ -43,7 +48,7 @@ export class PingService implements Startable { } async start () { - await this.components.getRegistrar().handle(this.protocol, this.handleMessage, { + await this.components.registrar.handle(this.protocol, this.handleMessage, { maxInboundStreams: this.init.maxInboundStreams, maxOutboundStreams: this.init.maxOutboundStreams }) @@ -51,7 +56,7 @@ export class PingService implements Startable { } async stop () { - await this.components.getRegistrar().unhandle(this.protocol) + await this.components.registrar.unhandle(this.protocol) this.started = false } @@ -82,7 +87,7 @@ export class PingService implements Startable { const start = Date.now() const data = randomBytes(PING_LENGTH) - const connection = await this.components.getConnectionManager().openConnection(peer, options) + const connection = await this.components.connectionManager.openConnection(peer, options) let timeoutController let signal = options.signal let stream: Stream | undefined diff --git a/src/pnet/index.ts b/src/pnet/index.ts index e1333083d7..9b63339940 100644 --- a/src/pnet/index.ts +++ b/src/pnet/index.ts @@ -24,7 +24,7 @@ export interface ProtectorInit { psk: Uint8Array } -export class PreSharedKeyConnectionProtector implements ConnectionProtector { +class PreSharedKeyConnectionProtector implements ConnectionProtector { public tag: string private readonly psk: Uint8Array private readonly enabled: boolean @@ -96,3 +96,7 @@ export class PreSharedKeyConnectionProtector implements ConnectionProtector { } } } + +export function preSharedKey (init: ProtectorInit): () => ConnectionProtector { + return () => new PreSharedKeyConnectionProtector(init) +} diff --git a/src/registrar.ts b/src/registrar.ts index edf871eedf..314f267f18 100644 --- a/src/registrar.ts +++ b/src/registrar.ts @@ -4,24 +4,31 @@ import { codes } from './errors.js' import { isTopology, StreamHandlerOptions, StreamHandlerRecord } from '@libp2p/interface-registrar' import merge from 'merge-options' import type { Registrar, StreamHandler, Topology } from '@libp2p/interface-registrar' -import type { PeerProtocolsChangeData } from '@libp2p/interface-peer-store' +import type { PeerProtocolsChangeData, PeerStore } from '@libp2p/interface-peer-store' import type { Connection } from '@libp2p/interface-connection' -import type { Components } from '@libp2p/components' +import type { ConnectionManager } from '@libp2p/interface-connection-manager' +import type { PeerId } from '@libp2p/interface-peer-id' const log = logger('libp2p:registrar') export const DEFAULT_MAX_INBOUND_STREAMS = 32 export const DEFAULT_MAX_OUTBOUND_STREAMS = 64 +export interface RegistrarComponents { + peerId: PeerId + connectionManager: ConnectionManager + peerStore: PeerStore +} + /** * Responsible for notifying registered protocols of events in the network. */ export class DefaultRegistrar implements Registrar { private readonly topologies: Map> private readonly handlers: Map - private readonly components: Components + private readonly components: RegistrarComponents - constructor (components: Components) { + constructor (components: RegistrarComponents) { this.topologies = new Map() this.handlers = new Map() this.components = components @@ -29,10 +36,10 @@ export class DefaultRegistrar implements Registrar { this._onDisconnect = this._onDisconnect.bind(this) this._onProtocolChange = this._onProtocolChange.bind(this) - this.components.getConnectionManager().addEventListener('peer:disconnect', this._onDisconnect) + this.components.connectionManager.addEventListener('peer:disconnect', this._onDisconnect) // happens after identify - this.components.getPeerStore().addEventListener('change:protocols', this._onProtocolChange) + this.components.peerStore.addEventListener('change:protocols', this._onProtocolChange) } getProtocols () { @@ -83,7 +90,7 @@ export class DefaultRegistrar implements Registrar { }) // Add new protocols to self protocols in the Protobook - await this.components.getPeerStore().protoBook.add(this.components.getPeerId(), [protocol]) + await this.components.peerStore.protoBook.add(this.components.peerId, [protocol]) } /** @@ -98,7 +105,7 @@ export class DefaultRegistrar implements Registrar { }) // Remove protocols from self protocols in the Protobook - await this.components.getPeerStore().protoBook.remove(this.components.getPeerId(), protocolList) + await this.components.peerStore.protoBook.remove(this.components.peerId, protocolList) } /** @@ -149,7 +156,7 @@ export class DefaultRegistrar implements Registrar { _onDisconnect (evt: CustomEvent) { const connection = evt.detail - void this.components.getPeerStore().protoBook.get(connection.remotePeer) + void this.components.peerStore.protoBook.get(connection.remotePeer) .then(peerProtocols => { for (const protocol of peerProtocols) { const topologies = this.topologies.get(protocol) @@ -200,7 +207,7 @@ export class DefaultRegistrar implements Registrar { } for (const topology of topologies.values()) { - const connection = this.components.getConnectionManager().getConnections(peerId)[0] + const connection = this.components.connectionManager.getConnections(peerId)[0] if (connection == null) { continue diff --git a/src/transport-manager.ts b/src/transport-manager.ts index 0721cfcdb8..d6ef0f1a3a 100644 --- a/src/transport-manager.ts +++ b/src/transport-manager.ts @@ -2,14 +2,15 @@ import { logger } from '@libp2p/logger' import pSettle from 'p-settle' import { codes } from './errors.js' import errCode from 'err-code' -import type { Listener, Transport, TransportManager, TransportManagerEvents } from '@libp2p/interface-transport' +import type { Listener, Transport, TransportManager, TransportManagerEvents, Upgrader } from '@libp2p/interface-transport' import type { Multiaddr } from '@multiformats/multiaddr' import type { Connection } from '@libp2p/interface-connection' import type { AbortOptions } from '@libp2p/interfaces' import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events' import type { Startable } from '@libp2p/interfaces/startable' -import type { Components } from '@libp2p/components' import { trackedMap } from '@libp2p/tracked-map' +import type { Metrics } from '@libp2p/interface-metrics' +import type { AddressManager } from '@libp2p/interface-address-manager' const log = logger('libp2p:transports') @@ -17,14 +18,20 @@ export interface TransportManagerInit { faultTolerance?: FaultTolerance } +export interface DefaultTransportManagerComponents { + metrics?: Metrics + addressManager: AddressManager + upgrader: Upgrader +} + export class DefaultTransportManager extends EventEmitter implements TransportManager, Startable { - private readonly components: Components + private readonly components: DefaultTransportManagerComponents private readonly transports: Map private readonly listeners: Map private readonly faultTolerance: FaultTolerance private started: boolean - constructor (components: Components, init: TransportManagerInit = {}) { + constructor (components: DefaultTransportManagerComponents, init: TransportManagerInit = {}) { super() this.components = components @@ -33,7 +40,7 @@ export class DefaultTransportManager extends EventEmitter implements Upgrader { - private readonly components: Components + private readonly components: DefaultUpgraderComponents private readonly connectionEncryption: Map private readonly muxers: Map private readonly inboundUpgradeTimeout: number - constructor (components: Components, init: UpgraderInit) { + constructor (components: DefaultUpgraderComponents, init: UpgraderInit) { super() this.components = components @@ -123,7 +135,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg * Upgrades an inbound connection */ async upgradeInbound (maConn: MultiaddrConnection): Promise { - const accept = await this.components.getConnectionManager().acceptIncomingConnection(maConn) + const accept = await this.components.connectionManager.acceptIncomingConnection(maConn) if (!accept) { throw errCode(new Error('connection denied'), codes.ERR_CONNECTION_DENIED) @@ -136,7 +148,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg let cryptoProtocol let setPeer let proxyPeer - const metrics = this.components.getMetrics() + const metrics = this.components.metrics const timeoutController = new TimeoutController(this.inboundUpgradeTimeout) @@ -150,7 +162,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg maConn.source = abortableStream.source maConn.sink = abortableStream.sink - if (await this.components.getConnectionGater().denyInboundConnection(maConn)) { + if (await this.components.connectionGater.denyInboundConnection(maConn)) { throw errCode(new Error('The multiaddr connection is blocked by gater.acceptConnection'), codes.ERR_CONNECTION_INTERCEPTED) } @@ -165,7 +177,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg // Protect let protectedConn = maConn - const protector = this.components.getConnectionProtector() + const protector = this.components.connectionProtector if (protector != null) { log('protecting the inbound connection') @@ -180,7 +192,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg protocol: cryptoProtocol } = await this._encryptInbound(protectedConn)) - if (await this.components.getConnectionGater().denyInboundEncryptedConnection(remotePeer, { + if (await this.components.connectionGater.denyInboundEncryptedConnection(remotePeer, { ...protectedConn, ...encryptedConn })) { @@ -203,7 +215,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg throw err } - if (await this.components.getConnectionGater().denyInboundUpgradedConnection(remotePeer, { + if (await this.components.connectionGater.denyInboundUpgradedConnection(remotePeer, { ...protectedConn, ...encryptedConn })) { @@ -226,7 +238,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg remotePeer }) } finally { - this.components.getConnectionManager().afterUpgradeInbound() + this.components.connectionManager.afterUpgradeInbound() timeoutController.clear() } } @@ -242,7 +254,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg const remotePeerId = peerIdFromString(idStr) - if (await this.components.getConnectionGater().denyOutboundConnection(remotePeerId, maConn)) { + if (await this.components.connectionGater.denyOutboundConnection(remotePeerId, maConn)) { throw errCode(new Error('The multiaddr connection is blocked by connectionGater.denyOutboundConnection'), codes.ERR_CONNECTION_INTERCEPTED) } @@ -253,7 +265,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg let muxerFactory let setPeer let proxyPeer - const metrics = this.components.getMetrics() + const metrics = this.components.metrics if (metrics != null) { ({ setTarget: setPeer, proxy: proxyPeer } = mutableProxy()) @@ -270,7 +282,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg // Protect let protectedConn = maConn if (opts?.skipProtection !== true) { - const protector = this.components.getConnectionProtector() + const protector = this.components.connectionProtector if (protector != null) { protectedConn = await protector.protect(maConn) @@ -287,7 +299,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg protocol: cryptoProtocol } = await this._encryptOutbound(protectedConn, remotePeerId)) - if (await this.components.getConnectionGater().denyOutboundEncryptedConnection(remotePeer, { + if (await this.components.connectionGater.denyOutboundEncryptedConnection(remotePeer, { ...protectedConn, ...encryptedConn })) { @@ -316,7 +328,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg throw err } - if (await this.components.getConnectionGater().denyOutboundUpgradedConnection(remotePeer, { + if (await this.components.connectionGater.denyOutboundUpgradedConnection(remotePeer, { ...protectedConn, ...encryptedConn })) { @@ -369,11 +381,11 @@ export class DefaultUpgrader extends EventEmitter implements Upg void Promise.resolve() .then(async () => { - const protocols = this.components.getRegistrar().getProtocols() + const protocols = this.components.registrar.getProtocols() const { stream, protocol } = await mss.handle(muxedStream, protocols) log('%s: incoming stream opened on %s', direction, protocol) - const metrics = this.components.getMetrics() + const metrics = this.components.metrics if (metrics != null) { metrics.trackStream({ stream, remotePeer, protocol }) @@ -383,7 +395,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg return } - const incomingLimit = findIncomingStreamLimit(protocol, this.components.getRegistrar()) + const incomingLimit = findIncomingStreamLimit(protocol, this.components.registrar) const streamCount = countStreams(protocol, 'inbound', connection) if (streamCount === incomingLimit) { @@ -400,7 +412,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg // If a protocol stream has been successfully negotiated and is to be passed to the application, // the peerstore should ensure that the peer is registered with that protocol - this.components.getPeerStore().protoBook.add(remotePeer, [protocol]).catch(err => log.error(err)) + this.components.peerStore.protoBook.add(remotePeer, [protocol]).catch(err => log.error(err)) connection.addStream(muxedStream) this._onStream({ connection, stream: muxedStream, protocol }) @@ -419,10 +431,6 @@ export class DefaultUpgrader extends EventEmitter implements Upg } }) - if (isInitializable(muxer)) { - muxer.init(this.components) - } - newStream = async (protocols: string[], options: AbortOptions = {}): Promise => { if (muxer == null) { throw errCode(new Error('Stream is not multiplexed'), codes.ERR_MUXER_UNAVAILABLE) @@ -430,7 +438,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg log('%s: starting new stream on %s', direction, protocols) const muxedStream = await muxer.newStream() - const metrics = this.components.getMetrics() + const metrics = this.components.metrics let controller: TimeoutController | undefined try { @@ -452,7 +460,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg metrics.trackStream({ stream, remotePeer, protocol }) } - const outgoingLimit = findOutgoingStreamLimit(protocol, this.components.getRegistrar()) + const outgoingLimit = findOutgoingStreamLimit(protocol, this.components.registrar) const streamCount = countStreams(protocol, 'outbound', connection) if (streamCount === outgoingLimit) { @@ -464,7 +472,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg // If a protocol stream has been successfully negotiated and is to be passed to the application, // the peerstore should ensure that the peer is registered with that protocol - this.components.getPeerStore().protoBook.add(remotePeer, [protocol]).catch(err => log.error(err)) + this.components.peerStore.protoBook.add(remotePeer, [protocol]).catch(err => log.error(err)) // after the handshake the returned stream can have early data so override // the souce/sink @@ -561,7 +569,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg */ _onStream (opts: OnStreamOptions): void { const { connection, stream, protocol } = opts - const { handler } = this.components.getRegistrar().getHandler(protocol) + const { handler } = this.components.registrar.getHandler(protocol) handler({ connection, stream }) } @@ -586,7 +594,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg log('encrypting inbound connection...') return { - ...await encrypter.secureInbound(this.components.getPeerId(), stream), + ...await encrypter.secureInbound(this.components.peerId, stream), protocol } } catch (err: any) { @@ -615,7 +623,7 @@ export class DefaultUpgrader extends EventEmitter implements Upg log('encrypting outbound connection to %p', remotePeerId) return { - ...await encrypter.secureOutbound(this.components.getPeerId(), stream, remotePeerId), + ...await encrypter.secureOutbound(this.components.peerId, stream, remotePeerId), protocol } } catch (err: any) { diff --git a/test/addresses/address-manager.spec.ts b/test/addresses/address-manager.spec.ts index 2413f0eb79..f08b0444ba 100644 --- a/test/addresses/address-manager.spec.ts +++ b/test/addresses/address-manager.spec.ts @@ -10,7 +10,6 @@ import { stubInterface } from 'ts-sinon' import type { TransportManager } from '@libp2p/interface-transport' import type { PeerId } from '@libp2p/interface-peer-id' import type { Libp2p } from '../../src/index.js' -import { Components } from '@libp2p/components' const listenAddresses = ['/ip4/127.0.0.1/tcp/15006/ws', '/ip4/127.0.0.1/tcp/15008/ws'] const announceAddreses = ['/dns4/peer.io'] @@ -23,10 +22,10 @@ describe('Address Manager', () => { }) it('should not need any addresses', () => { - const am = new DefaultAddressManager(new Components({ + const am = new DefaultAddressManager({ peerId, transportManager: stubInterface() - }), { + }, { announceFilter: stubInterface() }) @@ -35,10 +34,10 @@ describe('Address Manager', () => { }) it('should return listen multiaddrs on get', () => { - const am = new DefaultAddressManager(new Components({ + const am = new DefaultAddressManager({ peerId, transportManager: stubInterface() - }), { + }, { announceFilter: stubInterface(), listen: listenAddresses }) @@ -53,10 +52,10 @@ describe('Address Manager', () => { }) it('should return announce multiaddrs on get', () => { - const am = new DefaultAddressManager(new Components({ + const am = new DefaultAddressManager({ peerId, transportManager: stubInterface() - }), { + }, { announceFilter: stubInterface(), listen: listenAddresses, announce: announceAddreses @@ -71,10 +70,10 @@ describe('Address Manager', () => { }) it('should add observed addresses', () => { - const am = new DefaultAddressManager(new Components({ + const am = new DefaultAddressManager({ peerId, transportManager: stubInterface() - }), { + }, { announceFilter: stubInterface() }) @@ -87,10 +86,10 @@ describe('Address Manager', () => { it('should dedupe added observed addresses', () => { const ma = '/ip4/123.123.123.123/tcp/39201' - const am = new DefaultAddressManager(new Components({ + const am = new DefaultAddressManager({ peerId, transportManager: stubInterface() - }), { + }, { announceFilter: stubInterface() }) @@ -106,10 +105,10 @@ describe('Address Manager', () => { it('should only emit one change:addresses event', () => { const ma = '/ip4/123.123.123.123/tcp/39201' - const am = new DefaultAddressManager(new Components({ + const am = new DefaultAddressManager({ peerId, transportManager: stubInterface() - }), { + }, { announceFilter: stubInterface() }) let eventCount = 0 @@ -128,10 +127,10 @@ describe('Address Manager', () => { it('should strip our peer address from added observed addresses', () => { const ma = '/ip4/123.123.123.123/tcp/39201' - const am = new DefaultAddressManager(new Components({ + const am = new DefaultAddressManager({ peerId, transportManager: stubInterface() - }), { + }, { announceFilter: stubInterface() }) @@ -146,10 +145,10 @@ describe('Address Manager', () => { it('should strip our peer address from added observed addresses in difference formats', () => { const ma = '/ip4/123.123.123.123/tcp/39201' - const am = new DefaultAddressManager(new Components({ + const am = new DefaultAddressManager({ peerId, transportManager: stubInterface() - }), { + }, { announceFilter: stubInterface() }) diff --git a/test/addresses/addresses.node.ts b/test/addresses/addresses.node.ts index 37ac534513..cde591b468 100644 --- a/test/addresses/addresses.node.ts +++ b/test/addresses/addresses.node.ts @@ -32,7 +32,7 @@ describe('libp2p.multiaddrs', () => { } }) - let listenAddrs = libp2p.components.getAddressManager().getListenAddrs().map(ma => ma.toString()) + let listenAddrs = libp2p.components.addressManager.getListenAddrs().map(ma => ma.toString()) expect(listenAddrs).to.have.lengthOf(listenAddresses.length) expect(listenAddrs).to.include(listenAddresses[0]) expect(listenAddrs).to.include(listenAddresses[1]) @@ -41,7 +41,7 @@ describe('libp2p.multiaddrs', () => { // Only transportManager has visibility of the port used await libp2p.start() - listenAddrs = libp2p.components.getAddressManager().getListenAddrs().map(ma => ma.toString()) + listenAddrs = libp2p.components.addressManager.getListenAddrs().map(ma => ma.toString()) expect(listenAddrs).to.have.lengthOf(listenAddresses.length) expect(listenAddrs).to.include(listenAddresses[0]) expect(listenAddrs).to.include(listenAddresses[1]) @@ -60,10 +60,10 @@ describe('libp2p.multiaddrs', () => { await libp2p.start() - const tmListen = libp2p.components.getTransportManager().getAddrs().map((ma) => ma.toString()) + const tmListen = libp2p.components.transportManager.getAddrs().map((ma) => ma.toString()) // Announce 2 listen (transport) - const advertiseMultiaddrs = libp2p.components.getAddressManager().getAddresses().map((ma) => ma.decapsulateCode(protocols('p2p').code).toString()) + const advertiseMultiaddrs = libp2p.components.addressManager.getAddresses().map((ma) => ma.decapsulateCode(protocols('p2p').code).toString()) expect(advertiseMultiaddrs).to.have.lengthOf(2) tmListen.forEach((m) => { @@ -86,10 +86,10 @@ describe('libp2p.multiaddrs', () => { await libp2p.start() - const tmListen = libp2p.components.getTransportManager().getAddrs().map((ma) => ma.toString()) + const tmListen = libp2p.components.transportManager.getAddrs().map((ma) => ma.toString()) // Announce 1 announce addr - const advertiseMultiaddrs = libp2p.components.getAddressManager().getAddresses().map((ma) => ma.decapsulateCode(protocols('p2p').code).toString()) + const advertiseMultiaddrs = libp2p.components.addressManager.getAddresses().map((ma) => ma.decapsulateCode(protocols('p2p').code).toString()) expect(advertiseMultiaddrs.length).to.equal(announceAddreses.length) advertiseMultiaddrs.forEach((m) => { expect(tmListen).to.not.include(m) @@ -111,16 +111,16 @@ describe('libp2p.multiaddrs', () => { await libp2p.start() - expect(libp2p.components.getAddressManager().getAddresses()).to.have.lengthOf(0) + expect(libp2p.components.addressManager.getAddresses()).to.have.lengthOf(0) // Stub transportManager addresses to add a public address const stubMa = multiaddr('/ip4/120.220.10.1/tcp/1000') - sinon.stub(libp2p.components.getTransportManager(), 'getAddrs').returns([ + sinon.stub(libp2p.components.transportManager, 'getAddrs').returns([ ...listenAddresses.map((a) => multiaddr(a)), stubMa ]) - const multiaddrs = libp2p.components.getAddressManager().getAddresses() + const multiaddrs = libp2p.components.addressManager.getAddresses() expect(multiaddrs.length).to.equal(1) expect(multiaddrs[0].decapsulateCode(protocols('p2p').code).equals(stubMa)).to.eql(true) }) @@ -138,14 +138,14 @@ describe('libp2p.multiaddrs', () => { } }) - const listenAddrs = libp2p.components.getAddressManager().getListenAddrs().map((ma) => ma.toString()) + const listenAddrs = libp2p.components.addressManager.getListenAddrs().map((ma) => ma.toString()) expect(listenAddrs).to.have.lengthOf(listenAddresses.length) expect(listenAddrs).to.include(listenAddresses[0]) expect(listenAddrs).to.include(listenAddresses[1]) await libp2p.start() - const loopbackAddrs = libp2p.components.getAddressManager().getAddresses().filter(ma => isLoopback(ma)) + const loopbackAddrs = libp2p.components.addressManager.getAddresses().filter(ma => isLoopback(ma)) expect(loopbackAddrs).to.be.empty() }) @@ -163,11 +163,11 @@ describe('libp2p.multiaddrs', () => { await libp2p.start() - expect(libp2p.components.getAddressManager().getAddresses()).to.have.lengthOf(listenAddresses.length) + expect(libp2p.components.addressManager.getAddresses()).to.have.lengthOf(listenAddresses.length) - libp2p.components.getAddressManager().addObservedAddr(multiaddr(ma)) + libp2p.components.addressManager.addObservedAddr(multiaddr(ma)) - expect(libp2p.components.getAddressManager().getAddresses()).to.have.lengthOf(listenAddresses.length + 1) - expect(libp2p.components.getAddressManager().getAddresses().map(ma => ma.decapsulateCode(protocols('p2p').code).toString())).to.include(ma) + expect(libp2p.components.addressManager.getAddresses()).to.have.lengthOf(listenAddresses.length + 1) + expect(libp2p.components.addressManager.getAddresses().map(ma => ma.decapsulateCode(protocols('p2p').code).toString())).to.include(ma) }) }) diff --git a/test/addresses/utils.ts b/test/addresses/utils.ts index ef8d95d5e3..a4c1c2b999 100644 --- a/test/addresses/utils.ts +++ b/test/addresses/utils.ts @@ -1,10 +1,10 @@ -import { TCP } from '@libp2p/tcp' -import { WebSockets } from '@libp2p/websockets' +import { tcp } from '@libp2p/tcp' +import { webSockets } from '@libp2p/websockets' import { createBaseOptions } from '../utils/base-options.js' export const AddressesOptions = createBaseOptions({ transports: [ - new TCP(), - new WebSockets() + tcp(), + webSockets() ] }) diff --git a/test/configuration/pubsub.spec.ts b/test/configuration/pubsub.spec.ts index fdc557e4ac..6513ca9913 100644 --- a/test/configuration/pubsub.spec.ts +++ b/test/configuration/pubsub.spec.ts @@ -8,7 +8,7 @@ import { createLibp2p, Libp2p } from '../../src/index.js' import { baseOptions, pubsubSubsystemOptions } from './utils.js' import { createPeerId } from '../utils/creators/peer.js' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { FloodSub } from '@libp2p/floodsub' +import { floodsub } from '@libp2p/floodsub' import type { PubSub } from '@libp2p/interface-pubsub' describe('Pubsub subsystem is configurable', () => { @@ -61,7 +61,7 @@ describe('Pubsub subscription handlers adapter', () => { libp2p = await createLibp2p(mergeOptions(pubsubSubsystemOptions, { peerId, - pubsub: new FloodSub({ + pubsub: floodsub({ emitSelf: true }) })) diff --git a/test/configuration/utils.ts b/test/configuration/utils.ts index cfec40786a..d1983f9a26 100644 --- a/test/configuration/utils.ts +++ b/test/configuration/utils.ts @@ -1,7 +1,7 @@ -import { PubSubBaseProtocol } from '@libp2p/pubsub' -import { Plaintext } from '../../src/insecure/index.js' -import { Mplex } from '@libp2p/mplex' -import { WebSockets } from '@libp2p/websockets' +import { PubSubBaseProtocol, PubSubComponents } from '@libp2p/pubsub' +import { plaintext } from '../../src/insecure/index.js' +import { mplex } from '@libp2p/mplex' +import { webSockets } from '@libp2p/websockets' import * as filters from '@libp2p/websockets/filters' import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' import mergeOptions from 'merge-options' @@ -13,14 +13,14 @@ import * as cborg from 'cborg' const relayAddr = MULTIADDRS_WEBSOCKETS[0] export const baseOptions: Partial = { - transports: [new WebSockets()], - streamMuxers: [new Mplex()], - connectionEncryption: [new Plaintext()] + transports: [webSockets()], + streamMuxers: [mplex()], + connectionEncryption: [plaintext()] } class MockPubSub extends PubSubBaseProtocol { - constructor (init?: PubSubInit) { - super({ + constructor (components: PubSubComponents, init?: PubSubInit) { + super(components, { multicodecs: ['/mock-pubsub'], ...init }) @@ -51,7 +51,7 @@ class MockPubSub extends PubSubBaseProtocol { } peers.forEach(id => { - if (this.components.getPeerId().equals(id)) { + if (this.components.peerId.equals(id)) { return } @@ -68,11 +68,11 @@ class MockPubSub extends PubSubBaseProtocol { } export const pubsubSubsystemOptions: Libp2pOptions = mergeOptions(baseOptions, { - pubsub: new MockPubSub(), + pubsub: (components: PubSubComponents) => new MockPubSub(components), addresses: { listen: [`${relayAddr.toString()}/p2p-circuit`] }, transports: [ - new WebSockets({ filter: filters.all }) + webSockets({ filter: filters.all }) ] }) diff --git a/test/connection-manager/auto-dialler.spec.ts b/test/connection-manager/auto-dialler.spec.ts index 2704435d79..453b79ce7d 100644 --- a/test/connection-manager/auto-dialler.spec.ts +++ b/test/connection-manager/auto-dialler.spec.ts @@ -5,7 +5,6 @@ import { AutoDialler } from '../../src/connection-manager/auto-dialler.js' import pWaitFor from 'p-wait-for' import delay from 'delay' import { createEd25519PeerId } from '@libp2p/peer-id-factory' -import { Components } from '@libp2p/components' import { stubInterface } from 'ts-sinon' import type { ConnectionManager } from '@libp2p/interface-connection-manager' import type { PeerStore, Peer } from '@libp2p/interface-peer-store' @@ -37,11 +36,11 @@ describe('Auto-dialler', () => { const connectionManager = stubInterface() connectionManager.getConnections.returns([]) - const autoDialler = new AutoDialler(new Components({ + const autoDialler = new AutoDialler({ peerId: self.id, peerStore, connectionManager - }), { + }, { minConnections: 10 }) diff --git a/test/connection-manager/index.node.ts b/test/connection-manager/index.node.ts index 69fc2b23db..879d85211c 100644 --- a/test/connection-manager/index.node.ts +++ b/test/connection-manager/index.node.ts @@ -7,7 +7,6 @@ import { createBaseOptions } from '../utils/base-options.browser.js' import type { Libp2p } from '../../src/index.js' import type { PeerId } from '@libp2p/interface-peer-id' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' -import { Components } from '@libp2p/components' import { CustomEvent } from '@libp2p/interfaces/events' import * as STATUS from '@libp2p/interface-connection/status' import { stubInterface } from 'ts-sinon' @@ -19,6 +18,8 @@ import delay from 'delay' import type { Libp2pNode } from '../../src/libp2p.js' import { codes } from '../../src/errors.js' import { start } from '@libp2p/interfaces/startable' +import type { Metrics } from '@libp2p/interface-metrics' +import type { Dialer } from '@libp2p/interface-connection-manager' describe('Connection Manager', () => { let libp2p: Libp2p @@ -52,12 +53,17 @@ describe('Connection Manager', () => { peerStore.keyBook = stubInterface() const connectionManager = new DefaultConnectionManager({ + peerId: peerIds[0], + metrics: stubInterface(), + dialer: stubInterface(), + upgrader, + peerStore + }, { maxConnections: 1000, minConnections: 50, autoDialInterval: 1000, inboundUpgradeTimeout: 1000 }) - connectionManager.init(new Components({ upgrader, peerStore })) await start(connectionManager) @@ -88,12 +94,17 @@ describe('Connection Manager', () => { peerStore.keyBook = stubInterface() const connectionManager = new DefaultConnectionManager({ + peerId: peerIds[0], + metrics: stubInterface(), + dialer: stubInterface(), + upgrader, + peerStore + }, { maxConnections: 1000, minConnections: 50, autoDialInterval: 1000, inboundUpgradeTimeout: 1000 }) - connectionManager.init(new Components({ upgrader, peerStore })) await start(connectionManager) @@ -237,11 +248,11 @@ describe('libp2p.connections', () => { await libp2p.start() // Wait for peer to connect - await pWaitFor(() => libp2p.components.getConnectionManager().getConnections().length === minConnections) + await pWaitFor(() => libp2p.connectionManager.getConnections().length === minConnections) // Wait more time to guarantee no other connection happened await delay(200) - expect(libp2p.components.getConnectionManager().getConnections().length).to.eql(minConnections) + expect(libp2p.connectionManager.getConnections().length).to.eql(minConnections) await libp2p.stop() }) @@ -270,11 +281,11 @@ describe('libp2p.connections', () => { await libp2p.start() // Wait for peer to connect - await pWaitFor(() => libp2p.components.getConnectionManager().getConnections().length === minConnections) + await pWaitFor(() => libp2p.connectionManager.getConnections().length === minConnections) // Should have connected to the peer with protocols - expect(libp2p.components.getConnectionManager().getConnections(nodes[0].peerId)).to.be.empty() - expect(libp2p.components.getConnectionManager().getConnections(nodes[1].peerId)).to.not.be.empty() + expect(libp2p.connectionManager.getConnections(nodes[0].peerId)).to.be.empty() + expect(libp2p.connectionManager.getConnections(nodes[1].peerId)).to.not.be.empty() await libp2p.stop() }) @@ -300,15 +311,15 @@ describe('libp2p.connections', () => { // Wait for peer to connect const conn = await libp2p.dial(nodes[0].peerId) - expect(libp2p.components.getConnectionManager().getConnections(nodes[0].peerId)).to.not.be.empty() + expect(libp2p.connectionManager.getConnections(nodes[0].peerId)).to.not.be.empty() await conn.close() // Closed - await pWaitFor(() => libp2p.components.getConnectionManager().getConnections().length === 0) + await pWaitFor(() => libp2p.connectionManager.getConnections().length === 0) // Connected - await pWaitFor(() => libp2p.components.getConnectionManager().getConnections().length === 1) + await pWaitFor(() => libp2p.connectionManager.getConnections().length === 1) - expect(libp2p.components.getConnectionManager().getConnections(nodes[0].peerId)).to.not.be.empty() + expect(libp2p.connectionManager.getConnections(nodes[0].peerId)).to.not.be.empty() await libp2p.stop() }) @@ -334,7 +345,7 @@ describe('libp2p.connections', () => { await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.getMultiaddrs()) await libp2p.dial(remoteLibp2p.peerId) - const conns = libp2p.components.getConnectionManager().getConnections() + const conns = libp2p.connectionManager.getConnections() expect(conns.length).to.eql(1) const conn = conns[0] @@ -405,7 +416,7 @@ describe('libp2p.connections', () => { }) }) await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.getMultiaddrs()) - await libp2p.components.getConnectionManager().openConnection(remoteLibp2p.peerId) + await libp2p.connectionManager.openConnection(remoteLibp2p.peerId) for (const multiaddr of remoteLibp2p.getMultiaddrs()) { expect(denyDialMultiaddr.calledWith(remoteLibp2p.peerId, multiaddr)).to.be.true() diff --git a/test/connection-manager/index.spec.ts b/test/connection-manager/index.spec.ts index 86b6ba8480..bb3d71dfc2 100644 --- a/test/connection-manager/index.spec.ts +++ b/test/connection-manager/index.spec.ts @@ -12,10 +12,12 @@ import { CustomEvent } from '@libp2p/interfaces/events' import { KEEP_ALIVE } from '@libp2p/interface-peer-store/tags' import pWaitFor from 'p-wait-for' import { multiaddr } from '@multiformats/multiaddr' -import { Components } from '@libp2p/components' import { stubInterface } from 'ts-sinon' import type { Dialer } from '@libp2p/interface-connection-manager' import type { Connection } from '@libp2p/interface-connection' +import type { Metrics } from '@libp2p/interface-metrics' +import type { Upgrader } from '@libp2p/interface-transport' +import type { PeerStore } from '@libp2p/interface-peer-store' const defaultOptions = { maxConnections: 10, @@ -41,11 +43,11 @@ describe('Connection Manager', () => { started: false }) - const spy = sinon.spy(libp2p.components.getConnectionManager() as DefaultConnectionManager, 'start') + const spy = sinon.spy(libp2p.connectionManager as DefaultConnectionManager, 'start') await libp2p.start() expect(spy).to.have.property('callCount', 1) - expect(libp2p.components.getMetrics()).to.not.exist() + expect(libp2p.metrics).to.not.exist() }) it('should be able to create with metrics', async () => { @@ -58,11 +60,11 @@ describe('Connection Manager', () => { started: false }) - const spy = sinon.spy(libp2p.components.getConnectionManager() as DefaultConnectionManager, 'start') + const spy = sinon.spy(libp2p.connectionManager as DefaultConnectionManager, 'start') await libp2p.start() expect(spy).to.have.property('callCount', 1) - expect(libp2p.components.getMetrics()).to.exist() + expect(libp2p.metrics).to.exist() }) it('should close connections with low tag values first', async () => { @@ -79,7 +81,7 @@ describe('Connection Manager', () => { await libp2p.start() - const connectionManager = libp2p.components.getConnectionManager() as DefaultConnectionManager + const connectionManager = libp2p.connectionManager as DefaultConnectionManager const connectionManagerMaybeDisconnectOneSpy = sinon.spy(connectionManager, '_pruneConnections') const spies = new Map>>() @@ -129,7 +131,7 @@ describe('Connection Manager', () => { await libp2p.start() - const connectionManager = libp2p.components.getConnectionManager() as DefaultConnectionManager + const connectionManager = libp2p.connectionManager as DefaultConnectionManager const connectionManagerMaybeDisconnectOneSpy = sinon.spy(connectionManager, '_pruneConnections') // Add 1 too many connections @@ -164,7 +166,7 @@ describe('Connection Manager', () => { started: false }) - const connectionManager = libp2p.components.getConnectionManager() as DefaultConnectionManager + const connectionManager = libp2p.connectionManager as DefaultConnectionManager const connectionManagerOpenConnectionSpy = sinon.spy(connectionManager, 'openConnection') await libp2p.start() @@ -187,6 +189,12 @@ describe('Connection Manager', () => { it('should deny connections from denylist multiaddrs', async () => { const remoteAddr = multiaddr('/ip4/83.13.55.32/tcp/59283') const connectionManager = new DefaultConnectionManager({ + peerId: libp2p.peerId, + metrics: stubInterface(), + upgrader: stubInterface(), + peerStore: stubInterface(), + dialer: stubInterface() + }, { ...defaultOptions, deny: [ '/ip4/83.13.55.32' @@ -205,21 +213,20 @@ describe('Connection Manager', () => { }) it('should deny connections when maxConnections is exceeded', async () => { - const connectionManager = new DefaultConnectionManager({ - ...defaultOptions, - maxConnections: 1 - }) - const dialer = stubInterface() dialer.dial.resolves(stubInterface()) - const components = new Components({ + const connectionManager = new DefaultConnectionManager({ + peerId: libp2p.peerId, + metrics: stubInterface(), + upgrader: stubInterface(), + peerStore: stubInterface(), dialer + }, { + ...defaultOptions, + maxConnections: 1 }) - // set mocks - connectionManager.init(components) - // max out the connection limit await connectionManager.openConnection(await createEd25519PeerId()) expect(connectionManager.getConnections()).to.have.lengthOf(1) @@ -236,21 +243,19 @@ describe('Connection Manager', () => { }) it('should deny connections from peers that connect too frequently', async () => { - const connectionManager = new DefaultConnectionManager({ - ...defaultOptions, - inboundConnectionThreshold: 1 - }) - const dialer = stubInterface() dialer.dial.resolves(stubInterface()) - - const components = new Components({ + const connectionManager = new DefaultConnectionManager({ + peerId: libp2p.peerId, + metrics: stubInterface(), + upgrader: stubInterface(), + peerStore: stubInterface(), dialer + }, { + ...defaultOptions, + inboundConnectionThreshold: 1 }) - // set mocks - connectionManager.init(components) - // an inbound connection is opened const remotePeer = await createEd25519PeerId() const maConn = mockMultiaddrConnection({ @@ -271,7 +276,15 @@ describe('Connection Manager', () => { it('should allow connections from allowlist multiaddrs', async () => { const remoteAddr = multiaddr('/ip4/83.13.55.32/tcp/59283') + const dialer = stubInterface() + dialer.dial.resolves(stubInterface()) const connectionManager = new DefaultConnectionManager({ + peerId: libp2p.peerId, + metrics: stubInterface(), + upgrader: stubInterface(), + peerStore: stubInterface(), + dialer + }, { ...defaultOptions, maxConnections: 1, allow: [ @@ -279,16 +292,6 @@ describe('Connection Manager', () => { ] }) - const dialer = stubInterface() - dialer.dial.resolves(stubInterface()) - - const components = new Components({ - dialer - }) - - // set mocks - connectionManager.init(components) - // max out the connection limit await connectionManager.openConnection(await createEd25519PeerId()) expect(connectionManager.getConnections()).to.have.lengthOf(1) @@ -306,21 +309,20 @@ describe('Connection Manager', () => { }) it('should limit the number of inbound pending connections', async () => { - const connectionManager = new DefaultConnectionManager({ - ...defaultOptions, - maxIncomingPendingConnections: 1 - }) - const dialer = stubInterface() dialer.dial.resolves(stubInterface()) - const components = new Components({ + const connectionManager = new DefaultConnectionManager({ + peerId: await createEd25519PeerId(), + metrics: stubInterface(), + upgrader: stubInterface(), + peerStore: stubInterface(), dialer + }, { + ...defaultOptions, + maxIncomingPendingConnections: 1 }) - // set mocks - connectionManager.init(components) - // start the upgrade const maConn1 = mockMultiaddrConnection({ source: [], diff --git a/test/content-routing/content-routing.node.ts b/test/content-routing/content-routing.node.ts index acd81badc6..e4c6d5db74 100644 --- a/test/content-routing/content-routing.node.ts +++ b/test/content-routing/content-routing.node.ts @@ -129,7 +129,7 @@ describe('content-routing', () => { node = await createNode({ config: createBaseOptions({ contentRouters: [ - delegate + () => delegate ], dht: undefined }) @@ -233,7 +233,9 @@ describe('content-routing', () => { node = await createNode({ config: createRoutingOptions({ - contentRouters: [delegate] + contentRouters: [ + () => delegate + ] }) }) }) diff --git a/test/content-routing/dht/operation.node.ts b/test/content-routing/dht/operation.node.ts index 4711e1531f..d6c51866e1 100644 --- a/test/content-routing/dht/operation.node.ts +++ b/test/content-routing/dht/operation.node.ts @@ -15,7 +15,7 @@ const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/8000') const remoteListenAddr = multiaddr('/ip4/127.0.0.1/tcp/8001') async function getRemoteAddr (remotePeerId: PeerId, libp2p: Libp2pNode) { - const addrs = await libp2p.components.getPeerStore().addressBook.get(remotePeerId) + const addrs = await libp2p.components.peerStore.addressBook.get(remotePeerId) if (addrs.length === 0) { throw new Error('No addrs found') @@ -59,7 +59,7 @@ describe('DHT subsystem operates correctly', () => { remoteLibp2p.start() ]) - await libp2p.components.getPeerStore().addressBook.set(remotePeerId, [remoteListenAddr]) + await libp2p.components.peerStore.addressBook.set(remotePeerId, [remoteListenAddr]) remAddr = await getRemoteAddr(remotePeerId, libp2p) }) @@ -94,9 +94,9 @@ describe('DHT subsystem operates correctly', () => { pWaitFor(() => remoteLibp2p.dht.lan.routingTable.size === 1) ]) - await libp2p.components.getContentRouting().put(key, value) + await libp2p.components.contentRouting.put(key, value) - const fetchedValue = await remoteLibp2p.components.getContentRouting().get(key) + const fetchedValue = await remoteLibp2p.components.contentRouting.get(key) expect(fetchedValue).to.equalBytes(value) }) }) @@ -120,7 +120,7 @@ describe('DHT subsystem operates correctly', () => { await libp2p.start() await remoteLibp2p.start() - await libp2p.components.getPeerStore().addressBook.set(remotePeerId, [remoteListenAddr]) + await libp2p.components.peerStore.addressBook.set(remotePeerId, [remoteListenAddr]) remAddr = await getRemoteAddr(remotePeerId, libp2p) }) @@ -166,9 +166,9 @@ describe('DHT subsystem operates correctly', () => { await start(dht) await pWaitFor(() => libp2p.dht.lan.routingTable.size === 1) - await libp2p.components.getContentRouting().put(key, value) + await libp2p.components.contentRouting.put(key, value) - const fetchedValue = await remoteLibp2p.components.getContentRouting().get(key) + const fetchedValue = await remoteLibp2p.components.contentRouting.get(key) expect(fetchedValue).to.equalBytes(value) }) }) diff --git a/test/content-routing/dht/utils.ts b/test/content-routing/dht/utils.ts index e3a9106161..0e1eebe45f 100644 --- a/test/content-routing/dht/utils.ts +++ b/test/content-routing/dht/utils.ts @@ -1,10 +1,10 @@ -import { KadDHT } from '@libp2p/kad-dht' +import { kadDHT } from '@libp2p/kad-dht' import type { Libp2pOptions } from '../../../src/index.js' import { createBaseOptions } from '../../utils/base-options.js' export function createSubsystemOptions (...overrides: Libp2pOptions[]) { return createBaseOptions({ - dht: new KadDHT({ + dht: kadDHT({ kBucketSize: 20 }) }, ...overrides) diff --git a/test/content-routing/utils.ts b/test/content-routing/utils.ts index 25ae8b23be..67fb2f1a86 100644 --- a/test/content-routing/utils.ts +++ b/test/content-routing/utils.ts @@ -1,10 +1,10 @@ -import { KadDHT } from '@libp2p/kad-dht' +import { kadDHT } from '@libp2p/kad-dht' import type { Libp2pOptions } from '../../src/index.js' import { createBaseOptions } from '../utils/base-options.js' export function createRoutingOptions (...overrides: Libp2pOptions[]): Libp2pOptions { return createBaseOptions({ - dht: new KadDHT({ + dht: kadDHT({ kBucketSize: 20 }) }, ...overrides) diff --git a/test/core/consume-peer-record.spec.ts b/test/core/consume-peer-record.spec.ts index 0f5770d43b..e84f597035 100644 --- a/test/core/consume-peer-record.spec.ts +++ b/test/core/consume-peer-record.spec.ts @@ -1,7 +1,7 @@ /* eslint-env mocha */ -import { WebSockets } from '@libp2p/websockets' -import { Plaintext } from '../../src/insecure/index.js' +import { webSockets } from '@libp2p/websockets' +import { plaintext } from '../../src/insecure/index.js' import { createPeerId } from '../utils/creators/peer.js' import { multiaddr } from '@multiformats/multiaddr' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' @@ -15,10 +15,10 @@ describe('Consume peer record', () => { const config: Libp2pOptions = { peerId, transports: [ - new WebSockets() + webSockets() ], connectionEncryption: [ - new Plaintext() + plaintext() ] } libp2p = await createLibp2pNode(config) @@ -31,7 +31,7 @@ describe('Consume peer record', () => { it('should consume peer record when observed addrs are added', async () => { let done: () => void - libp2p.components.getPeerStore().addressBook.consumePeerRecord = async () => { + libp2p.components.peerStore.addressBook.consumePeerRecord = async () => { done() return true } @@ -42,7 +42,7 @@ describe('Consume peer record', () => { await libp2p.start() - libp2p.components.getAddressManager().addObservedAddr(multiaddr('/ip4/123.123.123.123/tcp/3983')) + libp2p.components.addressManager.addObservedAddr(multiaddr('/ip4/123.123.123.123/tcp/3983')) await p diff --git a/test/core/encryption.spec.ts b/test/core/encryption.spec.ts index e8f52b80f8..d49b04c312 100644 --- a/test/core/encryption.spec.ts +++ b/test/core/encryption.spec.ts @@ -1,8 +1,8 @@ /* eslint-env mocha */ import { expect } from 'aegir/chai' -import { WebSockets } from '@libp2p/websockets' -import { Plaintext } from '../../src/insecure/index.js' +import { webSockets } from '@libp2p/websockets' +import { plaintext } from '../../src/insecure/index.js' import { createLibp2p, Libp2pOptions } from '../../src/index.js' import { codes as ErrorCodes } from '../../src/errors.js' import { createPeerId } from '../utils/creators/peer.js' @@ -19,7 +19,7 @@ describe('Connection encryption configuration', () => { const config = { peerId, transports: [ - new WebSockets() + webSockets() ] } @@ -31,7 +31,7 @@ describe('Connection encryption configuration', () => { const config = { peerId, transports: [ - new WebSockets() + webSockets() ], connectionEncryption: [] } @@ -43,10 +43,10 @@ describe('Connection encryption configuration', () => { const config: Libp2pOptions = { peerId, transports: [ - new WebSockets() + webSockets() ], connectionEncryption: [ - new Plaintext() + plaintext() ] } await createLibp2p(config) diff --git a/test/core/get-public-key.spec.ts b/test/core/get-public-key.spec.ts index 041d9c4971..b401902b19 100644 --- a/test/core/get-public-key.spec.ts +++ b/test/core/get-public-key.spec.ts @@ -1,13 +1,13 @@ /* eslint-env mocha */ import { expect } from 'aegir/chai' -import { WebSockets } from '@libp2p/websockets' -import { Plaintext } from '../../src/insecure/index.js' +import { webSockets } from '@libp2p/websockets' +import { plaintext } from '../../src/insecure/index.js' import { createPeerId } from '../utils/creators/peer.js' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' import type { Libp2pOptions } from '../../src/index.js' import sinon from 'sinon' -import { KadDHT } from '@libp2p/kad-dht' +import { kadDHT } from '@libp2p/kad-dht' describe('getPublicKey', () => { let libp2p: Libp2pNode @@ -17,12 +17,12 @@ describe('getPublicKey', () => { const config: Libp2pOptions = { peerId, transports: [ - new WebSockets() + webSockets() ], connectionEncryption: [ - new Plaintext() + plaintext() ], - dht: new KadDHT() + dht: kadDHT() } libp2p = await createLibp2pNode(config) diff --git a/test/core/listening.node.ts b/test/core/listening.node.ts index 41eeddf84b..519ac79176 100644 --- a/test/core/listening.node.ts +++ b/test/core/listening.node.ts @@ -1,8 +1,8 @@ /* eslint-env mocha */ import { expect } from 'aegir/chai' -import { TCP } from '@libp2p/tcp' -import { Plaintext } from '../../src/insecure/index.js' +import { tcp } from '@libp2p/tcp' +import { plaintext } from '../../src/insecure/index.js' import { createPeerId } from '../utils/creators/peer.js' import type { PeerId } from '@libp2p/interface-peer-id' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' @@ -28,16 +28,16 @@ describe('Listening', () => { listen: [listenAddr] }, transports: [ - new TCP() + tcp() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) await libp2p.start() - const addrs = libp2p.components.getTransportManager().getAddrs() + const addrs = libp2p.components.transportManager.getAddrs() // Should get something like: // /ip4/127.0.0.1/tcp/50866 diff --git a/test/dialing/dial-request.spec.ts b/test/dialing/dial-request.spec.ts index 10763f513c..2a0674a46b 100644 --- a/test/dialing/dial-request.spec.ts +++ b/test/dialing/dial-request.spec.ts @@ -10,7 +10,11 @@ import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/int import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { multiaddr } from '@multiformats/multiaddr' import { DefaultDialer } from '../../src/connection-manager/dialer/index.js' -import { Components } from '@libp2p/components' +import type { Metrics } from '@libp2p/interface-metrics' +import type { PeerStore } from '@libp2p/interface-peer-store' +import type { TransportManager } from '@libp2p/interface-transport' +import type { ConnectionGater } from '@libp2p/interface-connection' +import { stubInterface } from 'ts-sinon' const error = new Error('dial failure') describe('Dial Request', () => { @@ -24,7 +28,13 @@ describe('Dial Request', () => { } const dialAction: DialAction = async (num) => await actions[num.toString()]() const controller = new AbortController() - const dialer = new DefaultDialer(new Components(), { + const dialer = new DefaultDialer({ + peerId: await createEd25519PeerId(), + metrics: stubInterface(), + peerStore: stubInterface(), + transportManager: stubInterface(), + connectionGater: stubInterface() + }, { maxParallelDials: 2 }) const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') @@ -54,7 +64,13 @@ describe('Dial Request', () => { } const dialAction: DialAction = async (num) => await actions[num.toString()]() const controller = new AbortController() - const dialer = new DefaultDialer(new Components(), { + const dialer = new DefaultDialer({ + peerId: await createEd25519PeerId(), + metrics: stubInterface(), + peerStore: stubInterface(), + transportManager: stubInterface(), + connectionGater: stubInterface() + }, { maxParallelDials: 2 }) const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') @@ -99,7 +115,13 @@ describe('Dial Request', () => { const dialAction: DialAction = async (num) => await actions[num.toString()]() const addrs = Object.keys(actions) const controller = new AbortController() - const dialer = new DefaultDialer(new Components(), { + const dialer = new DefaultDialer({ + peerId: await createEd25519PeerId(), + metrics: stubInterface(), + peerStore: stubInterface(), + transportManager: stubInterface(), + connectionGater: stubInterface() + }, { maxParallelDials: 2 }) const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') @@ -139,7 +161,13 @@ describe('Dial Request', () => { const dialAction: DialAction = async (num) => await actions[num.toString()]() const controller = new AbortController() - const dialer = new DefaultDialer(new Components(), { + const dialer = new DefaultDialer({ + peerId: await createEd25519PeerId(), + metrics: stubInterface(), + peerStore: stubInterface(), + transportManager: stubInterface(), + connectionGater: stubInterface() + }, { maxParallelDials: 2 }) const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') @@ -185,7 +213,13 @@ describe('Dial Request', () => { const dialAction: DialAction = async (num) => await actions[num.toString()]() const addrs = Object.keys(actions) const controller = new AbortController() - const dialer = new DefaultDialer(new Components(), { + const dialer = new DefaultDialer({ + peerId: await createEd25519PeerId(), + metrics: stubInterface(), + peerStore: stubInterface(), + transportManager: stubInterface(), + connectionGater: stubInterface() + }, { maxParallelDials: 2 }) const dialerReleaseTokenSpy = sinon.spy(dialer, 'releaseToken') @@ -238,7 +272,13 @@ describe('Dial Request', () => { const dialRequest = new DialRequest({ addrs: Object.keys(actions).map(str => multiaddr(str)), - dialer: new DefaultDialer(new Components(), { + dialer: new DefaultDialer({ + peerId: await createEd25519PeerId(), + metrics: stubInterface(), + peerStore: stubInterface(), + transportManager: stubInterface(), + connectionGater: stubInterface() + }, { maxParallelDials: 3 }), dialAction: async (ma, opts) => { diff --git a/test/dialing/direct.node.ts b/test/dialing/direct.node.ts index be0e2e0660..098b2f01d8 100644 --- a/test/dialing/direct.node.ts +++ b/test/dialing/direct.node.ts @@ -2,9 +2,9 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' -import { TCP } from '@libp2p/tcp' -import { Mplex } from '@libp2p/mplex' -import { Plaintext } from '../../src/insecure/index.js' +import { tcp } from '@libp2p/tcp' +import { mplex } from '@libp2p/mplex' +import { plaintext } from '../../src/insecure/index.js' import type { Multiaddr } from '@multiformats/multiaddr' import { multiaddr } from '@multiformats/multiaddr' @@ -25,13 +25,13 @@ import { DefaultTransportManager } from '../../src/transport-manager.js' import { codes as ErrorCodes } from '../../src/errors.js' import { mockConnectionGater, mockDuplex, mockMultiaddrConnection, mockUpgrader, mockConnection } from '@libp2p/interface-mocks' import Peers from '../fixtures/peers.js' -import { Components } from '@libp2p/components' import { createFromJSON } from '@libp2p/peer-id-factory' import type { PeerId } from '@libp2p/interface-peer-id' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' -import { PreSharedKeyConnectionProtector } from '../../src/pnet/index.js' +import { preSharedKey } from '../../src/pnet/index.js' import swarmKey from '../fixtures/swarm.key.js' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' +import { DefaultComponents } from '../../src/components.js' const swarmKeyBuffer = uint8ArrayFromString(swarmKey) const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0') @@ -41,8 +41,8 @@ describe('Dialing (direct, TCP)', () => { let remoteTM: DefaultTransportManager let localTM: DefaultTransportManager let remoteAddr: Multiaddr - let remoteComponents: Components - let localComponents: Components + let remoteComponents: DefaultComponents + let localComponents: DefaultComponents beforeEach(async () => { const [localPeerId, remotePeerId] = await Promise.all([ @@ -50,39 +50,39 @@ describe('Dialing (direct, TCP)', () => { createFromJSON(Peers[1]) ]) - remoteComponents = new Components({ + remoteComponents = new DefaultComponents({ peerId: remotePeerId, datastore: new MemoryDatastore(), upgrader: mockUpgrader(), - connectionGater: mockConnectionGater(), - peerStore: new PersistentPeerStore() + connectionGater: mockConnectionGater() }) - remoteComponents.setAddressManager(new DefaultAddressManager(remoteComponents, { + remoteComponents.peerStore = new PersistentPeerStore(remoteComponents) + remoteComponents.addressManager = new DefaultAddressManager(remoteComponents, { listen: [ listenAddr.toString() ] - })) + }) remoteTM = new DefaultTransportManager(remoteComponents) - remoteTM.add(new TCP()) + remoteTM.add(tcp()()) - localComponents = new Components({ + localComponents = new DefaultComponents({ peerId: localPeerId, datastore: new MemoryDatastore(), upgrader: mockUpgrader(), connectionGater: mockConnectionGater() }) - localComponents.setPeerStore(new PersistentPeerStore()) - localComponents.setConnectionManager(new DefaultConnectionManager({ + localComponents.peerStore = new PersistentPeerStore(localComponents) + localComponents.connectionManager = new DefaultConnectionManager(localComponents, { maxConnections: 100, minConnections: 50, autoDialInterval: 1000, inboundUpgradeTimeout: 1000 - })) + }) localTM = new DefaultTransportManager(localComponents) - localTM.add(new TCP()) + localTM.add(tcp()()) - localComponents.setTransportManager(localTM) + localComponents.transportManager = localTM await remoteTM.listen([listenAddr]) @@ -121,21 +121,21 @@ describe('Dialing (direct, TCP)', () => { }) it('should be able to connect to a given peer id', async () => { - await localComponents.getPeerStore().addressBook.set(remoteComponents.getPeerId(), remoteTM.getAddrs()) + await localComponents.peerStore.addressBook.set(remoteComponents.peerId, remoteTM.getAddrs()) const dialer = new DefaultDialer(localComponents) - const connection = await dialer.dial(remoteComponents.getPeerId()) + const connection = await dialer.dial(remoteComponents.peerId) expect(connection).to.exist() await connection.close() }) it('should fail to connect to a given peer with unsupported addresses', async () => { - await localComponents.getPeerStore().addressBook.add(remoteComponents.getPeerId(), [unsupportedAddr]) + await localComponents.peerStore.addressBook.add(remoteComponents.peerId, [unsupportedAddr]) const dialer = new DefaultDialer(localComponents) - await expect(dialer.dial(remoteComponents.getPeerId())) + await expect(dialer.dial(remoteComponents.peerId)) .to.eventually.be.rejectedWith(Error) .and.to.have.nested.property('.code', ErrorCodes.ERR_NO_VALID_ADDRESSES) }) @@ -144,7 +144,7 @@ describe('Dialing (direct, TCP)', () => { const remoteAddrs = remoteTM.getAddrs() const peerId = await createFromJSON(Peers[1]) - await localComponents.getPeerStore().addressBook.add(peerId, [...remoteAddrs, unsupportedAddr]) + await localComponents.peerStore.addressBook.add(peerId, [...remoteAddrs, unsupportedAddr]) const dialer = new DefaultDialer(localComponents) @@ -183,7 +183,7 @@ describe('Dialing (direct, TCP)', () => { ] const peerId = await createFromJSON(Peers[1]) - await localComponents.getPeerStore().addressBook.add(peerId, addrs) + await localComponents.peerStore.addressBook.add(peerId, addrs) const dialer = new DefaultDialer(localComponents, { maxParallelDials: 2 @@ -232,13 +232,13 @@ describe('libp2p.dialer (direct, TCP)', () => { listen: [listenAddr.toString()] }, transports: [ - new TCP() + tcp() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) await remoteLibp2p.handle('/echo/1.0.0', ({ stream }) => { @@ -246,7 +246,7 @@ describe('libp2p.dialer (direct, TCP)', () => { }) await remoteLibp2p.start() - remoteAddr = remoteLibp2p.components.getTransportManager().getAddrs()[0].encapsulate(`/p2p/${remotePeerId.toString()}`) + remoteAddr = remoteLibp2p.components.transportManager.getAddrs()[0].encapsulate(`/p2p/${remotePeerId.toString()}`) }) afterEach(async () => { @@ -265,19 +265,19 @@ describe('libp2p.dialer (direct, TCP)', () => { libp2p = await createLibp2pNode({ peerId, transports: [ - new TCP() + tcp() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) await libp2p.start() - await expect(libp2p.dial(remoteLibp2p.components.getTransportManager().getAddrs()[0])).to.eventually.be.rejected() + await expect(libp2p.dial(remoteLibp2p.components.transportManager.getAddrs()[0])).to.eventually.be.rejected() .with.property('code', ErrorCodes.ERR_INVALID_MULTIADDR) }) @@ -285,19 +285,19 @@ describe('libp2p.dialer (direct, TCP)', () => { libp2p = await createLibp2pNode({ peerId, transports: [ - new TCP() + tcp() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) await libp2p.start() - const dialerDialSpy = sinon.spy(libp2p.components.getConnectionManager(), 'openConnection') + const dialerDialSpy = sinon.spy(libp2p.connectionManager, 'openConnection') const connection = await libp2p.dial(remoteAddr) expect(connection).to.exist() @@ -312,21 +312,21 @@ describe('libp2p.dialer (direct, TCP)', () => { libp2p = await createLibp2pNode({ peerId, transports: [ - new TCP() + tcp() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) await libp2p.start() - const dialerDialSpy = sinon.spy(libp2p.components.getConnectionManager(), 'openConnection') + const dialerDialSpy = sinon.spy(libp2p.connectionManager, 'openConnection') - await libp2p.components.getPeerStore().addressBook.set(remotePeerId, remoteLibp2p.getMultiaddrs()) + await libp2p.components.peerStore.addressBook.set(remotePeerId, remoteLibp2p.getMultiaddrs()) const connection = await libp2p.dial(remotePeerId) expect(connection).to.exist() @@ -341,13 +341,13 @@ describe('libp2p.dialer (direct, TCP)', () => { libp2p = await createLibp2pNode({ peerId, transports: [ - new TCP() + tcp() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) @@ -367,7 +367,7 @@ describe('libp2p.dialer (direct, TCP)', () => { void pipe(stream, stream) }) - await libp2p.components.getPeerStore().addressBook.set(remotePeerId, remoteLibp2p.getMultiaddrs()) + await libp2p.components.peerStore.addressBook.set(remotePeerId, remoteLibp2p.getMultiaddrs()) const connection = await libp2p.dial(remotePeerId) // Create local to remote streams @@ -405,13 +405,13 @@ describe('libp2p.dialer (direct, TCP)', () => { libp2p = await createLibp2pNode({ peerId, transports: [ - new TCP() + tcp() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) @@ -431,13 +431,13 @@ describe('libp2p.dialer (direct, TCP)', () => { libp2p = await createLibp2pNode({ peerId, transports: [ - new TCP() + tcp() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) @@ -454,20 +454,20 @@ describe('libp2p.dialer (direct, TCP)', () => { libp2p = await createLibp2pNode({ peerId, transports: [ - new TCP() + tcp() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ], - connectionProtector: new PreSharedKeyConnectionProtector({ + connectionProtector: preSharedKey({ psk: swarmKeyBuffer }) }) - const protector = libp2p.components.getConnectionProtector() + const protector = libp2p.components.connectionProtector if (protector == null) { throw new Error('No protector was configured') @@ -475,7 +475,7 @@ describe('libp2p.dialer (direct, TCP)', () => { const protectorProtectSpy = sinon.spy(protector, 'protect') - remoteLibp2p.components.setConnectionProtector(new PreSharedKeyConnectionProtector({ enabled: true, psk: swarmKeyBuffer })) + remoteLibp2p.components.connectionProtector = preSharedKey({ enabled: true, psk: swarmKeyBuffer })() await libp2p.start() @@ -492,13 +492,13 @@ describe('libp2p.dialer (direct, TCP)', () => { libp2p = await createLibp2pNode({ peerId, transports: [ - new TCP() + tcp() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) @@ -507,7 +507,7 @@ describe('libp2p.dialer (direct, TCP)', () => { const dials = 10 const fullAddress = remoteAddr.encapsulate(`/p2p/${remoteLibp2p.peerId.toString()}`) - await libp2p.components.getPeerStore().addressBook.set(remotePeerId, remoteLibp2p.getMultiaddrs()) + await libp2p.components.peerStore.addressBook.set(remotePeerId, remoteLibp2p.getMultiaddrs()) const dialResults = await Promise.all([...new Array(dials)].map(async (_, index) => { if (index % 2 === 0) return await libp2p.dial(remoteLibp2p.peerId) return await libp2p.dial(fullAddress) @@ -528,13 +528,13 @@ describe('libp2p.dialer (direct, TCP)', () => { libp2p = await createLibp2pNode({ peerId, transports: [ - new TCP() + tcp() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) @@ -542,9 +542,9 @@ describe('libp2p.dialer (direct, TCP)', () => { const dials = 10 const error = new Error('Boom') - sinon.stub(libp2p.components.getTransportManager(), 'dial').callsFake(async () => await Promise.reject(error)) + sinon.stub(libp2p.components.transportManager, 'dial').callsFake(async () => await Promise.reject(error)) - await libp2p.components.getPeerStore().addressBook.set(remotePeerId, remoteLibp2p.getMultiaddrs()) + await libp2p.components.peerStore.addressBook.set(remotePeerId, remoteLibp2p.getMultiaddrs()) const dialResults: Array> = await pSettle([...new Array(dials)].map(async (_, index) => { if (index % 2 === 0) return await libp2p.dial(remoteLibp2p.peerId) return await libp2p.dial(remoteAddr) diff --git a/test/dialing/direct.spec.ts b/test/dialing/direct.spec.ts index cf5bc14fae..297b1d4f8e 100644 --- a/test/dialing/direct.spec.ts +++ b/test/dialing/direct.spec.ts @@ -4,10 +4,10 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' import pDefer from 'p-defer' import delay from 'delay' -import { WebSockets } from '@libp2p/websockets' +import { webSockets } from '@libp2p/websockets' import * as filters from '@libp2p/websockets/filters' -import { Mplex } from '@libp2p/mplex' -import { Plaintext } from '../../src/insecure/index.js' +import { mplex } from '@libp2p/mplex' +import { plaintext } from '../../src/insecure/index.js' import type { Multiaddr } from '@multiformats/multiaddr' import { multiaddr } from '@multiformats/multiaddr' import { AbortError } from '@libp2p/interfaces/errors' @@ -21,7 +21,6 @@ import { DefaultTransportManager } from '../../src/transport-manager.js' import { mockConnectionGater, mockDuplex, mockMultiaddrConnection, mockUpgrader, mockConnection } from '@libp2p/interface-mocks' import { createPeerId } from '../utils/creators/peer.js' import type { TransportManager } from '@libp2p/interface-transport' -import { Components } from '@libp2p/components' import { peerIdFromString } from '@libp2p/peer-id' import type { Connection } from '@libp2p/interface-connection' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' @@ -31,39 +30,40 @@ import Peers from '../fixtures/peers.js' import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' import type { PeerId } from '@libp2p/interface-peer-id' import { pEvent } from 'p-event' +import { DefaultComponents } from '../../src/components.js' const unsupportedAddr = multiaddr('/ip4/127.0.0.1/tcp/9999') describe('Dialing (direct, WebSockets)', () => { let localTM: TransportManager - let localComponents: Components + let localComponents: DefaultComponents let remoteAddr: Multiaddr - let remoteComponents: Components + let remoteComponents: DefaultComponents beforeEach(async () => { - localComponents = new Components({ + localComponents = new DefaultComponents({ peerId: await createFromJSON(Peers[0]), datastore: new MemoryDatastore(), upgrader: mockUpgrader(), connectionGater: mockConnectionGater() }) - localComponents.setPeerStore(new PersistentPeerStore({ - addressFilter: localComponents.getConnectionGater().filterMultiaddrForPeer - })) - localComponents.setConnectionManager(new DefaultConnectionManager({ + localComponents.peerStore = new PersistentPeerStore(localComponents, { + addressFilter: localComponents.connectionGater.filterMultiaddrForPeer + }) + localComponents.connectionManager = new DefaultConnectionManager(localComponents, { maxConnections: 100, minConnections: 50, autoDialInterval: 1000, inboundUpgradeTimeout: 1000 - })) + }) localTM = new DefaultTransportManager(localComponents) - localTM.add(new WebSockets({ filter: filters.all })) - localComponents.setTransportManager(localTM) + localTM.add(webSockets({ filter: filters.all })()) + localComponents.transportManager = localTM // this peer is spun up in .aegir.cjs remoteAddr = MULTIADDRS_WEBSOCKETS[0] - remoteComponents = new Components({ + remoteComponents = new DefaultComponents({ peerId: peerIdFromString(remoteAddr.getPeerId() ?? '') }) }) @@ -110,7 +110,7 @@ describe('Dialing (direct, WebSockets)', () => { const dialer = new DefaultDialer(localComponents) const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') - await localComponents.getPeerStore().addressBook.set(remotePeerId, [remoteAddr]) + await localComponents.peerStore.addressBook.set(remotePeerId, [remoteAddr]) const connection = await dialer.dial(remoteAddr) expect(connection).to.exist() @@ -120,7 +120,7 @@ describe('Dialing (direct, WebSockets)', () => { it('should fail to connect to an unsupported multiaddr', async () => { const dialer = new DefaultDialer(localComponents) - await expect(dialer.dial(unsupportedAddr.encapsulate(`/p2p/${remoteComponents.getPeerId().toString()}`))) + await expect(dialer.dial(unsupportedAddr.encapsulate(`/p2p/${remoteComponents.peerId.toString()}`))) .to.eventually.be.rejectedWith(Error) .and.to.have.nested.property('.code', ErrorCodes.ERR_NO_VALID_ADDRESSES) }) @@ -129,7 +129,7 @@ describe('Dialing (direct, WebSockets)', () => { const dialer = new DefaultDialer(localComponents) const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') - await localComponents.getPeerStore().addressBook.set(remotePeerId, [remoteAddr]) + await localComponents.peerStore.addressBook.set(remotePeerId, [remoteAddr]) const connection = await dialer.dial(remotePeerId) expect(connection).to.exist() @@ -140,7 +140,7 @@ describe('Dialing (direct, WebSockets)', () => { const dialer = new DefaultDialer(localComponents) const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') - await localComponents.getPeerStore().addressBook.set(remotePeerId, [unsupportedAddr]) + await localComponents.peerStore.addressBook.set(remotePeerId, [unsupportedAddr]) await expect(dialer.dial(remotePeerId)) .to.eventually.be.rejectedWith(Error) @@ -153,7 +153,7 @@ describe('Dialing (direct, WebSockets)', () => { }) const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') - await localComponents.getPeerStore().addressBook.set(remotePeerId, [remoteAddr]) + await localComponents.peerStore.addressBook.set(remotePeerId, [remoteAddr]) sinon.stub(localTM, 'dial').callsFake(async (addr, options) => { expect(options.signal).to.exist() @@ -175,7 +175,7 @@ describe('Dialing (direct, WebSockets)', () => { }) const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') - await localComponents.getPeerStore().addressBook.set(remotePeerId, Array.from({ length: 11 }, (_, i) => multiaddr(`/ip4/127.0.0.1/tcp/1500${i}/ws/p2p/12D3KooWHFKTMzwerBtsVmtz4ZZEQy2heafxzWw6wNn5PPYkBxJ5`))) + await localComponents.peerStore.addressBook.set(remotePeerId, Array.from({ length: 11 }, (_, i) => multiaddr(`/ip4/127.0.0.1/tcp/1500${i}/ws/p2p/12D3KooWHFKTMzwerBtsVmtz4ZZEQy2heafxzWw6wNn5PPYkBxJ5`))) await expect(dialer.dial(remoteAddr)) .to.eventually.be.rejected() @@ -198,10 +198,10 @@ describe('Dialing (direct, WebSockets)', () => { }) // Inject data in the AddressBook - await localComponents.getPeerStore().addressBook.add(remoteComponents.getPeerId(), peerMultiaddrs) + await localComponents.peerStore.addressBook.add(remoteComponents.peerId, peerMultiaddrs) // Perform 3 multiaddr dials - await dialer.dial(remoteComponents.getPeerId()) + await dialer.dial(remoteComponents.peerId) const sortedAddresses = peerMultiaddrs .map((m) => ({ multiaddr: m, isCertified: false })) @@ -225,7 +225,7 @@ describe('Dialing (direct, WebSockets)', () => { }) // Inject data in the AddressBook - await localComponents.getPeerStore().addressBook.add(remotePeerId, addrs) + await localComponents.peerStore.addressBook.add(remotePeerId, addrs) expect(dialer.tokens).to.have.lengthOf(2) @@ -263,7 +263,7 @@ describe('Dialing (direct, WebSockets)', () => { }) // Inject data in the AddressBook - await localComponents.getPeerStore().addressBook.add(remoteComponents.getPeerId(), addrs) + await localComponents.peerStore.addressBook.add(remoteComponents.peerId, addrs) expect(dialer.tokens).to.have.lengthOf(2) @@ -278,7 +278,7 @@ describe('Dialing (direct, WebSockets)', () => { }) // Perform 3 multiaddr dials - const dialPromise = dialer.dial(remoteComponents.getPeerId()) + const dialPromise = dialer.dial(remoteComponents.peerId) // Let the call stack run await delay(0) @@ -306,7 +306,7 @@ describe('Dialing (direct, WebSockets)', () => { }) // Perform dial - const dialPromise = dialer.dial(remoteComponents.getPeerId()) + const dialPromise = dialer.dial(remoteComponents.peerId) // Let the call stack run await delay(0) @@ -340,19 +340,19 @@ describe('libp2p.dialer (direct, WebSockets)', () => { libp2p = await createLibp2pNode({ peerId, transports: [ - new WebSockets({ + webSockets({ filter: filters.all }) ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) - const dialer = libp2p.components.getDialer() + const dialer = libp2p.components.dialer expect(dialer).to.exist() expect(dialer).to.have.property('tokens').with.lengthOf(Constants.MAX_PARALLEL_DIALS) @@ -364,15 +364,15 @@ describe('libp2p.dialer (direct, WebSockets)', () => { const config = { peerId, transports: [ - new WebSockets({ + webSockets({ filter: filters.all }) ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ], connectionManager: { maxParallelDials: 10, @@ -382,7 +382,7 @@ describe('libp2p.dialer (direct, WebSockets)', () => { } libp2p = await createLibp2pNode(config) - const dialer = libp2p.components.getDialer() + const dialer = libp2p.components.dialer expect(dialer).to.exist() expect(dialer).to.have.property('tokens').with.lengthOf(config.connectionManager.maxParallelDials) @@ -394,20 +394,20 @@ describe('libp2p.dialer (direct, WebSockets)', () => { libp2p = await createLibp2pNode({ peerId, transports: [ - new WebSockets({ + webSockets({ filter: filters.all }) ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) - const dialerDialSpy = sinon.spy(libp2p.components.getDialer(), 'dial') - const addressBookAddSpy = sinon.spy(libp2p.components.getPeerStore().addressBook, 'add') + const dialerDialSpy = sinon.spy(libp2p.components.dialer, 'dial') + const addressBookAddSpy = sinon.spy(libp2p.components.peerStore.addressBook, 'add') await libp2p.start() @@ -427,15 +427,15 @@ describe('libp2p.dialer (direct, WebSockets)', () => { libp2p = await createLibp2pNode({ peerId, transports: [ - new WebSockets({ + webSockets({ filter: filters.all }) ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) @@ -444,7 +444,7 @@ describe('libp2p.dialer (direct, WebSockets)', () => { } const identifySpy = sinon.spy(libp2p.identifyService, 'identify') - const protobookSetSpy = sinon.spy(libp2p.components.getPeerStore().protoBook, 'set') + const protobookSetSpy = sinon.spy(libp2p.components.peerStore.protoBook, 'set') const connectionPromise = pEvent(libp2p.connectionManager, 'peer:connect') await libp2p.start() @@ -467,15 +467,15 @@ describe('libp2p.dialer (direct, WebSockets)', () => { libp2p = await createLibp2pNode({ peerId, transports: [ - new WebSockets({ + webSockets({ filter: filters.all }) ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) @@ -495,15 +495,15 @@ describe('libp2p.dialer (direct, WebSockets)', () => { libp2p = await createLibp2pNode({ peerId, transports: [ - new WebSockets({ + webSockets({ filter: filters.all }) ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) @@ -516,19 +516,19 @@ describe('libp2p.dialer (direct, WebSockets)', () => { libp2p = await createLibp2pNode({ peerId, transports: [ - new WebSockets({ + webSockets({ filter: filters.all }) ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) - const dialer = libp2p.components.getDialer() as DefaultDialer + const dialer = libp2p.components.dialer as DefaultDialer sinon.stub(dialer, '_createDialTarget').callsFake(async () => { const deferredDial = pDefer() return await deferredDial.promise @@ -553,21 +553,21 @@ describe('libp2p.dialer (direct, WebSockets)', () => { libp2p = await createLibp2pNode({ peerId, transports: [ - new WebSockets({ + webSockets({ filter: filters.all }) ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) await libp2p.start() - const dialer = libp2p.components.getDialer() as DefaultDialer + const dialer = libp2p.components.dialer as DefaultDialer const dialerDestroyStub = sinon.spy(dialer, 'stop') await libp2p.stop() @@ -579,15 +579,15 @@ describe('libp2p.dialer (direct, WebSockets)', () => { libp2p = await createLibp2pNode({ peerId, transports: [ - new WebSockets({ + webSockets({ filter: filters.all }) ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) diff --git a/test/dialing/resolver.spec.ts b/test/dialing/resolver.spec.ts index 8aa12b342e..4c2f7dca77 100644 --- a/test/dialing/resolver.spec.ts +++ b/test/dialing/resolver.spec.ts @@ -14,7 +14,6 @@ import { Circuit } from '../../src/circuit/transport.js' import pDefer from 'p-defer' import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-mocks' import { peerIdFromString } from '@libp2p/peer-id' -import { WebSockets } from '@libp2p/websockets' const relayAddr = MULTIADDRS_WEBSOCKETS[0] @@ -154,7 +153,7 @@ describe('Dialing (resolvable addresses)', () => { const deferred = pDefer() // Stub transport - const transport = getTransport(libp2p, WebSockets.prototype[Symbol.toStringTag]) + const transport = getTransport(libp2p, '@libp2p/websockets') const stubTransport = sinon.stub(transport, 'dial') stubTransport.callsFake(async (multiaddr) => { expect(multiaddr.equals(dnsMa)).to.equal(true) @@ -200,7 +199,7 @@ describe('Dialing (resolvable addresses)', () => { resolver.returns(Promise.reject(new Error())) // Stub transport - const transport = getTransport(libp2p, WebSockets.prototype[Symbol.toStringTag]) + const transport = getTransport(libp2p, '@libp2p/websockets') const spy = sinon.spy(transport, 'dial') await expect(libp2p.dial(dialAddr)) @@ -211,7 +210,7 @@ describe('Dialing (resolvable addresses)', () => { }) function getTransport (libp2p: Libp2pNode, tag: string) { - const transport = libp2p.components.getTransportManager().getTransports().find(t => { + const transport = libp2p.components.transportManager.getTransports().find(t => { return t[Symbol.toStringTag] === tag }) diff --git a/test/fetch/fetch.node.ts b/test/fetch/fetch.node.ts index 82a27d2aee..3fd93b9890 100644 --- a/test/fetch/fetch.node.ts +++ b/test/fetch/fetch.node.ts @@ -2,9 +2,9 @@ import { expect } from 'aegir/chai' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' -import { TCP } from '@libp2p/tcp' -import { Mplex } from '@libp2p/mplex' -import { Plaintext } from '../../src/insecure/index.js' +import { tcp } from '@libp2p/tcp' +import { mplex } from '@libp2p/mplex' +import { plaintext } from '../../src/insecure/index.js' import { createPeerId } from '../utils/creators/peer.js' import { codes } from '../../src/errors.js' import type { PeerId } from '@libp2p/interface-peer-id' @@ -16,13 +16,13 @@ async function createNode (peerId: PeerId) { listen: ['/ip4/0.0.0.0/tcp/0'] }, transports: [ - new TCP() + tcp() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) } diff --git a/test/fetch/index.spec.ts b/test/fetch/index.spec.ts index 8ef13dd316..27a7e80db3 100644 --- a/test/fetch/index.spec.ts +++ b/test/fetch/index.spec.ts @@ -6,7 +6,6 @@ import { FetchService, FetchServiceInit } from '../../src/fetch/index.js' import Peers from '../fixtures/peers.js' import { mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-mocks' import { createFromJSON } from '@libp2p/peer-id-factory' -import { Components } from '@libp2p/components' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' import { start, stop } from '@libp2p/interfaces/startable' import { CustomEvent } from '@libp2p/interfaces/events' @@ -15,6 +14,7 @@ import delay from 'delay' import { pipe } from 'it-pipe' import { PersistentPeerStore } from '@libp2p/peer-store' import { MemoryDatastore } from 'datastore-core' +import { DefaultComponents } from '../../src/components.js' const defaultInit: FetchServiceInit = { protocolPrefix: 'ipfs', @@ -26,26 +26,26 @@ const defaultInit: FetchServiceInit = { async function createComponents (index: number) { const peerId = await createFromJSON(Peers[index]) - const components = new Components({ + const components = new DefaultComponents({ peerId, registrar: mockRegistrar(), upgrader: mockUpgrader(), - peerStore: new PersistentPeerStore(), - datastore: new MemoryDatastore(), - connectionManager: new DefaultConnectionManager({ - minConnections: 50, - maxConnections: 1000, - autoDialInterval: 1000, - inboundUpgradeTimeout: 1000 - }) + datastore: new MemoryDatastore() + }) + components.peerStore = new PersistentPeerStore(components) + components.connectionManager = new DefaultConnectionManager(components, { + minConnections: 50, + maxConnections: 1000, + autoDialInterval: 1000, + inboundUpgradeTimeout: 1000 }) return components } describe('fetch', () => { - let localComponents: Components - let remoteComponents: Components + let localComponents: DefaultComponents + let remoteComponents: DefaultComponents beforeEach(async () => { localComponents = await createComponents(0) @@ -83,11 +83,11 @@ describe('fetch', () => { // simulate connection between nodes const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) - localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { detail: localToRemote })) - remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { detail: remoteToLocal })) + localComponents.upgrader.dispatchEvent(new CustomEvent('connection', { detail: localToRemote })) + remoteComponents.upgrader.dispatchEvent(new CustomEvent('connection', { detail: remoteToLocal })) // Run fetch - const result = await localFetch.fetch(remoteComponents.getPeerId(), key) + const result = await localFetch.fetch(remoteComponents.peerId, key) expect(result).to.equalBytes(value) }) @@ -102,12 +102,12 @@ describe('fetch', () => { // simulate connection between nodes const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) - localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { detail: localToRemote })) - remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { detail: remoteToLocal })) + localComponents.upgrader.dispatchEvent(new CustomEvent('connection', { detail: localToRemote })) + remoteComponents.upgrader.dispatchEvent(new CustomEvent('connection', { detail: remoteToLocal })) // replace existing handler with a really slow one - await remoteComponents.getRegistrar().unhandle(remoteFetch.protocol) - await remoteComponents.getRegistrar().handle(remoteFetch.protocol, ({ stream }) => { + await remoteComponents.registrar.unhandle(remoteFetch.protocol) + await remoteComponents.registrar.handle(remoteFetch.protocol, ({ stream }) => { void pipe( stream, async function * (source) { @@ -128,7 +128,7 @@ describe('fetch', () => { const timeoutController = new TimeoutController(10) // Run fetch, should time out - await expect(localFetch.fetch(remoteComponents.getPeerId(), key, { + await expect(localFetch.fetch(remoteComponents.peerId, key, { signal: timeoutController.signal })) .to.eventually.be.rejected.with.property('code', 'ABORT_ERR') diff --git a/test/identify/index.spec.ts b/test/identify/index.spec.ts index d28b036c9a..5a5f0b247e 100644 --- a/test/identify/index.spec.ts +++ b/test/identify/index.spec.ts @@ -16,7 +16,6 @@ import drain from 'it-drain' import { pipe } from 'it-pipe' import { mockConnectionGater, mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-mocks' import { createFromJSON } from '@libp2p/peer-id-factory' -import { Components } from '@libp2p/components' import { PeerRecordUpdater } from '../../src/peer-record-updater.js' import { MULTICODEC_IDENTIFY, @@ -29,6 +28,7 @@ import { start, stop } from '@libp2p/interfaces/startable' import { TimeoutController } from 'timeout-abort-controller' import { CustomEvent } from '@libp2p/interfaces/events' import pDefer from 'p-defer' +import { DefaultComponents } from '../../src/components.js' const listenMaddrs = [multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] @@ -49,35 +49,35 @@ const protocols = [MULTICODEC_IDENTIFY, MULTICODEC_IDENTIFY_PUSH] async function createComponents (index: number) { const peerId = await createFromJSON(Peers[index]) - const components = new Components({ + const components = new DefaultComponents({ peerId, datastore: new MemoryDatastore(), registrar: mockRegistrar(), upgrader: mockUpgrader(), - connectionGater: mockConnectionGater(), - peerStore: new PersistentPeerStore(), - connectionManager: new DefaultConnectionManager({ - minConnections: 50, - maxConnections: 1000, - autoDialInterval: 1000, - inboundUpgradeTimeout: 1000 - }) + connectionGater: mockConnectionGater() + }) + components.peerStore = new PersistentPeerStore(components) + components.connectionManager = new DefaultConnectionManager(components, { + minConnections: 50, + maxConnections: 1000, + autoDialInterval: 1000, + inboundUpgradeTimeout: 1000 }) - components.setAddressManager(new DefaultAddressManager(components, { + components.addressManager = new DefaultAddressManager(components, { announce: listenMaddrs.map(ma => ma.toString()) - })) + }) const transportManager = new DefaultTransportManager(components) - components.setTransportManager(transportManager) + components.transportManager = transportManager - await components.getPeerStore().protoBook.set(peerId, protocols) + await components.peerStore.protoBook.set(peerId, protocols) return components } describe('identify', () => { - let localComponents: Components - let remoteComponents: Components + let localComponents: DefaultComponents + let remoteComponents: DefaultComponents let remotePeerRecordUpdater: PeerRecordUpdater @@ -111,8 +111,8 @@ describe('identify', () => { const [localToRemote] = connectionPair(localComponents, remoteComponents) - const localAddressBookConsumePeerRecordSpy = sinon.spy(localComponents.getPeerStore().addressBook, 'consumePeerRecord') - const localProtoBookSetSpy = sinon.spy(localComponents.getPeerStore().protoBook, 'set') + const localAddressBookConsumePeerRecordSpy = sinon.spy(localComponents.peerStore.addressBook, 'consumePeerRecord') + const localProtoBookSetSpy = sinon.spy(localComponents.peerStore.protoBook, 'set') // Make sure the remote peer has a peer record to share during identify await remotePeerRecordUpdater.update() @@ -124,7 +124,7 @@ describe('identify', () => { expect(localProtoBookSetSpy.callCount).to.equal(1) // Validate the remote peer gets updated in the peer store - const addresses = await localComponents.getPeerStore().addressBook.get(remoteComponents.getPeerId()) + const addresses = await localComponents.peerStore.addressBook.get(remoteComponents.peerId) expect(addresses).to.exist() expect(addresses).have.lengthOf(listenMaddrs.length) @@ -154,9 +154,9 @@ describe('identify', () => { const [localToRemote] = connectionPair(localComponents, remoteComponents) - sinon.stub(localComponents.getPeerStore().addressBook, 'consumePeerRecord').throws() + sinon.stub(localComponents.peerStore.addressBook, 'consumePeerRecord').throws() - const localProtoBookSetSpy = sinon.spy(localComponents.getPeerStore().protoBook, 'set') + const localProtoBookSetSpy = sinon.spy(localComponents.peerStore.protoBook, 'set') // Run identify await localIdentify.identify(localToRemote) @@ -164,7 +164,7 @@ describe('identify', () => { expect(localProtoBookSetSpy.callCount).to.equal(1) // Validate the remote peer gets updated in the peer store - const addresses = await localComponents.getPeerStore().addressBook.get(remoteComponents.getPeerId()) + const addresses = await localComponents.peerStore.addressBook.get(remoteComponents.peerId) expect(addresses).to.exist() expect(addresses).have.lengthOf(listenMaddrs.length) @@ -182,17 +182,17 @@ describe('identify', () => { const [localToRemote] = connectionPair(localComponents, remoteComponents) // send an invalid message - await remoteComponents.getRegistrar().unhandle(MULTICODEC_IDENTIFY) - await remoteComponents.getRegistrar().handle(MULTICODEC_IDENTIFY, (data) => { + await remoteComponents.registrar.unhandle(MULTICODEC_IDENTIFY) + await remoteComponents.registrar.handle(MULTICODEC_IDENTIFY, (data) => { void Promise.resolve().then(async () => { const { connection, stream } = data - const signedPeerRecord = await remoteComponents.getPeerStore().addressBook.getRawEnvelope(remoteComponents.getPeerId()) + const signedPeerRecord = await remoteComponents.peerStore.addressBook.getRawEnvelope(remoteComponents.peerId) const message = Message.Identify.encode({ protocolVersion: '123', agentVersion: '123', // send bad public key - publicKey: localComponents.getPeerId().publicKey ?? new Uint8Array(0), + publicKey: localComponents.peerId.publicKey ?? new Uint8Array(0), listenAddrs: [], signedPeerRecord, observedAddr: connection.remoteAddr.bytes, @@ -224,16 +224,16 @@ describe('identify', () => { } }) - await expect(localComponents.getPeerStore().metadataBook.getValue(localComponents.getPeerId(), 'AgentVersion')) + await expect(localComponents.peerStore.metadataBook.getValue(localComponents.peerId, 'AgentVersion')) .to.eventually.be.undefined() - await expect(localComponents.getPeerStore().metadataBook.getValue(localComponents.getPeerId(), 'ProtocolVersion')) + await expect(localComponents.peerStore.metadataBook.getValue(localComponents.peerId, 'ProtocolVersion')) .to.eventually.be.undefined() await start(localIdentify) - await expect(localComponents.getPeerStore().metadataBook.getValue(localComponents.getPeerId(), 'AgentVersion')) + await expect(localComponents.peerStore.metadataBook.getValue(localComponents.peerId, 'AgentVersion')) .to.eventually.deep.equal(uint8ArrayFromString(agentVersion)) - await expect(localComponents.getPeerStore().metadataBook.getValue(localComponents.getPeerId(), 'ProtocolVersion')) + await expect(localComponents.peerStore.metadataBook.getValue(localComponents.peerId, 'ProtocolVersion')) .to.eventually.be.ok() await stop(localIdentify) @@ -249,8 +249,8 @@ describe('identify', () => { const [localToRemote] = connectionPair(localComponents, remoteComponents) // replace existing handler with a really slow one - await remoteComponents.getRegistrar().unhandle(MULTICODEC_IDENTIFY) - await remoteComponents.getRegistrar().handle(MULTICODEC_IDENTIFY, ({ stream }) => { + await remoteComponents.registrar.unhandle(MULTICODEC_IDENTIFY) + await remoteComponents.registrar.handle(MULTICODEC_IDENTIFY, ({ stream }) => { void pipe( stream, async function * (source) { @@ -297,7 +297,7 @@ describe('identify', () => { const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) // handle incoming identify requests and send too much data - await localComponents.getRegistrar().handle('/ipfs/id/1.0.0', ({ stream }) => { + await localComponents.registrar.handle('/ipfs/id/1.0.0', ({ stream }) => { const data = new Uint8Array(1024) void Promise.resolve().then(async () => { @@ -313,10 +313,10 @@ describe('identify', () => { }) // ensure connections are registered by connection manager - localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { + localComponents.upgrader.dispatchEvent(new CustomEvent('connection', { detail: localToRemote })) - remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { + remoteComponents.upgrader.dispatchEvent(new CustomEvent('connection', { detail: remoteToLocal })) @@ -343,7 +343,7 @@ describe('identify', () => { const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) // handle incoming identify requests and don't send anything - await localComponents.getRegistrar().handle('/ipfs/id/1.0.0', ({ stream }) => { + await localComponents.registrar.handle('/ipfs/id/1.0.0', ({ stream }) => { const data = new Uint8Array(1024) void Promise.resolve().then(async () => { @@ -368,10 +368,10 @@ describe('identify', () => { }) // ensure connections are registered by connection manager - localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { + localComponents.upgrader.dispatchEvent(new CustomEvent('connection', { detail: localToRemote })) - remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { + remoteComponents.upgrader.dispatchEvent(new CustomEvent('connection', { detail: remoteToLocal })) diff --git a/test/identify/push.spec.ts b/test/identify/push.spec.ts index f48d16e2b7..fb3c6e722e 100644 --- a/test/identify/push.spec.ts +++ b/test/identify/push.spec.ts @@ -12,7 +12,6 @@ import drain from 'it-drain' import { pipe } from 'it-pipe' import { mockConnectionGater, mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-mocks' import { createFromJSON } from '@libp2p/peer-id-factory' -import { Components } from '@libp2p/components' import { PeerRecordUpdater } from '../../src/peer-record-updater.js' import { MULTICODEC_IDENTIFY, @@ -24,6 +23,10 @@ import { CustomEvent } from '@libp2p/interfaces/events' import delay from 'delay' import { pEvent } from 'p-event' import { start, stop } from '@libp2p/interfaces/startable' +import { stubInterface } from 'ts-sinon' +import type { Dialer } from '@libp2p/interface-connection-manager' +import type { Metrics } from '@libp2p/interface-metrics' +import { DefaultComponents } from '../../src/components.js' const listenMaddrs = [multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] @@ -41,38 +44,40 @@ const defaultInit: IdentifyServiceInit = { const protocols = [MULTICODEC_IDENTIFY, MULTICODEC_IDENTIFY_PUSH] -async function createComponents (index: number) { +async function createComponents (index: number): Promise { const peerId = await createFromJSON(Peers[index]) - const components = new Components({ + const components = new DefaultComponents({ peerId, datastore: new MemoryDatastore(), registrar: mockRegistrar(), upgrader: mockUpgrader(), connectionGater: mockConnectionGater(), - peerStore: new PersistentPeerStore(), - connectionManager: new DefaultConnectionManager({ - minConnections: 50, - maxConnections: 1000, - autoDialInterval: 1000, - inboundUpgradeTimeout: 1000 - }) + metrics: stubInterface(), + dialer: stubInterface() + }) + components.peerStore = new PersistentPeerStore(components) + components.connectionManager = new DefaultConnectionManager(components, { + minConnections: 50, + maxConnections: 1000, + autoDialInterval: 1000, + inboundUpgradeTimeout: 1000 }) - components.setAddressManager(new DefaultAddressManager(components, { + components.addressManager = new DefaultAddressManager(components, { announce: listenMaddrs.map(ma => ma.toString()) - })) + }) const transportManager = new DefaultTransportManager(components) - components.setTransportManager(transportManager) + components.transportManager = transportManager - await components.getPeerStore().protoBook.set(peerId, protocols) + await components.peerStore.protoBook.set(peerId, protocols) return components } describe('identify (push)', () => { - let localComponents: Components - let remoteComponents: Components + let localComponents: DefaultComponents + let remoteComponents: DefaultComponents let localPeerRecordUpdater: PeerRecordUpdater @@ -107,10 +112,10 @@ describe('identify (push)', () => { const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) // ensure connections are registered by connection manager - localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { + localComponents.upgrader.dispatchEvent(new CustomEvent('connection', { detail: localToRemote })) - remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { + remoteComponents.upgrader.dispatchEvent(new CustomEvent('connection', { detail: remoteToLocal })) @@ -122,21 +127,21 @@ describe('identify (push)', () => { const updatedAddress = multiaddr('/ip4/127.0.0.1/tcp/48322') // should have protocols but not our new one - const identifiedProtocols = await remoteComponents.getPeerStore().protoBook.get(localComponents.getPeerId()) + const identifiedProtocols = await remoteComponents.peerStore.protoBook.get(localComponents.peerId) expect(identifiedProtocols).to.not.be.empty() expect(identifiedProtocols).to.not.include(updatedProtocol) // should have addresses but not our new one - const identifiedAddresses = await remoteComponents.getPeerStore().addressBook.get(localComponents.getPeerId()) + const identifiedAddresses = await remoteComponents.peerStore.addressBook.get(localComponents.peerId) expect(identifiedAddresses).to.not.be.empty() expect(identifiedAddresses.map(a => a.multiaddr.toString())).to.not.include(updatedAddress.toString()) // update local data - change event will trigger push - await localComponents.getPeerStore().protoBook.add(localComponents.getPeerId(), [updatedProtocol]) - await localComponents.getPeerStore().addressBook.add(localComponents.getPeerId(), [updatedAddress]) + await localComponents.peerStore.protoBook.add(localComponents.peerId, [updatedProtocol]) + await localComponents.peerStore.addressBook.add(localComponents.peerId, [updatedAddress]) // needed to update the peer record and send our supported addresses - const addressManager = localComponents.getAddressManager() + const addressManager = localComponents.addressManager addressManager.getAddresses = () => { return [updatedAddress] } @@ -148,7 +153,7 @@ describe('identify (push)', () => { await localPeerRecordUpdater.update() // wait for the remote peer store to notice the changes - const eventPromise = pEvent(remoteComponents.getPeerStore(), 'change:multiaddrs') + const eventPromise = pEvent(remoteComponents.peerStore, 'change:multiaddrs') // push updated peer record to connections await localIdentify.pushToPeerStore() @@ -156,12 +161,12 @@ describe('identify (push)', () => { await eventPromise // should have new protocol - const updatedProtocols = await remoteComponents.getPeerStore().protoBook.get(localComponents.getPeerId()) + const updatedProtocols = await remoteComponents.peerStore.protoBook.get(localComponents.peerId) expect(updatedProtocols).to.not.be.empty() expect(updatedProtocols).to.include(updatedProtocol) // should have new address - const updatedAddresses = await remoteComponents.getPeerStore().addressBook.get(localComponents.getPeerId()) + const updatedAddresses = await remoteComponents.peerStore.addressBook.get(localComponents.peerId) expect(updatedAddresses.map(a => { return { multiaddr: a.multiaddr.toString(), @@ -191,8 +196,8 @@ describe('identify (push)', () => { const [localToRemote] = connectionPair(localComponents, remoteComponents) // replace existing handler with a really slow one - await remoteComponents.getRegistrar().unhandle(MULTICODEC_IDENTIFY_PUSH) - await remoteComponents.getRegistrar().handle(MULTICODEC_IDENTIFY_PUSH, ({ stream }) => { + await remoteComponents.registrar.unhandle(MULTICODEC_IDENTIFY_PUSH) + await remoteComponents.registrar.handle(MULTICODEC_IDENTIFY_PUSH, ({ stream }) => { void pipe( stream, async function * (source) { @@ -238,10 +243,10 @@ describe('identify (push)', () => { const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) // ensure connections are registered by connection manager - localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { + localComponents.upgrader.dispatchEvent(new CustomEvent('connection', { detail: localToRemote })) - remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { + remoteComponents.upgrader.dispatchEvent(new CustomEvent('connection', { detail: remoteToLocal })) @@ -253,39 +258,39 @@ describe('identify (push)', () => { const updatedAddress = multiaddr('/ip4/127.0.0.1/tcp/48322') // should have protocols but not our new one - const identifiedProtocols = await remoteComponents.getPeerStore().protoBook.get(localComponents.getPeerId()) + const identifiedProtocols = await remoteComponents.peerStore.protoBook.get(localComponents.peerId) expect(identifiedProtocols).to.not.be.empty() expect(identifiedProtocols).to.not.include(updatedProtocol) // should have addresses but not our new one - const identifiedAddresses = await remoteComponents.getPeerStore().addressBook.get(localComponents.getPeerId()) + const identifiedAddresses = await remoteComponents.peerStore.addressBook.get(localComponents.peerId) expect(identifiedAddresses).to.not.be.empty() expect(identifiedAddresses.map(a => a.multiaddr.toString())).to.not.include(updatedAddress.toString()) // update local data - change event will trigger push - await localComponents.getPeerStore().protoBook.add(localComponents.getPeerId(), [updatedProtocol]) - await localComponents.getPeerStore().addressBook.add(localComponents.getPeerId(), [updatedAddress]) + await localComponents.peerStore.protoBook.add(localComponents.peerId, [updatedProtocol]) + await localComponents.peerStore.addressBook.add(localComponents.peerId, [updatedAddress]) // needed to send our supported addresses - const addressManager = localComponents.getAddressManager() + const addressManager = localComponents.addressManager addressManager.getAddresses = () => { return [updatedAddress] } // wait until remote peer store notices protocol list update - const waitForUpdate = pEvent(remoteComponents.getPeerStore(), 'change:protocols') + const waitForUpdate = pEvent(remoteComponents.peerStore, 'change:protocols') await localIdentify.pushToPeerStore() await waitForUpdate // should have new protocol - const updatedProtocols = await remoteComponents.getPeerStore().protoBook.get(localComponents.getPeerId()) + const updatedProtocols = await remoteComponents.peerStore.protoBook.get(localComponents.peerId) expect(updatedProtocols).to.not.be.empty() expect(updatedProtocols).to.include(updatedProtocol) // should have new address - const updatedAddresses = await remoteComponents.getPeerStore().addressBook.get(localComponents.getPeerId()) + const updatedAddresses = await remoteComponents.peerStore.addressBook.get(localComponents.peerId) expect(updatedAddresses.map(a => { return { multiaddr: a.multiaddr.toString(), diff --git a/test/insecure/compliance.spec.ts b/test/insecure/compliance.spec.ts index 88b013d1d0..d51f14ee69 100644 --- a/test/insecure/compliance.spec.ts +++ b/test/insecure/compliance.spec.ts @@ -1,12 +1,12 @@ /* eslint-env mocha */ import suite from '@libp2p/interface-connection-encrypter-compliance-tests' -import { Plaintext } from '../../src/insecure/index.js' +import { plaintext } from '../../src/insecure/index.js' describe('plaintext compliance', () => { suite({ async setup () { - return new Plaintext() + return plaintext()() }, async teardown () { diff --git a/test/insecure/plaintext.spec.ts b/test/insecure/plaintext.spec.ts index 6ce70547c3..27fa0a3eb6 100644 --- a/test/insecure/plaintext.spec.ts +++ b/test/insecure/plaintext.spec.ts @@ -3,7 +3,7 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' import Peers from '../fixtures/peers.js' -import { Plaintext } from '../../src/insecure/index.js' +import { plaintext } from '../../src/insecure/index.js' import { InvalidCryptoExchangeError, UnexpectedPeerError @@ -19,7 +19,7 @@ describe('plaintext', () => { let localPeer: PeerId let remotePeer: PeerId let wrongPeer: PeerId - let plaintext: ConnectionEncrypter + let encrypter: ConnectionEncrypter beforeEach(async () => { [localPeer, remotePeer, wrongPeer] = await Promise.all([ @@ -28,7 +28,7 @@ describe('plaintext', () => { createFromJSON(Peers[2]) ]) - plaintext = new Plaintext() + encrypter = plaintext()() }) afterEach(() => { @@ -45,8 +45,8 @@ describe('plaintext', () => { }) await Promise.all([ - plaintext.secureInbound(remotePeer, inbound), - plaintext.secureOutbound(localPeer, outbound, wrongPeer) + encrypter.secureInbound(remotePeer, inbound), + encrypter.secureOutbound(localPeer, outbound, wrongPeer) ]).then(() => expect.fail('should have failed'), (err) => { expect(err).to.exist() expect(err).to.have.property('code', UnexpectedPeerError.code) @@ -66,8 +66,8 @@ describe('plaintext', () => { }) await expect(Promise.all([ - plaintext.secureInbound(localPeer, inbound), - plaintext.secureOutbound(remotePeer, outbound, localPeer) + encrypter.secureInbound(localPeer, inbound), + encrypter.secureOutbound(remotePeer, outbound, localPeer) ])) .to.eventually.be.rejected.with.property('code', InvalidCryptoExchangeError.code) }) diff --git a/test/interop.ts b/test/interop.ts index 3d4b9f3994..edcac8f601 100644 --- a/test/interop.ts +++ b/test/interop.ts @@ -4,20 +4,20 @@ import { createServer } from '@libp2p/daemon-server' import { createClient } from '@libp2p/daemon-client' import { createLibp2p, Libp2pOptions } from '../src/index.js' import { Noise } from '@chainsafe/libp2p-noise' -import { TCP } from '@libp2p/tcp' +import { tcp } from '@libp2p/tcp' import { multiaddr } from '@multiformats/multiaddr' -import { KadDHT } from '@libp2p/kad-dht' +import { kadDHT } from '@libp2p/kad-dht' import { path as p2pd } from 'go-libp2p' import { execa } from 'execa' import pDefer from 'p-defer' import { logger } from '@libp2p/logger' -import { Mplex } from '@libp2p/mplex' -import { Yamux } from '@chainsafe/libp2p-yamux' +import { mplex } from '@libp2p/mplex' +import { yamux } from '@chainsafe/libp2p-yamux' import fs from 'fs' import { unmarshalPrivateKey } from '@libp2p/crypto/keys' import type { PeerId } from '@libp2p/interface-peer-id' import { peerIdFromKeys } from '@libp2p/peer-id' -import { FloodSub } from '@libp2p/floodsub' +import { floodsub } from '@libp2p/floodsub' // IPFS_LOGGING=debug DEBUG=libp2p*,go-libp2p:* npm run test:interop @@ -93,46 +93,49 @@ async function createJsPeer (options: SpawnOptions): Promise { addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, - transports: [new TCP()], + transports: [tcp()], streamMuxers: [], - connectionEncryption: [new Noise()] + connectionEncryption: [() => new Noise()] } if (options.muxer === 'mplex') { - opts.streamMuxers?.push(new Mplex()) + opts.streamMuxers?.push(mplex()) } else { - opts.streamMuxers?.push(new Yamux()) - } - - if (options.dht === true) { - // go-libp2p-daemon only has the older single-table DHT instead of the dual - // lan/wan version found in recent go-ipfs versions. unfortunately it's been - // abandoned so here we simulate the older config with the js implementation - const dht = new KadDHT({ - clientMode: false - }) - const lan = dht.lan - - const protocol = '/ipfs/kad/1.0.0' - lan.protocol = protocol - // @ts-expect-error - lan.network.protocol = protocol - // @ts-expect-error - lan.topologyListener.protocol = protocol - - // @ts-expect-error - opts.dht = lan + opts.streamMuxers?.push(yamux()) } if (options.pubsub === true) { if (options.pubsubRouter === 'floodsub') { - opts.pubsub = new FloodSub() + opts.pubsub = floodsub() } else { - opts.pubsub = new FloodSub() + opts.pubsub = floodsub() + } + } + + if (options.dht === true) { + opts.dht = (components: any) => { + const dht = kadDHT({ + clientMode: false + })(components) + + // go-libp2p-daemon only has the older single-table DHT instead of the dual + // lan/wan version found in recent go-ipfs versions. unfortunately it's been + // abandoned so here we simulate the older config with the js implementation + const lan = dht.lan + + const protocol = '/ipfs/kad/1.0.0' + lan.protocol = protocol + // @ts-expect-error + lan.network.protocol = protocol + // @ts-expect-error + lan.topologyListener.protocol = protocol + + return lan } } const node = await createLibp2p(opts) + const server = await createServer(multiaddr('/ip4/0.0.0.0/tcp/0'), node) await server.start() diff --git a/test/keychain/cms-interop.spec.ts b/test/keychain/cms-interop.spec.ts index 6349ce4d8b..871d46821e 100644 --- a/test/keychain/cms-interop.spec.ts +++ b/test/keychain/cms-interop.spec.ts @@ -6,16 +6,19 @@ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { MemoryDatastore } from 'datastore-core/memory' import { KeyChain } from '../../src/keychain/index.js' -import { Components } from '@libp2p/components' +import { createEd25519PeerId } from '@libp2p/peer-id-factory' describe('cms interop', () => { const passPhrase = 'this is not a secure phrase' const aliceKeyName = 'cms-interop-alice' let ks: KeyChain - before(() => { + before(async () => { const datastore = new MemoryDatastore() - ks = new KeyChain(new Components({ datastore }), { pass: passPhrase }) + ks = new KeyChain({ + peerId: await createEd25519PeerId(), + datastore + }, { pass: passPhrase }) }) const plainData = uint8ArrayFromString('This is a message from Alice to Bob') diff --git a/test/keychain/keychain.spec.ts b/test/keychain/keychain.spec.ts index d93fe7e11f..b58c8e8a15 100644 --- a/test/keychain/keychain.spec.ts +++ b/test/keychain/keychain.spec.ts @@ -9,10 +9,9 @@ import { Key } from 'interface-datastore/key' import { MemoryDatastore } from 'datastore-core/memory' import { KeyChain, KeyChainInit, KeyInfo } from '../../src/keychain/index.js' import { pbkdf2 } from '@libp2p/crypto' -import { Components } from '@libp2p/components' import type { Datastore } from 'interface-datastore' import type { PeerId } from '@libp2p/interface-peer-id' -import { createFromPrivKey } from '@libp2p/peer-id-factory' +import { createEd25519PeerId, createFromPrivKey } from '@libp2p/peer-id-factory' import { unmarshalPrivateKey } from '@libp2p/crypto/keys' describe('keychain', () => { @@ -28,8 +27,14 @@ describe('keychain', () => { datastore1 = new MemoryDatastore() datastore2 = new MemoryDatastore() - ks = new KeyChain(new Components({ datastore: datastore2 }), { pass: passPhrase }) - emptyKeystore = new KeyChain(new Components({ datastore: datastore1 }), { pass: passPhrase }) + ks = new KeyChain({ + peerId: await createEd25519PeerId(), + datastore: datastore2 + }, { pass: passPhrase }) + emptyKeystore = new KeyChain({ + peerId: await createEd25519PeerId(), + datastore: datastore1 + }, { pass: passPhrase }) await datastore1.open() await datastore2.open() @@ -40,36 +45,63 @@ describe('keychain', () => { await datastore2.close() }) - it('can start without a password', () => { - expect(() => new KeyChain(new Components({ datastore: datastore2 }), {})).to.not.throw() + it('can start without a password', async () => { + await expect(async function () { + return new KeyChain({ + peerId: await createEd25519PeerId(), + datastore: datastore2 + }, {}) + }()).to.eventually.be.ok() }) - it('needs a NIST SP 800-132 non-weak pass phrase', () => { - expect(() => new KeyChain(new Components({ datastore: datastore2 }), { pass: '< 20 character' })).to.throw() + it('needs a NIST SP 800-132 non-weak pass phrase', async () => { + await expect(async function () { + return new KeyChain({ + peerId: await createEd25519PeerId(), + datastore: datastore2 + }, { pass: '< 20 character' }) + }()).to.eventually.be.rejected() }) it('has default options', () => { expect(KeyChain.options).to.exist() }) - it('supports supported hashing alorithms', () => { - const ok = new KeyChain(new Components({ datastore: datastore2 }), { pass: passPhrase, dek: { hash: 'sha2-256', salt: 'salt-salt-salt-salt', iterationCount: 1000, keyLength: 14 } }) + it('supports supported hashing alorithms', async () => { + const ok = new KeyChain({ + peerId: await createEd25519PeerId(), + datastore: datastore2 + }, { pass: passPhrase, dek: { hash: 'sha2-256', salt: 'salt-salt-salt-salt', iterationCount: 1000, keyLength: 14 } }) expect(ok).to.exist() }) - it('does not support unsupported hashing alorithms', () => { - expect(() => new KeyChain(new Components({ datastore: datastore2 }), { pass: passPhrase, dek: { hash: 'my-hash', salt: 'salt-salt-salt-salt', iterationCount: 1000, keyLength: 14 } })).to.throw() + it('does not support unsupported hashing alorithms', async () => { + await expect(async function () { + return new KeyChain({ + peerId: await createEd25519PeerId(), + datastore: datastore2 + }, { pass: passPhrase, dek: { hash: 'my-hash', salt: 'salt-salt-salt-salt', iterationCount: 1000, keyLength: 14 } }) + }()).to.eventually.be.rejected() }) it('can list keys without a password', async () => { - const keychain = new KeyChain(new Components({ datastore: datastore2 }), {}) + const keychain = new KeyChain({ + peerId: await createEd25519PeerId(), + datastore: datastore2 + }, {}) expect(await keychain.listKeys()).to.have.lengthOf(0) }) it('can find a key without a password', async () => { - const keychain = new KeyChain(new Components({ datastore: datastore2 }), {}) - const keychainWithPassword = new KeyChain(new Components({ datastore: datastore2 }), { pass: `hello-${Date.now()}-${Date.now()}` }) + const keychain = new KeyChain({ + peerId: await createEd25519PeerId(), + datastore: datastore2 + }, {}) + const keychainWithPassword = new KeyChain({ + peerId: await createEd25519PeerId(), + datastore: datastore2 + }, { pass: `hello-${Date.now()}-${Date.now()}` }) const name = `key-${Math.random()}` const { id } = await keychainWithPassword.createKey(name, 'Ed25519') @@ -78,8 +110,14 @@ describe('keychain', () => { }) it('can remove a key without a password', async () => { - const keychainWithoutPassword = new KeyChain(new Components({ datastore: datastore2 }), {}) - const keychainWithPassword = new KeyChain(new Components({ datastore: datastore2 }), { pass: `hello-${Date.now()}-${Date.now()}` }) + const keychainWithoutPassword = new KeyChain({ + peerId: await createEd25519PeerId(), + datastore: datastore2 + }, {}) + const keychainWithPassword = new KeyChain({ + peerId: await createEd25519PeerId(), + datastore: datastore2 + }, { pass: `hello-${Date.now()}-${Date.now()}` }) const name = `key-${Math.random()}` expect(await keychainWithPassword.createKey(name, 'Ed25519')).to.have.property('name', name) @@ -89,16 +127,22 @@ describe('keychain', () => { }) it('requires a name to create a password', async () => { - const keychain = new KeyChain(new Components({ datastore: datastore2 }), {}) + const keychain = new KeyChain({ + peerId: await createEd25519PeerId(), + datastore: datastore2 + }, {}) // @ts-expect-error invalid parameters - await expect(keychain.createKey(undefined, 'derp')).to.be.rejected() + await expect(keychain.createKey(undefined, 'derp')).to.eventually.be.rejected() }) - it('can generate options', () => { + it('can generate options', async () => { const options = KeyChain.generateOptions() options.pass = passPhrase - const chain = new KeyChain(new Components({ datastore: datastore2 }), options) + const chain = new KeyChain({ + peerId: await createEd25519PeerId(), + datastore: datastore2 + }, options) expect(chain).to.exist() }) @@ -433,7 +477,10 @@ describe('keychain', () => { hash: 'sha2-512' } } - kc = new KeyChain(new Components({ datastore: ds }), options) + kc = new KeyChain({ + peerId: await createEd25519PeerId(), + datastore: ds + }, options) await ds.open() }) diff --git a/test/metrics/index.node.ts b/test/metrics/index.node.ts index 380989eb24..8924aea4cc 100644 --- a/test/metrics/index.node.ts +++ b/test/metrics/index.node.ts @@ -34,7 +34,7 @@ describe('libp2p.metrics', () => { config: createBaseOptions() }) - expect(libp2p.components.getMetrics()).to.be.undefined() + expect(libp2p.metrics).to.be.undefined() }) it('should start/stop metrics on startup/shutdown when enabled', async () => { @@ -47,7 +47,7 @@ describe('libp2p.metrics', () => { }) libp2p = await createNode({ started: false, config }) - const metrics = libp2p.components.getMetrics() as DefaultMetrics + const metrics = libp2p.metrics as DefaultMetrics if (metrics == null) { throw new Error('Metrics not configured') @@ -106,7 +106,7 @@ describe('libp2p.metrics', () => { expect(result).to.have.length(bytes.length) - const metrics = libp2p.components.getMetrics() + const metrics = libp2p.metrics if (metrics == null) { throw new Error('Metrics not configured') @@ -166,7 +166,7 @@ describe('libp2p.metrics', () => { drain ) - const metrics = libp2p.components.getMetrics() + const metrics = libp2p.metrics if (metrics == null) { throw new Error('Metrics not configured') diff --git a/test/nat-manager/nat-manager.node.ts b/test/nat-manager/nat-manager.node.ts index 01049a2a0d..87f11193d9 100644 --- a/test/nat-manager/nat-manager.node.ts +++ b/test/nat-manager/nat-manager.node.ts @@ -3,17 +3,17 @@ import { expect } from 'aegir/chai' import { DefaultAddressManager } from '../../src/address-manager/index.js' import { DefaultTransportManager, FaultTolerance } from '../../src/transport-manager.js' -import { TCP } from '@libp2p/tcp' +import { tcp } from '@libp2p/tcp' import { mockUpgrader } from '@libp2p/interface-mocks' import { NatManager } from '../../src/nat-manager.js' import delay from 'delay' import Peers from '../fixtures/peers.js' import { codes } from '../../src/errors.js' import { createFromJSON } from '@libp2p/peer-id-factory' -import { Components } from '@libp2p/components' import type { NatAPI } from '@achingbrain/nat-port-mapper' import { StubbedInstance, stubInterface } from 'ts-sinon' import { start, stop } from '@libp2p/interfaces/startable' +import { DefaultComponents } from '../../src/components.js' const DEFAULT_ADDRESSES = [ '/ip4/127.0.0.1/tcp/0', @@ -24,15 +24,15 @@ describe('Nat Manager (TCP)', () => { const teardown: Array<() => Promise> = [] let client: StubbedInstance - async function createNatManager (addrs = DEFAULT_ADDRESSES, natManagerOptions = {}) { - const components = new Components({ + async function createNatManager (addrs = DEFAULT_ADDRESSES, natManagerOptions = {}): Promise<{ natManager: NatManager, components: DefaultComponents }> { + const components: any = { peerId: await createFromJSON(Peers[0]), upgrader: mockUpgrader() - }) - components.setAddressManager(new DefaultAddressManager(components, { listen: addrs })) - components.setTransportManager(new DefaultTransportManager(components, { + } + components.addressManager = new DefaultAddressManager(components, { listen: addrs }) + components.transportManager = new DefaultTransportManager(components, { faultTolerance: FaultTolerance.NO_FATAL - })) + }) const natManager = new NatManager(components, { enabled: true, @@ -46,12 +46,12 @@ describe('Nat Manager (TCP)', () => { return client } - components.getTransportManager().add(new TCP()) - await components.getTransportManager().listen(components.getAddressManager().getListenAddrs()) + components.transportManager.add(tcp()()) + await components.transportManager.listen(components.addressManager.getListenAddrs()) teardown.push(async () => { await stop(natManager) - await components.getTransportManager().removeAll() + await components.transportManager.removeAll() }) return { @@ -70,21 +70,21 @@ describe('Nat Manager (TCP)', () => { let addressChangedEventFired = false - components.getAddressManager().addEventListener('change:addresses', () => { + components.addressManager.addEventListener('change:addresses', () => { addressChangedEventFired = true }) client.externalIp.resolves('82.3.1.5') - let observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + let observed = components.addressManager.getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() await start(natManager) - observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + observed = components.addressManager.getObservedAddrs().map(ma => ma.toString()) expect(observed).to.not.be.empty() - const internalPorts = components.getTransportManager().getAddrs() + const internalPorts = components.transportManager.getAddrs() .filter(ma => ma.isThinWaistAddress()) .map(ma => ma.toOptions()) .filter(({ host, transport }) => host !== '127.0.0.1' && transport === 'tcp') @@ -110,12 +110,12 @@ describe('Nat Manager (TCP)', () => { client.externalIp.resolves('192.168.1.1') - let observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + let observed = components.addressManager.getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() await expect(natManager._start()).to.eventually.be.rejectedWith(/double NAT/) - observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + observed = components.addressManager.getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() expect(client.map.called).to.be.false() @@ -144,12 +144,12 @@ describe('Nat Manager (TCP)', () => { '/ip6/::/tcp/0' ]) - let observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + let observed = components.addressManager.getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() await start(natManager) - observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + observed = components.addressManager.getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() }) @@ -161,12 +161,12 @@ describe('Nat Manager (TCP)', () => { '/ip6/::1/tcp/0' ]) - let observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + let observed = components.addressManager.getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() await start(natManager) - observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + observed = components.addressManager.getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() }) @@ -178,12 +178,12 @@ describe('Nat Manager (TCP)', () => { '/ip4/0.0.0.0/utp' ]) - let observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + let observed = components.addressManager.getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() await start(natManager) - observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + observed = components.addressManager.getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() }) @@ -195,12 +195,12 @@ describe('Nat Manager (TCP)', () => { '/ip4/127.0.0.1/tcp/0' ]) - let observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + let observed = components.addressManager.getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() await start(natManager) - observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + observed = components.addressManager.getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() }) @@ -212,12 +212,12 @@ describe('Nat Manager (TCP)', () => { '/ip4/0.0.0.0/tcp/0/sctp/0' ]) - let observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + let observed = components.addressManager.getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() await start(natManager) - observed = components.getAddressManager().getObservedAddrs().map(ma => ma.toString()) + observed = components.addressManager.getObservedAddrs().map(ma => ma.toString()) expect(observed).to.be.empty() }) @@ -225,8 +225,7 @@ describe('Nat Manager (TCP)', () => { const peerId = await createFromJSON(Peers[0]) expect(() => { - // @ts-expect-error invalid parameters - new NatManager(new Components({ peerId }), { ttl: 5 }) // eslint-disable-line no-new + new NatManager(new DefaultComponents({ peerId }), { ttl: 5, enabled: true, keepAlive: true }) // eslint-disable-line no-new }).to.throw().with.property('code', codes.ERR_INVALID_PARAMETERS) }) }) diff --git a/test/peer-discovery/index.node.ts b/test/peer-discovery/index.node.ts index 72729cdd42..60a6ce6c83 100644 --- a/test/peer-discovery/index.node.ts +++ b/test/peer-discovery/index.node.ts @@ -3,10 +3,10 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' import defer from 'p-defer' -import { Bootstrap } from '@libp2p/bootstrap' +import { bootstrap } from '@libp2p/bootstrap' import { randomBytes } from '@libp2p/crypto' -import { KadDHT } from '@libp2p/kad-dht' -import { MulticastDNS } from '@libp2p/mdns' +import { kadDHT } from '@libp2p/kad-dht' +import { mdns } from '@libp2p/mdns' import { multiaddr } from '@multiformats/multiaddr' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { createBaseOptions } from '../utils/base-options.js' @@ -40,7 +40,7 @@ describe('peer discovery scenarios', () => { libp2p = await createLibp2pNode(createBaseOptions({ peerId, peerDiscovery: [ - new MulticastDNS() + mdns() ] })) @@ -77,7 +77,7 @@ describe('peer discovery scenarios', () => { autoDial: false }, peerDiscovery: [ - new Bootstrap({ + bootstrap({ list: bootstrappers }) ] @@ -117,7 +117,7 @@ describe('peer discovery scenarios', () => { ] }, peerDiscovery: [ - new MulticastDNS({ + mdns({ interval: 200, // discover quickly serviceTag }) @@ -172,7 +172,7 @@ describe('peer discovery scenarios', () => { connectionManager: { autoDial: false }, - dht: new KadDHT() + dht: kadDHT() }) const localConfig = getConfig(peerId) diff --git a/test/peer-discovery/index.spec.ts b/test/peer-discovery/index.spec.ts index 117e426768..5358523061 100644 --- a/test/peer-discovery/index.spec.ts +++ b/test/peer-discovery/index.spec.ts @@ -7,6 +7,8 @@ import { createPeerId } from '../utils/creators/peer.js' import type { PeerId } from '@libp2p/interface-peer-id' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' import type { Startable } from '@libp2p/interfaces/startable' +import { stubInterface } from 'ts-sinon' +import type { PeerDiscovery } from '@libp2p/interface-peer-discovery' describe('peer discovery', () => { describe('basic functions', () => { @@ -25,46 +27,23 @@ describe('peer discovery', () => { sinon.reset() }) - it('should stop discovery on libp2p start/stop', async () => { - let started = 0 - let stopped = 0 - - class MockDiscovery implements Startable { - static tag = 'mock' - - started = false - - isStarted () { - return this.started - } - - start () { - this.started = true - started++ - } - - stop () { - this.started = false - stopped++ - } - - addEventListener () {} - removeEventListener () {} - } + it('should start/stop startable discovery on libp2p start/stop', async () => { + const discovery = stubInterface() libp2p = await createLibp2pNode(createBaseOptions({ peerId, peerDiscovery: [ - new MockDiscovery() + () => discovery ] })) await libp2p.start() - expect(started).to.equal(1) - expect(stopped).to.equal(0) + expect(discovery.start.calledOnce).to.be.true() + expect(discovery.stop.called).to.be.false() + await libp2p.stop() - expect(started).to.equal(1) - expect(stopped).to.equal(1) + expect(discovery.start.calledOnce).to.be.true() + expect(discovery.stop.calledOnce).to.be.true() }) }) }) diff --git a/test/peer-routing/peer-routing.node.ts b/test/peer-routing/peer-routing.node.ts index 1cffc5d6cb..a3bc91c07b 100644 --- a/test/peer-routing/peer-routing.node.ts +++ b/test/peer-routing/peer-routing.node.ts @@ -16,7 +16,7 @@ import type { PeerId } from '@libp2p/interface-peer-id' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { EventTypes, MessageType } from '@libp2p/interface-dht' import type { PeerInfo } from '@libp2p/interface-peer-info' -import { KadDHT } from '@libp2p/kad-dht' +import { kadDHT } from '@libp2p/kad-dht' import type { PeerRouting } from '@libp2p/interface-peer-routing' import { StubbedInstance, stubInterface } from 'ts-sinon' @@ -242,7 +242,9 @@ describe('peer-routing', () => { node = await createNode({ config: createBaseOptions({ - peerRouters: [delegate] + peerRouters: [ + () => delegate + ] }) }) }) @@ -320,8 +322,10 @@ describe('peer-routing', () => { node = await createNode({ config: createRoutingOptions({ - peerRouters: [delegate], - dht: new KadDHT() + peerRouters: [ + () => delegate + ], + dht: kadDHT() }) }) }) diff --git a/test/peer-routing/utils.ts b/test/peer-routing/utils.ts index 25ae8b23be..67fb2f1a86 100644 --- a/test/peer-routing/utils.ts +++ b/test/peer-routing/utils.ts @@ -1,10 +1,10 @@ -import { KadDHT } from '@libp2p/kad-dht' +import { kadDHT } from '@libp2p/kad-dht' import type { Libp2pOptions } from '../../src/index.js' import { createBaseOptions } from '../utils/base-options.js' export function createRoutingOptions (...overrides: Libp2pOptions[]): Libp2pOptions { return createBaseOptions({ - dht: new KadDHT({ + dht: kadDHT({ kBucketSize: 20 }) }, ...overrides) diff --git a/test/ping/index.spec.ts b/test/ping/index.spec.ts index 9e17d0b678..4fedd39806 100644 --- a/test/ping/index.spec.ts +++ b/test/ping/index.spec.ts @@ -6,7 +6,6 @@ import { PingService, PingServiceInit } from '../../src/ping/index.js' import Peers from '../fixtures/peers.js' import { mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-mocks' import { createFromJSON } from '@libp2p/peer-id-factory' -import { Components } from '@libp2p/components' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' import { start, stop } from '@libp2p/interfaces/startable' import { CustomEvent } from '@libp2p/interfaces/events' @@ -15,6 +14,7 @@ import delay from 'delay' import { pipe } from 'it-pipe' import { PersistentPeerStore } from '@libp2p/peer-store' import { MemoryDatastore } from 'datastore-core' +import { DefaultComponents } from '../../src/components.js' const defaultInit: PingServiceInit = { protocolPrefix: 'ipfs', @@ -23,29 +23,29 @@ const defaultInit: PingServiceInit = { timeout: 1000 } -async function createComponents (index: number) { +async function createComponents (index: number): Promise { const peerId = await createFromJSON(Peers[index]) - const components = new Components({ + const components = new DefaultComponents({ peerId, registrar: mockRegistrar(), upgrader: mockUpgrader(), - peerStore: new PersistentPeerStore(), - datastore: new MemoryDatastore(), - connectionManager: new DefaultConnectionManager({ - minConnections: 50, - maxConnections: 1000, - autoDialInterval: 1000, - inboundUpgradeTimeout: 1000 - }) + datastore: new MemoryDatastore() + }) + components.peerStore = new PersistentPeerStore(components) + components.connectionManager = new DefaultConnectionManager(components, { + minConnections: 50, + maxConnections: 1000, + autoDialInterval: 1000, + inboundUpgradeTimeout: 1000 }) return components } describe('ping', () => { - let localComponents: Components - let remoteComponents: Components + let localComponents: DefaultComponents + let remoteComponents: DefaultComponents beforeEach(async () => { localComponents = await createComponents(0) @@ -75,11 +75,11 @@ describe('ping', () => { // simulate connection between nodes const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) - localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { detail: localToRemote })) - remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { detail: remoteToLocal })) + localComponents.upgrader.dispatchEvent(new CustomEvent('connection', { detail: localToRemote })) + remoteComponents.upgrader.dispatchEvent(new CustomEvent('connection', { detail: remoteToLocal })) // Run ping - await expect(localPing.ping(remoteComponents.getPeerId())).to.eventually.be.gte(0) + await expect(localPing.ping(remoteComponents.peerId)).to.eventually.be.gte(0) }) it('should time out pinging another peer when waiting for a pong', async () => { @@ -91,12 +91,12 @@ describe('ping', () => { // simulate connection between nodes const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) - localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { detail: localToRemote })) - remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { detail: remoteToLocal })) + localComponents.upgrader.dispatchEvent(new CustomEvent('connection', { detail: localToRemote })) + remoteComponents.upgrader.dispatchEvent(new CustomEvent('connection', { detail: remoteToLocal })) // replace existing handler with a really slow one - await remoteComponents.getRegistrar().unhandle(remotePing.protocol) - await remoteComponents.getRegistrar().handle(remotePing.protocol, ({ stream }) => { + await remoteComponents.registrar.unhandle(remotePing.protocol) + await remoteComponents.registrar.handle(remotePing.protocol, ({ stream }) => { void pipe( stream, async function * (source) { @@ -117,7 +117,7 @@ describe('ping', () => { const timeoutController = new TimeoutController(10) // Run ping, should time out - await expect(localPing.ping(remoteComponents.getPeerId(), { + await expect(localPing.ping(remoteComponents.peerId, { signal: timeoutController.signal })) .to.eventually.be.rejected.with.property('code', 'ABORT_ERR') diff --git a/test/ping/ping.node.ts b/test/ping/ping.node.ts index 61cd01ff04..a92479ea16 100644 --- a/test/ping/ping.node.ts +++ b/test/ping/ping.node.ts @@ -20,8 +20,8 @@ describe('ping', () => { ]) await populateAddressBooks(nodes) - await nodes[0].components.getPeerStore().addressBook.set(nodes[1].peerId, nodes[1].getMultiaddrs()) - await nodes[1].components.getPeerStore().addressBook.set(nodes[0].peerId, nodes[0].getMultiaddrs()) + await nodes[0].components.peerStore.addressBook.set(nodes[1].peerId, nodes[1].getMultiaddrs()) + await nodes[1].components.peerStore.addressBook.set(nodes[0].peerId, nodes[0].getMultiaddrs()) }) afterEach(async () => await Promise.all(nodes.map(async n => await n.stop()))) diff --git a/test/pnet/index.spec.ts b/test/pnet/index.spec.ts index 52a8e72b40..86c614c64c 100644 --- a/test/pnet/index.spec.ts +++ b/test/pnet/index.spec.ts @@ -3,7 +3,7 @@ import { expect } from 'aegir/chai' import { pipe } from 'it-pipe' import all from 'it-all' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { PreSharedKeyConnectionProtector, generateKey } from '../../src/pnet/index.js' +import { preSharedKey, generateKey } from '../../src/pnet/index.js' import { INVALID_PSK } from '../../src/pnet/errors.js' import { mockMultiaddrConnPair } from '@libp2p/interface-mocks' import { multiaddr } from '@multiformats/multiaddr' @@ -18,11 +18,11 @@ generateKey(wrongSwarmKeyBuffer) describe('private network', () => { it('should accept a valid psk buffer', () => { - const protector = new PreSharedKeyConnectionProtector({ + const protector = preSharedKey({ psk: swarmKeyBuffer - }) + })() - expect(protector.tag).to.equal('/key/swarm/psk/1.0.0/') + expect(protector).to.have.property('tag', '/key/swarm/psk/1.0.0/') }) it('should protect a simple connection', async () => { @@ -33,9 +33,9 @@ describe('private network', () => { ], remotePeer: await createEd25519PeerId() }) - const protector = new PreSharedKeyConnectionProtector({ + const protector = preSharedKey({ psk: swarmKeyBuffer - }) + })() const [aToB, bToA] = await Promise.all([ protector.protect(inbound), @@ -68,13 +68,13 @@ describe('private network', () => { ], remotePeer: await createEd25519PeerId() }) - const protector = new PreSharedKeyConnectionProtector({ + const protector = preSharedKey({ psk: swarmKeyBuffer - }) - const protectorB = new PreSharedKeyConnectionProtector({ + })() + const protectorB = preSharedKey({ enabled: true, psk: wrongSwarmKeyBuffer - }) + })() const [aToB, bToA] = await Promise.all([ protector.protect(inbound), @@ -97,17 +97,17 @@ describe('private network', () => { describe('invalid psks', () => { it('should not accept a bad psk', () => { expect(() => { - return new PreSharedKeyConnectionProtector({ + return preSharedKey({ psk: uint8ArrayFromString('not-a-key') - }) + })() }).to.throw(INVALID_PSK) }) it('should not accept a psk of incorrect length', () => { expect(() => { - return new PreSharedKeyConnectionProtector({ + return preSharedKey({ psk: uint8ArrayFromString('/key/swarm/psk/1.0.0/\n/base16/\ndffb7e') - }) + })() }).to.throw(INVALID_PSK) }) }) diff --git a/test/registrar/registrar.spec.ts b/test/registrar/registrar.spec.ts index 080ecef3e9..a26a8c64e9 100644 --- a/test/registrar/registrar.spec.ts +++ b/test/registrar/registrar.spec.ts @@ -11,21 +11,21 @@ import { createPeerId, createNode } from '../utils/creators/peer.js' import { createBaseOptions } from '../utils/base-options.browser.js' import type { Registrar } from '@libp2p/interface-registrar' import type { PeerId } from '@libp2p/interface-peer-id' -import { Components } from '@libp2p/components' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { CustomEvent } from '@libp2p/interfaces/events' import type { Connection } from '@libp2p/interface-connection' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' -import { Plaintext } from '../../src/insecure/index.js' -import { WebSockets } from '@libp2p/websockets' -import { Mplex } from '@libp2p/mplex' +import { plaintext } from '../../src/insecure/index.js' +import { webSockets } from '@libp2p/websockets' +import { mplex } from '@libp2p/mplex' import type { PeerProtocolsChangeData } from '@libp2p/interface-peer-store' +import { DefaultComponents } from '../../src/components.js' const protocol = '/test/1.0.0' describe('registrar', () => { - let components: Components + let components: DefaultComponents let registrar: Registrar let peerId: PeerId @@ -35,17 +35,17 @@ describe('registrar', () => { describe('errors', () => { beforeEach(() => { - components = new Components({ + components = new DefaultComponents({ peerId, datastore: new MemoryDatastore(), - upgrader: mockUpgrader(), - peerStore: new PersistentPeerStore(), - connectionManager: new DefaultConnectionManager({ - minConnections: 50, - maxConnections: 1000, - autoDialInterval: 1000, - inboundUpgradeTimeout: 1000 - }) + upgrader: mockUpgrader() + }) + components.peerStore = new PersistentPeerStore(components) + components.connectionManager = new DefaultConnectionManager(components, { + minConnections: 50, + maxConnections: 1000, + autoDialInterval: 1000, + inboundUpgradeTimeout: 1000 }) registrar = new DefaultRegistrar(components) }) @@ -83,12 +83,12 @@ describe('registrar', () => { onDisconnect: () => { } }) - expect(libp2p.components.getRegistrar().getTopologies(protocol)).to.have.lengthOf(0) + expect(libp2p.components.registrar.getTopologies(protocol)).to.have.lengthOf(0) - const identifier = await libp2p.components.getRegistrar().register(protocol, topology) + const identifier = await libp2p.components.registrar.register(protocol, topology) expect(identifier).to.exist() - expect(libp2p.components.getRegistrar().getTopologies(protocol)).to.have.lengthOf(1) + expect(libp2p.components.registrar.getTopologies(protocol)).to.have.lengthOf(1) }) it('should be able to unregister a protocol', async () => { @@ -97,19 +97,19 @@ describe('registrar', () => { onDisconnect: () => { } }) - expect(libp2p.components.getRegistrar().getTopologies(protocol)).to.have.lengthOf(0) + expect(libp2p.components.registrar.getTopologies(protocol)).to.have.lengthOf(0) - const identifier = await libp2p.components.getRegistrar().register(protocol, topology) + const identifier = await libp2p.components.registrar.register(protocol, topology) - expect(libp2p.components.getRegistrar().getTopologies(protocol)).to.have.lengthOf(1) + expect(libp2p.components.registrar.getTopologies(protocol)).to.have.lengthOf(1) - libp2p.components.getRegistrar().unregister(identifier) + libp2p.components.registrar.unregister(identifier) - expect(libp2p.components.getRegistrar().getTopologies(protocol)).to.have.lengthOf(0) + expect(libp2p.components.registrar.getTopologies(protocol)).to.have.lengthOf(0) }) it('should not error if unregistering unregistered topology handler', () => { - libp2p.components.getRegistrar().unregister('bad-identifier') + libp2p.components.registrar.unregister('bad-identifier') }) it('should call onConnect handler for connected peers after register', async () => { @@ -137,18 +137,18 @@ describe('registrar', () => { await libp2p.start() // Register protocol - await libp2p.components.getRegistrar().register(protocol, topology) + await libp2p.components.registrar.register(protocol, topology) // Add connected peer with protocol to peerStore and registrar await libp2p.peerStore.protoBook.add(remotePeerId, [protocol]) // remote peer connects - await libp2p.components.getUpgrader().dispatchEvent(new CustomEvent('connection', { + await libp2p.components.upgrader.dispatchEvent(new CustomEvent('connection', { detail: conn })) // identify completes - await libp2p.components.getPeerStore().dispatchEvent(new CustomEvent('change:protocols', { + await libp2p.components.peerStore.dispatchEvent(new CustomEvent('change:protocols', { detail: { peerId: conn.remotePeer, protocols: [protocol], @@ -158,7 +158,7 @@ describe('registrar', () => { // remote peer disconnects await conn.close() - await libp2p.components.getUpgrader().dispatchEvent(new CustomEvent('connectionEnd', { + await libp2p.components.upgrader.dispatchEvent(new CustomEvent('connectionEnd', { detail: conn })) @@ -189,7 +189,7 @@ describe('registrar', () => { await libp2p.start() // Register protocol - await libp2p.components.getRegistrar().register(protocol, topology) + await libp2p.components.registrar.register(protocol, topology) // Add connected peer to peerStore and registrar await libp2p.peerStore.protoBook.set(remotePeerId, []) @@ -198,12 +198,12 @@ describe('registrar', () => { await libp2p.peerStore.protoBook.add(remotePeerId, [protocol]) // remote peer connects - await libp2p.components.getUpgrader().dispatchEvent(new CustomEvent('connection', { + await libp2p.components.upgrader.dispatchEvent(new CustomEvent('connection', { detail: conn })) // identify completes - await libp2p.components.getPeerStore().dispatchEvent(new CustomEvent('change:protocols', { + await libp2p.components.peerStore.dispatchEvent(new CustomEvent('change:protocols', { detail: { peerId: conn.remotePeer, protocols: [protocol], @@ -223,17 +223,17 @@ describe('registrar', () => { libp2p = await createLibp2pNode({ peerId: await createEd25519PeerId(), transports: [ - new WebSockets() + webSockets() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) - const registrar = libp2p.components.getRegistrar() + const registrar = libp2p.components.registrar expect(registrar.getProtocols()).to.not.have.any.keys(['/echo/1.0.0', '/echo/1.0.1']) diff --git a/test/relay/auto-relay.node.ts b/test/relay/auto-relay.node.ts index a5d2b1075b..90c99610ea 100644 --- a/test/relay/auto-relay.node.ts +++ b/test/relay/auto-relay.node.ts @@ -265,7 +265,7 @@ describe('auto-relay', () => { await relayLibp2p1.hangUp(relayLibp2p3.peerId) // Stub dial - sinon.stub(relayLibp2p1.components.getConnectionManager(), 'openConnection').callsFake(async () => { + sinon.stub(relayLibp2p1.components.connectionManager, 'openConnection').callsFake(async () => { deferred.resolve() return await Promise.reject(new Error('failed to dial')) }) @@ -348,14 +348,14 @@ describe('auto-relay', () => { createNode({ config: createNodeOptions({ contentRouters: [ - localDelegate + () => localDelegate ] }) }), createNode({ config: createNodeOptions({ contentRouters: [ - remoteDelegate + () => remoteDelegate ] }) }), @@ -373,7 +373,7 @@ describe('auto-relay', () => { } }, contentRouters: [ - relayDelegate + () => relayDelegate ] }) }) diff --git a/test/relay/relay.node.ts b/test/relay/relay.node.ts index 1f0a899b28..823050e157 100644 --- a/test/relay/relay.node.ts +++ b/test/relay/relay.node.ts @@ -66,7 +66,7 @@ describe('Dialing (via relay, TCP)', () => { }) it('should be able to connect to a peer over a relay with active connections', async () => { - const relayAddr = relayLibp2p.components.getTransportManager().getAddrs()[0] + const relayAddr = relayLibp2p.components.transportManager.getAddrs()[0] const relayIdString = relayLibp2p.peerId.toString() const dialAddr = relayAddr @@ -94,7 +94,7 @@ describe('Dialing (via relay, TCP)', () => { }) it('should fail to connect to a peer over a relay with inactive connections', async () => { - const relayAddr = relayLibp2p.components.getTransportManager().getAddrs()[0] + const relayAddr = relayLibp2p.components.transportManager.getAddrs()[0] const relayIdString = relayLibp2p.peerId.toString() const dialAddr = relayAddr @@ -107,7 +107,7 @@ describe('Dialing (via relay, TCP)', () => { }) it('should not stay connected to a relay when not already connected and HOP fails', async () => { - const relayAddr = relayLibp2p.components.getTransportManager().getAddrs()[0] + const relayAddr = relayLibp2p.components.transportManager.getAddrs()[0] const relayIdString = relayLibp2p.peerId.toString() const dialAddr = relayAddr @@ -119,13 +119,13 @@ describe('Dialing (via relay, TCP)', () => { .and.to.have.nested.property('.errors[0].code', Errors.ERR_HOP_REQUEST_FAILED) // We should not be connected to the relay, because we weren't before the dial - const srcToRelayConns = srcLibp2p.components.getConnectionManager().getConnections(relayLibp2p.peerId) + const srcToRelayConns = srcLibp2p.components.connectionManager.getConnections(relayLibp2p.peerId) expect(srcToRelayConns).to.be.empty() }) it('dialer should stay connected to an already connected relay on hop failure', async () => { const relayIdString = relayLibp2p.peerId.toString() - const relayAddr = relayLibp2p.components.getTransportManager().getAddrs()[0].encapsulate(`/p2p/${relayIdString}`) + const relayAddr = relayLibp2p.components.transportManager.getAddrs()[0].encapsulate(`/p2p/${relayIdString}`) const dialAddr = relayAddr .encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerId.toString()}`) @@ -136,24 +136,24 @@ describe('Dialing (via relay, TCP)', () => { .to.eventually.be.rejected() .and.to.have.nested.property('.errors[0].code', Errors.ERR_HOP_REQUEST_FAILED) - const srcToRelayConn = srcLibp2p.components.getConnectionManager().getConnections(relayLibp2p.peerId) + const srcToRelayConn = srcLibp2p.components.connectionManager.getConnections(relayLibp2p.peerId) expect(srcToRelayConn).to.have.lengthOf(1) expect(srcToRelayConn).to.have.nested.property('[0].stat.status', 'OPEN') }) it('destination peer should stay connected to an already connected relay on hop failure', async () => { const relayIdString = relayLibp2p.peerId.toString() - const relayAddr = relayLibp2p.components.getTransportManager().getAddrs()[0].encapsulate(`/p2p/${relayIdString}`) + const relayAddr = relayLibp2p.components.transportManager.getAddrs()[0].encapsulate(`/p2p/${relayIdString}`) const dialAddr = relayAddr .encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerId.toString()}`) // Connect the destination peer and the relay - const tcpAddrs = dstLibp2p.components.getTransportManager().getAddrs() - sinon.stub(dstLibp2p.components.getAddressManager(), 'getListenAddrs').returns([multiaddr(`${relayAddr.toString()}/p2p-circuit`)]) + const tcpAddrs = dstLibp2p.components.transportManager.getAddrs() + sinon.stub(dstLibp2p.components.addressManager, 'getListenAddrs').returns([multiaddr(`${relayAddr.toString()}/p2p-circuit`)]) - await dstLibp2p.components.getTransportManager().listen(dstLibp2p.components.getAddressManager().getListenAddrs()) - expect(dstLibp2p.components.getTransportManager().getAddrs()).to.have.deep.members([...tcpAddrs, dialAddr.decapsulate('p2p')]) + await dstLibp2p.components.transportManager.listen(dstLibp2p.components.addressManager.getListenAddrs()) + expect(dstLibp2p.components.transportManager.getAddrs()).to.have.deep.members([...tcpAddrs, dialAddr.decapsulate('p2p')]) // send an invalid relay message from the relay to the destination peer const connections = relayLibp2p.getConnections(dstLibp2p.peerId) @@ -167,7 +167,7 @@ describe('Dialing (via relay, TCP)', () => { streamHandler.close() // should still be connected - const dstToRelayConn = dstLibp2p.components.getConnectionManager().getConnections(relayLibp2p.peerId) + const dstToRelayConn = dstLibp2p.components.connectionManager.getConnections(relayLibp2p.peerId) expect(dstToRelayConn).to.have.lengthOf(1) expect(dstToRelayConn).to.have.nested.property('[0].stat.status', 'OPEN') }) @@ -188,7 +188,7 @@ describe('Dialing (via relay, TCP)', () => { }) }) - const relayAddr = relayLibp2p.components.getTransportManager().getAddrs()[0] + const relayAddr = relayLibp2p.components.transportManager.getAddrs()[0] const dialAddr = relayAddr.encapsulate(`/p2p/${relayLibp2p.peerId.toString()}`) const connection = await srcLibp2p.dial(dialAddr) diff --git a/test/transports/transport-manager.node.ts b/test/transports/transport-manager.node.ts index 5a14f4ff30..2e1feeb925 100644 --- a/test/transports/transport-manager.node.ts +++ b/test/transports/transport-manager.node.ts @@ -6,7 +6,7 @@ import { DefaultAddressManager } from '../../src/address-manager/index.js' import { DefaultTransportManager } from '../../src/transport-manager.js' import { PersistentPeerStore } from '@libp2p/peer-store' import { PeerRecord } from '@libp2p/peer-record' -import { TCP } from '@libp2p/tcp' +import { tcp } from '@libp2p/tcp' import { multiaddr } from '@multiformats/multiaddr' import { mockUpgrader } from '@libp2p/interface-mocks' import sinon from 'sinon' @@ -14,8 +14,8 @@ import Peers from '../fixtures/peers.js' import pWaitFor from 'p-wait-for' import type { PeerId } from '@libp2p/interface-peer-id' import { createFromJSON } from '@libp2p/peer-id-factory' -import { Components } from '@libp2p/components' import { PeerRecordUpdater } from '../../src/peer-record-updater.js' +import { DefaultComponents } from '../../src/components.js' const addrs = [ multiaddr('/ip4/127.0.0.1/tcp/0'), @@ -25,24 +25,24 @@ const addrs = [ describe('Transport Manager (TCP)', () => { let tm: DefaultTransportManager let localPeer: PeerId - let components: Components + let components: DefaultComponents before(async () => { localPeer = await createFromJSON(Peers[0]) }) beforeEach(() => { - components = new Components({ + components = new DefaultComponents({ peerId: localPeer, datastore: new MemoryDatastore(), upgrader: mockUpgrader() }) - components.setAddressManager(new DefaultAddressManager(components, { listen: addrs.map(addr => addr.toString()) })) - components.setPeerStore(new PersistentPeerStore()) + components.addressManager = new DefaultAddressManager(components, { listen: addrs.map(addr => addr.toString()) }) + components.peerStore = new PersistentPeerStore(components) tm = new DefaultTransportManager(components) - components.setTransportManager(tm) + components.transportManager = tm }) afterEach(async () => { @@ -52,14 +52,14 @@ describe('Transport Manager (TCP)', () => { it('should be able to add and remove a transport', async () => { expect(tm.getTransports()).to.have.lengthOf(0) - tm.add(new TCP()) + tm.add(tcp()()) expect(tm.getTransports()).to.have.lengthOf(1) - await tm.remove(TCP.prototype[Symbol.toStringTag]) + await tm.remove('@libp2p/tcp') expect(tm.getTransports()).to.have.lengthOf(0) }) it('should be able to listen', async () => { - const transport = new TCP() + const transport = tcp()() expect(tm.getTransports()).to.be.empty() @@ -80,16 +80,16 @@ describe('Transport Manager (TCP)', () => { const peerRecordUpdater = new PeerRecordUpdater(components) await peerRecordUpdater.start() - let signedPeerRecord = await components.getPeerStore().addressBook.getPeerRecord(localPeer) + let signedPeerRecord = await components.peerStore.addressBook.getPeerRecord(localPeer) expect(signedPeerRecord).to.not.exist() - tm.add(new TCP()) + tm.add(tcp()()) await tm.listen(addrs) // Should created Self Peer record on new listen address, but it is done async // with no event so we have to wait a bit await pWaitFor(async () => { - signedPeerRecord = await components.getPeerStore().addressBook.getPeerRecord(localPeer) + signedPeerRecord = await components.peerStore.addressBook.getPeerRecord(localPeer) return signedPeerRecord != null }, { interval: 100, timeout: 2000 }) @@ -105,7 +105,7 @@ describe('Transport Manager (TCP)', () => { }) it('should be able to dial', async () => { - tm.add(new TCP()) + tm.add(tcp()()) await tm.listen(addrs) const addr = tm.getAddrs().shift() diff --git a/test/transports/transport-manager.spec.ts b/test/transports/transport-manager.spec.ts index a019bbca1f..c315714a37 100644 --- a/test/transports/transport-manager.spec.ts +++ b/test/transports/transport-manager.spec.ts @@ -3,32 +3,32 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' import { multiaddr } from '@multiformats/multiaddr' -import { WebSockets } from '@libp2p/websockets' +import { webSockets } from '@libp2p/websockets' import * as filters from '@libp2p/websockets/filters' -import { Plaintext } from '../../src/insecure/index.js' +import { plaintext } from '../../src/insecure/index.js' import { DefaultAddressManager } from '../../src/address-manager/index.js' import { DefaultTransportManager, FaultTolerance } from '../../src/transport-manager.js' import { mockUpgrader } from '@libp2p/interface-mocks' import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' import { codes as ErrorCodes } from '../../src/errors.js' import Peers from '../fixtures/peers.js' -import { Components } from '@libp2p/components' import { createEd25519PeerId, createFromJSON } from '@libp2p/peer-id-factory' import type { PeerId } from '@libp2p/interface-peer-id' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' +import type { DefaultComponents } from '../../src/components.js' const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0') describe('Transport Manager (WebSockets)', () => { let tm: DefaultTransportManager - let components: Components + let components: DefaultComponents before(async () => { - components = new Components({ + components = { peerId: await createEd25519PeerId(), upgrader: mockUpgrader() - }) - components.setAddressManager(new DefaultAddressManager(components, { listen: [listenAddr.toString()] })) + } as any + components.addressManager = new DefaultAddressManager(components, { listen: [listenAddr.toString()] }) tm = new DefaultTransportManager(components) }) @@ -39,30 +39,30 @@ describe('Transport Manager (WebSockets)', () => { }) it('should be able to add and remove a transport', async () => { - const transport = new WebSockets({ + const transport = webSockets({ filter: filters.all }) expect(tm.getTransports()).to.have.lengthOf(0) - tm.add(transport) + tm.add(transport()) expect(tm.getTransports()).to.have.lengthOf(1) - await tm.remove(transport[Symbol.toStringTag]) + await tm.remove('@libp2p/websockets') expect(tm.getTransports()).to.have.lengthOf(0) }) it('should not be able to add a transport twice', async () => { - tm.add(new WebSockets()) + tm.add(webSockets()()) expect(() => { - tm.add(new WebSockets()) + tm.add(webSockets()()) }) .to.throw() .and.to.have.property('code', ErrorCodes.ERR_DUPLICATE_TRANSPORT) }) it('should be able to dial', async () => { - tm.add(new WebSockets({ filter: filters.all })) + tm.add(webSockets({ filter: filters.all })()) const addr = MULTIADDRS_WEBSOCKETS[0] const connection = await tm.dial(addr) expect(connection).to.exist() @@ -70,7 +70,7 @@ describe('Transport Manager (WebSockets)', () => { }) it('should fail to dial an unsupported address', async () => { - tm.add(new WebSockets({ filter: filters.all })) + tm.add(webSockets({ filter: filters.all })()) const addr = multiaddr('/ip4/127.0.0.1/tcp/0') await expect(tm.dial(addr)) .to.eventually.be.rejected() @@ -78,7 +78,7 @@ describe('Transport Manager (WebSockets)', () => { }) it('should fail to listen with no valid address', async () => { - tm.add(new WebSockets({ filter: filters.all })) + tm.add(webSockets({ filter: filters.all })()) await expect(tm.listen([listenAddr])) .to.eventually.be.rejected() @@ -108,8 +108,8 @@ describe('libp2p.transportManager (dial only)', () => { addresses: { listen: ['/ip4/127.0.0.1/tcp/0'] }, - transports: [new WebSockets()], - connectionEncryption: [new Plaintext()] + transports: [webSockets()], + connectionEncryption: [plaintext()] }) await expect(libp2p.start()).to.eventually.be.rejected @@ -126,10 +126,10 @@ describe('libp2p.transportManager (dial only)', () => { faultTolerance: FaultTolerance.NO_FATAL }, transports: [ - new WebSockets() + webSockets() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) @@ -146,10 +146,10 @@ describe('libp2p.transportManager (dial only)', () => { faultTolerance: FaultTolerance.NO_FATAL }, transports: [ - new WebSockets() + webSockets() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) diff --git a/test/upgrading/upgrader.spec.ts b/test/upgrading/upgrader.spec.ts index 1a0751267e..b86ce3b7d4 100644 --- a/test/upgrading/upgrader.spec.ts +++ b/test/upgrading/upgrader.spec.ts @@ -2,13 +2,13 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' -import { Mplex } from '@libp2p/mplex' +import { mplex } from '@libp2p/mplex' import { multiaddr } from '@multiformats/multiaddr' import { pipe } from 'it-pipe' import all from 'it-all' import pSettle from 'p-settle' -import { WebSockets } from '@libp2p/websockets' -import { PreSharedKeyConnectionProtector } from '../../src/pnet/index.js' +import { webSockets } from '@libp2p/websockets' +import { preSharedKey } from '../../src/pnet/index.js' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import swarmKey from '../fixtures/swarm.key.js' import { DefaultUpgrader } from '../../src/upgrader.js' @@ -18,8 +18,7 @@ import Peers from '../fixtures/peers.js' import type { Upgrader } from '@libp2p/interface-transport' import type { PeerId } from '@libp2p/interface-peer-id' import { createFromJSON } from '@libp2p/peer-id-factory' -import { Components } from '@libp2p/components' -import { Plaintext } from '../../src/insecure/index.js' +import { plaintext } from '../../src/insecure/index.js' import type { ConnectionEncrypter, SecuredConnection } from '@libp2p/interface-connection-encrypter' import type { StreamMuxer, StreamMuxerFactory, StreamMuxerInit } from '@libp2p/interface-stream-muxer' import type { Stream } from '@libp2p/interface-connection' @@ -32,6 +31,7 @@ import drain from 'it-drain' import { Uint8ArrayList } from 'uint8arraylist' import { PersistentPeerStore } from '@libp2p/peer-store' import { MemoryDatastore } from 'datastore-core' +import { DefaultComponents } from '../../src/components.js' const addrs = [ multiaddr('/ip4/127.0.0.1/tcp/0'), @@ -44,8 +44,8 @@ describe('Upgrader', () => { let remoteUpgrader: Upgrader let localPeer: PeerId let remotePeer: PeerId - let localComponents: Components - let remoteComponents: Components + let localComponents: DefaultComponents + let remoteComponents: DefaultComponents beforeEach(async () => { ([ @@ -56,18 +56,18 @@ describe('Upgrader', () => { createFromJSON(Peers[1]) ])) - localComponents = new Components({ + localComponents = new DefaultComponents({ peerId: localPeer, connectionGater: mockConnectionGater(), registrar: mockRegistrar(), - peerStore: new PersistentPeerStore(), - datastore: new MemoryDatastore(), - connectionManager: mockConnectionManager() + datastore: new MemoryDatastore() }) - localMuxerFactory = new Mplex() + localComponents.peerStore = new PersistentPeerStore(localComponents) + localComponents.connectionManager = mockConnectionManager(localComponents) + localMuxerFactory = mplex()() localUpgrader = new DefaultUpgrader(localComponents, { connectionEncryption: [ - new Plaintext() + plaintext()() ], muxers: [ localMuxerFactory @@ -75,31 +75,31 @@ describe('Upgrader', () => { inboundUpgradeTimeout: 1000 }) - remoteComponents = new Components({ + remoteComponents = new DefaultComponents({ peerId: remotePeer, connectionGater: mockConnectionGater(), registrar: mockRegistrar(), - peerStore: new PersistentPeerStore(), - datastore: new MemoryDatastore(), - connectionManager: mockConnectionManager() + datastore: new MemoryDatastore() }) + remoteComponents.peerStore = new PersistentPeerStore(remoteComponents) + remoteComponents.connectionManager = mockConnectionManager(remoteComponents) remoteUpgrader = new DefaultUpgrader(remoteComponents, { connectionEncryption: [ - new Plaintext() + plaintext()() ], muxers: [ - new Mplex() + mplex()() ], inboundUpgradeTimeout: 1000 }) - await localComponents.getRegistrar().handle('/echo/1.0.0', ({ stream }) => { + await localComponents.registrar.handle('/echo/1.0.0', ({ stream }) => { void pipe(stream, stream) }, { maxInboundStreams: 10, maxOutboundStreams: 10 }) - await remoteComponents.getRegistrar().handle('/echo/1.0.0', ({ stream }) => { + await remoteComponents.registrar.handle('/echo/1.0.0', ({ stream }) => { void pipe(stream, stream) }, { maxInboundStreams: 10, @@ -145,14 +145,14 @@ describe('Upgrader', () => { // No available muxers localUpgrader = new DefaultUpgrader(localComponents, { connectionEncryption: [ - new Plaintext() + plaintext()() ], muxers: [], inboundUpgradeTimeout: 1000 }) remoteUpgrader = new DefaultUpgrader(remoteComponents, { connectionEncryption: [ - new Plaintext() + plaintext()() ], muxers: [], inboundUpgradeTimeout: 1000 @@ -178,13 +178,13 @@ describe('Upgrader', () => { it('should use a private connection protector when provided', async () => { const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) - const protector = new PreSharedKeyConnectionProtector({ + const protector = preSharedKey({ psk: uint8ArrayFromString(swarmKey) - }) + })() const protectorProtectSpy = sinon.spy(protector, 'protect') - localComponents.setConnectionProtector(protector) - remoteComponents.setConnectionProtector(protector) + localComponents.connectionProtector = protector + remoteComponents.connectionProtector = protector const connections = await Promise.all([ localUpgrader.upgradeOutbound(outbound), @@ -276,7 +276,7 @@ describe('Upgrader', () => { localUpgrader = new DefaultUpgrader(localComponents, { connectionEncryption: [ - new Plaintext() + plaintext()() ], muxers: [ new OtherMuxerFactory() @@ -285,10 +285,10 @@ describe('Upgrader', () => { }) remoteUpgrader = new DefaultUpgrader(remoteComponents, { connectionEncryption: [ - new Plaintext() + plaintext()() ], muxers: [ - new Mplex() + mplex()() ], inboundUpgradeTimeout: 1000 }) @@ -430,7 +430,7 @@ describe('Upgrader', () => { }) it('should close streams when protocol negotiation fails', async () => { - await remoteComponents.getRegistrar().unhandle('/echo/1.0.0') + await remoteComponents.registrar.unhandle('/echo/1.0.0') const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) @@ -481,21 +481,21 @@ describe('libp2p.upgrader', () => { libp2p = await createLibp2pNode({ peerId: peers[0], transports: [ - new WebSockets() + webSockets() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ], - connectionProtector: new PreSharedKeyConnectionProtector({ + connectionProtector: preSharedKey({ psk: uint8ArrayFromString(swarmKey) }) }) - expect(libp2p.components.getUpgrader()).to.exist() - expect(libp2p.components.getConnectionProtector()).to.exist() + expect(libp2p.components.upgrader).to.exist() + expect(libp2p.components.connectionProtector).to.exist() }) it('should return muxed streams', async () => { @@ -503,13 +503,13 @@ describe('libp2p.upgrader', () => { libp2p = await createLibp2pNode({ peerId: peers[0], transports: [ - new WebSockets() + webSockets() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) await libp2p.start() @@ -519,13 +519,13 @@ describe('libp2p.upgrader', () => { remoteLibp2p = await createLibp2pNode({ peerId: remotePeer, transports: [ - new WebSockets() + webSockets() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) await remoteLibp2p.start() @@ -533,10 +533,10 @@ describe('libp2p.upgrader', () => { const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) const [localConnection] = await Promise.all([ - libp2p.components.getUpgrader().upgradeOutbound(outbound), - remoteLibp2p.components.getUpgrader().upgradeInbound(inbound) + libp2p.components.upgrader.upgradeOutbound(outbound), + remoteLibp2p.components.upgrader.upgradeInbound(inbound) ]) - const remoteLibp2pUpgraderOnStreamSpy = sinon.spy(remoteLibp2p.components.getUpgrader() as DefaultUpgrader, '_onStream') + const remoteLibp2pUpgraderOnStreamSpy = sinon.spy(remoteLibp2p.components.upgrader as DefaultUpgrader, '_onStream') const stream = await localConnection.newStream(['/echo/1.0.0']) expect(stream).to.include.keys(['id', 'close', 'reset', 'stat']) @@ -550,13 +550,13 @@ describe('libp2p.upgrader', () => { libp2p = await createLibp2pNode({ peerId: peers[0], transports: [ - new WebSockets() + webSockets() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) await libp2p.start() @@ -564,13 +564,13 @@ describe('libp2p.upgrader', () => { remoteLibp2p = await createLibp2pNode({ peerId: remotePeer, transports: [ - new WebSockets() + webSockets() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) await remoteLibp2p.start() @@ -578,13 +578,13 @@ describe('libp2p.upgrader', () => { const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) // Spy on emit for easy verification - const connectionManagerDispatchEventSpy = sinon.spy(libp2p.components.getConnectionManager(), 'dispatchEvent') + const connectionManagerDispatchEventSpy = sinon.spy(libp2p.connectionManager, 'dispatchEvent') // Upgrade and check the connect event const connectionPromise = pEvent(libp2p.connectionManager, 'peer:connect') const connections = await Promise.all([ - libp2p.components.getUpgrader().upgradeOutbound(outbound), - remoteLibp2p.components.getUpgrader().upgradeInbound(inbound) + libp2p.components.upgrader.upgradeOutbound(outbound), + remoteLibp2p.components.upgrader.upgradeInbound(inbound) ]) await connectionPromise expect(connectionManagerDispatchEventSpy.callCount).to.equal(1) @@ -609,13 +609,13 @@ describe('libp2p.upgrader', () => { libp2p = await createLibp2pNode({ peerId: peers[0], transports: [ - new WebSockets() + webSockets() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) await libp2p.start() @@ -623,13 +623,13 @@ describe('libp2p.upgrader', () => { remoteLibp2p = await createLibp2pNode({ peerId: remotePeer, transports: [ - new WebSockets() + webSockets() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) await remoteLibp2p.start() @@ -637,8 +637,8 @@ describe('libp2p.upgrader', () => { const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) const [localToRemote] = await Promise.all([ - libp2p.components.getUpgrader().upgradeOutbound(outbound), - remoteLibp2p.components.getUpgrader().upgradeInbound(inbound) + libp2p.components.upgrader.upgradeOutbound(outbound), + remoteLibp2p.components.upgrader.upgradeInbound(inbound) ]) let streamCount = 0 @@ -671,13 +671,13 @@ describe('libp2p.upgrader', () => { libp2p = await createLibp2pNode({ peerId: peers[0], transports: [ - new WebSockets() + webSockets() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) await libp2p.start() @@ -685,13 +685,13 @@ describe('libp2p.upgrader', () => { remoteLibp2p = await createLibp2pNode({ peerId: remotePeer, transports: [ - new WebSockets() + webSockets() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ] }) await remoteLibp2p.start() @@ -699,8 +699,8 @@ describe('libp2p.upgrader', () => { const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) const [localToRemote] = await Promise.all([ - libp2p.components.getUpgrader().upgradeOutbound(outbound), - remoteLibp2p.components.getUpgrader().upgradeInbound(inbound) + libp2p.components.upgrader.upgradeOutbound(outbound), + remoteLibp2p.components.upgrader.upgradeInbound(inbound) ]) let streamCount = 0 diff --git a/test/utils/base-options.browser.ts b/test/utils/base-options.browser.ts index 3ac2f8d9d9..882d1ab1f7 100644 --- a/test/utils/base-options.browser.ts +++ b/test/utils/base-options.browser.ts @@ -1,23 +1,23 @@ -import { WebSockets } from '@libp2p/websockets' +import { webSockets } from '@libp2p/websockets' import * as filters from '@libp2p/websockets/filters' -import { Mplex } from '@libp2p/mplex' -import { Plaintext } from '../../src/insecure/index.js' +import { mplex } from '@libp2p/mplex' +import { plaintext } from '../../src/insecure/index.js' import type { Libp2pOptions } from '../../src' import mergeOptions from 'merge-options' export function createBaseOptions (overrides?: Libp2pOptions): Libp2pOptions { const options: Libp2pOptions = { transports: [ - new WebSockets({ + webSockets({ filter: filters.all }) ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ], relay: { enabled: false, diff --git a/test/utils/base-options.ts b/test/utils/base-options.ts index 610bdb2240..41c91405f1 100644 --- a/test/utils/base-options.ts +++ b/test/utils/base-options.ts @@ -1,19 +1,19 @@ -import { TCP } from '@libp2p/tcp' -import { Mplex } from '@libp2p/mplex' -import { Plaintext } from '../../src/insecure/index.js' +import { tcp } from '@libp2p/tcp' +import { mplex } from '@libp2p/mplex' +import { plaintext } from '../../src/insecure/index.js' import type { Libp2pOptions } from '../../src' import mergeOptions from 'merge-options' export function createBaseOptions (...overrides: Libp2pOptions[]): Libp2pOptions { const options: Libp2pOptions = { transports: [ - new TCP() + tcp() ], streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Plaintext() + plaintext() ], relay: { enabled: true, diff --git a/test/utils/creators/peer.ts b/test/utils/creators/peer.ts index 63875a1eb5..478e2bb315 100644 --- a/test/utils/creators/peer.ts +++ b/test/utils/creators/peer.ts @@ -64,7 +64,7 @@ export async function populateAddressBooks (peers: Libp2pNode[]) { for (let i = 0; i < peers.length; i++) { for (let j = 0; j < peers.length; j++) { if (i !== j) { - await peers[i].components.getPeerStore().addressBook.set(peers[j].peerId, peers[j].components.getAddressManager().getAddresses()) + await peers[i].components.peerStore.addressBook.set(peers[j].peerId, peers[j].components.addressManager.getAddresses()) } } } From e10eea24d40243c12584429a3b3012488f82bd00 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 14 Oct 2022 14:14:11 +0100 Subject: [PATCH 442/447] fix: regenerate protobuf defs (#1439) The new protons needs the protobuf files regenerating. --- package.json | 6 +- src/circuit/pb/index.ts | 64 +++++++++----------- src/fetch/pb/proto.ts | 59 ++++++++---------- src/identify/pb/message.ts | 58 +++++++----------- src/insecure/pb/proto.ts | 53 ++++++++-------- test/addresses/address-manager.spec.ts | 2 +- test/connection-manager/auto-dialler.spec.ts | 2 +- test/connection-manager/index.node.ts | 2 +- test/connection-manager/index.spec.ts | 2 +- test/content-routing/content-routing.node.ts | 2 +- test/dialing/dial-request.spec.ts | 2 +- test/identify/push.spec.ts | 2 +- test/nat-manager/nat-manager.node.ts | 2 +- test/peer-discovery/index.spec.ts | 2 +- test/peer-routing/peer-routing.node.ts | 2 +- test/relay/auto-relay.node.ts | 2 +- 16 files changed, 114 insertions(+), 148 deletions(-) diff --git a/package.json b/package.json index 9eb24ed4d8..53dceefa33 100644 --- a/package.json +++ b/package.json @@ -171,8 +171,8 @@ "@chainsafe/libp2p-noise": "^9.0.0", "@chainsafe/libp2p-yamux": "^3.0.0", "@libp2p/bootstrap": "^5.0.0", - "@libp2p/daemon-client": "^3.0.1", - "@libp2p/daemon-server": "^3.0.1", + "@libp2p/daemon-client": "^3.0.5", + "@libp2p/daemon-server": "^3.0.4", "@libp2p/floodsub": "^5.0.0", "@libp2p/interface-compliance-tests": "^3.0.2", "@libp2p/interface-connection-encrypter-compliance-tests": "^3.0.0", @@ -205,7 +205,7 @@ "protons": "^6.0.0", "rimraf": "^3.0.2", "sinon": "^14.0.0", - "ts-sinon": "^2.0.2" + "sinon-ts": "^1.0.0" }, "browser": { "nat-api": false diff --git a/src/circuit/pb/index.ts b/src/circuit/pb/index.ts index 44b0127cec..533fd39669 100644 --- a/src/circuit/pb/index.ts +++ b/src/circuit/pb/index.ts @@ -1,5 +1,7 @@ /* eslint-disable import/export */ +/* eslint-disable complexity */ /* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ import { enumeration, encodeMessage, decodeMessage, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -87,32 +89,31 @@ export namespace CircuitRelay { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.id != null) { - writer.uint32(10) - writer.bytes(obj.id) - } else { - throw new Error('Protocol error: required field "id" was not found in object') + if (opts.writeDefaults === true || (obj.id != null && obj.id.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.id) } if (obj.addrs != null) { for (const value of obj.addrs) { - writer.uint32(18) - writer.bytes(value) + w.uint32(18) + w.bytes(value) } - } else { - throw new Error('Protocol error: required field "addrs" was not found in object') } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + id: new Uint8Array(0), + addrs: [] + } const end = length == null ? reader.len : reader.pos + length @@ -124,7 +125,6 @@ export namespace CircuitRelay { obj.id = reader.bytes() break case 2: - obj.addrs = obj.addrs ?? [] obj.addrs.push(reader.bytes()) break default: @@ -133,16 +133,6 @@ export namespace CircuitRelay { } } - obj.addrs = obj.addrs ?? [] - - if (obj.id == null) { - throw new Error('Protocol error: value for required field "id" was not found in protobuf') - } - - if (obj.addrs == null) { - throw new Error('Protocol error: value for required field "addrs" was not found in protobuf') - } - return obj }) } @@ -163,33 +153,37 @@ export namespace CircuitRelay { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } if (obj.type != null) { - writer.uint32(8) - CircuitRelay.Type.codec().encode(obj.type, writer) + w.uint32(8) + CircuitRelay.Type.codec().encode(obj.type, w) } if (obj.srcPeer != null) { - writer.uint32(18) - CircuitRelay.Peer.codec().encode(obj.srcPeer, writer) + w.uint32(18) + CircuitRelay.Peer.codec().encode(obj.srcPeer, w, { + writeDefaults: false + }) } if (obj.dstPeer != null) { - writer.uint32(26) - CircuitRelay.Peer.codec().encode(obj.dstPeer, writer) + w.uint32(26) + CircuitRelay.Peer.codec().encode(obj.dstPeer, w, { + writeDefaults: false + }) } if (obj.code != null) { - writer.uint32(32) - CircuitRelay.Status.codec().encode(obj.code, writer) + w.uint32(32) + CircuitRelay.Status.codec().encode(obj.code, w) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = {} diff --git a/src/fetch/pb/proto.ts b/src/fetch/pb/proto.ts index d4997ea4fc..1f41f6e506 100644 --- a/src/fetch/pb/proto.ts +++ b/src/fetch/pb/proto.ts @@ -1,5 +1,7 @@ /* eslint-disable import/export */ +/* eslint-disable complexity */ /* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ import { encodeMessage, decodeMessage, message, enumeration } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -14,23 +16,23 @@ export namespace FetchRequest { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.identifier != null) { - writer.uint32(10) - writer.string(obj.identifier) - } else { - throw new Error('Protocol error: required field "identifier" was not found in object') + if (opts.writeDefaults === true || obj.identifier !== '') { + w.uint32(10) + w.string(obj.identifier) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + identifier: '' + } const end = length == null ? reader.len : reader.pos + length @@ -47,10 +49,6 @@ export namespace FetchRequest { } } - if (obj.identifier == null) { - throw new Error('Protocol error: value for required field "identifier" was not found in protobuf') - } - return obj }) } @@ -95,30 +93,29 @@ export namespace FetchResponse { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.status != null) { - writer.uint32(8) - FetchResponse.StatusCode.codec().encode(obj.status, writer) - } else { - throw new Error('Protocol error: required field "status" was not found in object') + if (opts.writeDefaults === true || (obj.status != null && __StatusCodeValues[obj.status] !== 0)) { + w.uint32(8) + FetchResponse.StatusCode.codec().encode(obj.status, w) } - if (obj.data != null) { - writer.uint32(18) - writer.bytes(obj.data) - } else { - throw new Error('Protocol error: required field "data" was not found in object') + if (opts.writeDefaults === true || (obj.data != null && obj.data.byteLength > 0)) { + w.uint32(18) + w.bytes(obj.data) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + status: StatusCode.OK, + data: new Uint8Array(0) + } const end = length == null ? reader.len : reader.pos + length @@ -138,14 +135,6 @@ export namespace FetchResponse { } } - if (obj.status == null) { - throw new Error('Protocol error: value for required field "status" was not found in protobuf') - } - - if (obj.data == null) { - throw new Error('Protocol error: value for required field "data" was not found in protobuf') - } - return obj }) } diff --git a/src/identify/pb/message.ts b/src/identify/pb/message.ts index 882b5b67a2..2498ac37bf 100644 --- a/src/identify/pb/message.ts +++ b/src/identify/pb/message.ts @@ -1,5 +1,7 @@ /* eslint-disable import/export */ +/* eslint-disable complexity */ /* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ import { encodeMessage, decodeMessage, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -20,59 +22,58 @@ export namespace Identify { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } if (obj.protocolVersion != null) { - writer.uint32(42) - writer.string(obj.protocolVersion) + w.uint32(42) + w.string(obj.protocolVersion) } if (obj.agentVersion != null) { - writer.uint32(50) - writer.string(obj.agentVersion) + w.uint32(50) + w.string(obj.agentVersion) } if (obj.publicKey != null) { - writer.uint32(10) - writer.bytes(obj.publicKey) + w.uint32(10) + w.bytes(obj.publicKey) } if (obj.listenAddrs != null) { for (const value of obj.listenAddrs) { - writer.uint32(18) - writer.bytes(value) + w.uint32(18) + w.bytes(value) } - } else { - throw new Error('Protocol error: required field "listenAddrs" was not found in object') } if (obj.observedAddr != null) { - writer.uint32(34) - writer.bytes(obj.observedAddr) + w.uint32(34) + w.bytes(obj.observedAddr) } if (obj.protocols != null) { for (const value of obj.protocols) { - writer.uint32(26) - writer.string(value) + w.uint32(26) + w.string(value) } - } else { - throw new Error('Protocol error: required field "protocols" was not found in object') } if (obj.signedPeerRecord != null) { - writer.uint32(66) - writer.bytes(obj.signedPeerRecord) + w.uint32(66) + w.bytes(obj.signedPeerRecord) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + listenAddrs: [], + protocols: [] + } const end = length == null ? reader.len : reader.pos + length @@ -90,14 +91,12 @@ export namespace Identify { obj.publicKey = reader.bytes() break case 2: - obj.listenAddrs = obj.listenAddrs ?? [] obj.listenAddrs.push(reader.bytes()) break case 4: obj.observedAddr = reader.bytes() break case 3: - obj.protocols = obj.protocols ?? [] obj.protocols.push(reader.string()) break case 8: @@ -109,17 +108,6 @@ export namespace Identify { } } - obj.listenAddrs = obj.listenAddrs ?? [] - obj.protocols = obj.protocols ?? [] - - if (obj.listenAddrs == null) { - throw new Error('Protocol error: value for required field "listenAddrs" was not found in protobuf') - } - - if (obj.protocols == null) { - throw new Error('Protocol error: value for required field "protocols" was not found in protobuf') - } - return obj }) } diff --git a/src/insecure/pb/proto.ts b/src/insecure/pb/proto.ts index 1ff2468f5c..e800904eb4 100644 --- a/src/insecure/pb/proto.ts +++ b/src/insecure/pb/proto.ts @@ -1,5 +1,7 @@ /* eslint-disable import/export */ +/* eslint-disable complexity */ /* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ import { encodeMessage, decodeMessage, message, enumeration } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -15,23 +17,25 @@ export namespace Exchange { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } if (obj.id != null) { - writer.uint32(10) - writer.bytes(obj.id) + w.uint32(10) + w.bytes(obj.id) } if (obj.pubkey != null) { - writer.uint32(18) - PublicKey.codec().encode(obj.pubkey, writer) + w.uint32(18) + PublicKey.codec().encode(obj.pubkey, w, { + writeDefaults: false + }) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = {} @@ -99,30 +103,29 @@ export namespace PublicKey { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.Type != null) { - writer.uint32(8) - KeyType.codec().encode(obj.Type, writer) - } else { - throw new Error('Protocol error: required field "Type" was not found in object') + if (opts.writeDefaults === true || (obj.Type != null && __KeyTypeValues[obj.Type] !== 0)) { + w.uint32(8) + KeyType.codec().encode(obj.Type, w) } - if (obj.Data != null) { - writer.uint32(18) - writer.bytes(obj.Data) - } else { - throw new Error('Protocol error: required field "Data" was not found in object') + if (opts.writeDefaults === true || (obj.Data != null && obj.Data.byteLength > 0)) { + w.uint32(18) + w.bytes(obj.Data) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + Type: KeyType.RSA, + Data: new Uint8Array(0) + } const end = length == null ? reader.len : reader.pos + length @@ -142,14 +145,6 @@ export namespace PublicKey { } } - if (obj.Type == null) { - throw new Error('Protocol error: value for required field "Type" was not found in protobuf') - } - - if (obj.Data == null) { - throw new Error('Protocol error: value for required field "Data" was not found in protobuf') - } - return obj }) } diff --git a/test/addresses/address-manager.spec.ts b/test/addresses/address-manager.spec.ts index f08b0444ba..57404029d9 100644 --- a/test/addresses/address-manager.spec.ts +++ b/test/addresses/address-manager.spec.ts @@ -6,7 +6,7 @@ import { AddressFilter, DefaultAddressManager } from '../../src/address-manager/ import { createNode } from '../utils/creators/peer.js' import { createFromJSON } from '@libp2p/peer-id-factory' import Peers from '../fixtures/peers.js' -import { stubInterface } from 'ts-sinon' +import { stubInterface } from 'sinon-ts' import type { TransportManager } from '@libp2p/interface-transport' import type { PeerId } from '@libp2p/interface-peer-id' import type { Libp2p } from '../../src/index.js' diff --git a/test/connection-manager/auto-dialler.spec.ts b/test/connection-manager/auto-dialler.spec.ts index 453b79ce7d..16d7296d34 100644 --- a/test/connection-manager/auto-dialler.spec.ts +++ b/test/connection-manager/auto-dialler.spec.ts @@ -5,7 +5,7 @@ import { AutoDialler } from '../../src/connection-manager/auto-dialler.js' import pWaitFor from 'p-wait-for' import delay from 'delay' import { createEd25519PeerId } from '@libp2p/peer-id-factory' -import { stubInterface } from 'ts-sinon' +import { stubInterface } from 'sinon-ts' import type { ConnectionManager } from '@libp2p/interface-connection-manager' import type { PeerStore, Peer } from '@libp2p/interface-peer-store' diff --git a/test/connection-manager/index.node.ts b/test/connection-manager/index.node.ts index 879d85211c..a00699ef7e 100644 --- a/test/connection-manager/index.node.ts +++ b/test/connection-manager/index.node.ts @@ -9,7 +9,7 @@ import type { PeerId } from '@libp2p/interface-peer-id' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' import { CustomEvent } from '@libp2p/interfaces/events' import * as STATUS from '@libp2p/interface-connection/status' -import { stubInterface } from 'ts-sinon' +import { stubInterface } from 'sinon-ts' import type { KeyBook, PeerStore } from '@libp2p/interface-peer-store' import sinon from 'sinon' import pWaitFor from 'p-wait-for' diff --git a/test/connection-manager/index.spec.ts b/test/connection-manager/index.spec.ts index bb3d71dfc2..4d37dd8f8f 100644 --- a/test/connection-manager/index.spec.ts +++ b/test/connection-manager/index.spec.ts @@ -12,7 +12,7 @@ import { CustomEvent } from '@libp2p/interfaces/events' import { KEEP_ALIVE } from '@libp2p/interface-peer-store/tags' import pWaitFor from 'p-wait-for' import { multiaddr } from '@multiformats/multiaddr' -import { stubInterface } from 'ts-sinon' +import { stubInterface } from 'sinon-ts' import type { Dialer } from '@libp2p/interface-connection-manager' import type { Connection } from '@libp2p/interface-connection' import type { Metrics } from '@libp2p/interface-metrics' diff --git a/test/content-routing/content-routing.node.ts b/test/content-routing/content-routing.node.ts index e4c6d5db74..61cda7c182 100644 --- a/test/content-routing/content-routing.node.ts +++ b/test/content-routing/content-routing.node.ts @@ -14,7 +14,7 @@ import type { Libp2p } from '../../src/index.js' import type { PeerInfo } from '@libp2p/interface-peer-info' import type { Libp2pNode } from '../../src/libp2p.js' import type { ContentRouting } from '@libp2p/interface-content-routing' -import { StubbedInstance, stubInterface } from 'ts-sinon' +import { StubbedInstance, stubInterface } from 'sinon-ts' import { peerIdFromString } from '@libp2p/peer-id' describe('content-routing', () => { diff --git a/test/dialing/dial-request.spec.ts b/test/dialing/dial-request.spec.ts index 2a0674a46b..c6500805b3 100644 --- a/test/dialing/dial-request.spec.ts +++ b/test/dialing/dial-request.spec.ts @@ -14,7 +14,7 @@ import type { Metrics } from '@libp2p/interface-metrics' import type { PeerStore } from '@libp2p/interface-peer-store' import type { TransportManager } from '@libp2p/interface-transport' import type { ConnectionGater } from '@libp2p/interface-connection' -import { stubInterface } from 'ts-sinon' +import { stubInterface } from 'sinon-ts' const error = new Error('dial failure') describe('Dial Request', () => { diff --git a/test/identify/push.spec.ts b/test/identify/push.spec.ts index fb3c6e722e..415573b1c7 100644 --- a/test/identify/push.spec.ts +++ b/test/identify/push.spec.ts @@ -23,7 +23,7 @@ import { CustomEvent } from '@libp2p/interfaces/events' import delay from 'delay' import { pEvent } from 'p-event' import { start, stop } from '@libp2p/interfaces/startable' -import { stubInterface } from 'ts-sinon' +import { stubInterface } from 'sinon-ts' import type { Dialer } from '@libp2p/interface-connection-manager' import type { Metrics } from '@libp2p/interface-metrics' import { DefaultComponents } from '../../src/components.js' diff --git a/test/nat-manager/nat-manager.node.ts b/test/nat-manager/nat-manager.node.ts index 87f11193d9..107173ca77 100644 --- a/test/nat-manager/nat-manager.node.ts +++ b/test/nat-manager/nat-manager.node.ts @@ -11,7 +11,7 @@ import Peers from '../fixtures/peers.js' import { codes } from '../../src/errors.js' import { createFromJSON } from '@libp2p/peer-id-factory' import type { NatAPI } from '@achingbrain/nat-port-mapper' -import { StubbedInstance, stubInterface } from 'ts-sinon' +import { StubbedInstance, stubInterface } from 'sinon-ts' import { start, stop } from '@libp2p/interfaces/startable' import { DefaultComponents } from '../../src/components.js' diff --git a/test/peer-discovery/index.spec.ts b/test/peer-discovery/index.spec.ts index 5358523061..b217b20ec5 100644 --- a/test/peer-discovery/index.spec.ts +++ b/test/peer-discovery/index.spec.ts @@ -7,7 +7,7 @@ import { createPeerId } from '../utils/creators/peer.js' import type { PeerId } from '@libp2p/interface-peer-id' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' import type { Startable } from '@libp2p/interfaces/startable' -import { stubInterface } from 'ts-sinon' +import { stubInterface } from 'sinon-ts' import type { PeerDiscovery } from '@libp2p/interface-peer-discovery' describe('peer discovery', () => { diff --git a/test/peer-routing/peer-routing.node.ts b/test/peer-routing/peer-routing.node.ts index a3bc91c07b..f54427eb3b 100644 --- a/test/peer-routing/peer-routing.node.ts +++ b/test/peer-routing/peer-routing.node.ts @@ -18,7 +18,7 @@ import { EventTypes, MessageType } from '@libp2p/interface-dht' import type { PeerInfo } from '@libp2p/interface-peer-info' import { kadDHT } from '@libp2p/kad-dht' import type { PeerRouting } from '@libp2p/interface-peer-routing' -import { StubbedInstance, stubInterface } from 'ts-sinon' +import { StubbedInstance, stubInterface } from 'sinon-ts' describe('peer-routing', () => { let peerId: PeerId diff --git a/test/relay/auto-relay.node.ts b/test/relay/auto-relay.node.ts index 90c99610ea..806e70d20f 100644 --- a/test/relay/auto-relay.node.ts +++ b/test/relay/auto-relay.node.ts @@ -11,7 +11,7 @@ import type { Libp2pNode } from '../../src/libp2p.js' import type { Options as PWaitForOptions } from 'p-wait-for' import { createRelayOptions, createNodeOptions } from './utils.js' import { protocols } from '@multiformats/multiaddr' -import { StubbedInstance, stubInterface } from 'ts-sinon' +import { StubbedInstance, stubInterface } from 'sinon-ts' import type { ContentRouting } from '@libp2p/interface-content-routing' async function usingAsRelay (node: Libp2pNode, relay: Libp2pNode, opts?: PWaitForOptions) { From a74d22a2cddf9ffdca26447fe21a62b5d13e773d Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Fri, 14 Oct 2022 06:16:19 -0700 Subject: [PATCH 443/447] fix: close stream after sending identify (#1424) Close the stream after we finish writing the identify message. This is behavior is specified https://github.com/libp2p/specs/blob/master/identify/README.md#identify > The peer being identified responds by returning an Identify message and closes the stream. This shows up when interacting with a go-libp2p node as go-libp2p waits for the stream to close before finishing reading the messages: https://github.com/libp2p/go-libp2p/blob/master/p2p/protocol/identify/id.go#L455. --- src/identify/index.ts | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/identify/index.ts b/src/identify/index.ts index 04da9c0600..4845584e50 100644 --- a/src/identify/index.ts +++ b/src/identify/index.ts @@ -2,7 +2,6 @@ import { logger } from '@libp2p/logger' import errCode from 'err-code' import * as lp from 'it-length-prefixed' import { pipe } from 'it-pipe' -import drain from 'it-drain' import first from 'it-first' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { multiaddr, protocols } from '@multiformats/multiaddr' @@ -191,16 +190,14 @@ export class IdentifyService implements Startable { // make stream abortable const source = abortableDuplex(stream, timeoutController.signal) - await pipe( + await source.sink(pipe( [Identify.encode({ listenAddrs, signedPeerRecord, protocols })], - lp.encode(), - source, - drain - ) + lp.encode() + )) } catch (err: any) { // Just log errors log.error('could not push identify update to peer', err) @@ -430,12 +427,8 @@ export class IdentifyService implements Startable { // make stream abortable const source = abortableDuplex(stream, timeoutController.signal) - await pipe( - [message], - lp.encode(), - source, - drain - ) + const msgWithLenPrefix = pipe([message], lp.encode()) + await source.sink(msgWithLenPrefix) } catch (err: any) { log.error('could not respond to identify request', err) } finally { From f4b1f546a0dcfe648fea2e08832c63a7846f51c2 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 14 Oct 2022 19:04:45 +0100 Subject: [PATCH 444/447] docs: update metrics docs (#1441) Document how libp2p components can record metrics --- doc/METRICS.md | 199 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 195 insertions(+), 4 deletions(-) diff --git a/doc/METRICS.md b/doc/METRICS.md index 027691cc0c..8bd39d5f0b 100644 --- a/doc/METRICS.md +++ b/doc/METRICS.md @@ -1,15 +1,51 @@ -# Bandwidth Metrics +# Libp2p Metrics -- Metrics gathering is optional, as there is a performance hit to using it. -- Metrics do NOT currently contain OS level stats, only libp2p application level Metrics. For example, TCP messages (ACK, FIN, etc) are not accounted for. +Metrics allow you to gather run time statistics on your libp2p node. + +## Table of Contents + +- [Overview](#overview) +- [Tracking](#tracking) + - [Enable metrics](#enable-metrics) + - [Stream Metrics](#stream-metrics) + - [Component Metrics](#component-metrics) + - [Application metrics](#application-metrics) + - [Integration](#integration) +- [Extracting metrics](#extracting-metrics) + +## Overview + +- Metrics gathering is optional, as there is a performance hit to using it - See the [API](./API.md) for Metrics usage. Metrics in libp2p do not emit events, as such applications wishing to read Metrics will need to do so actively. This ensures that the system is not unnecessarily firing update notifications. +- For large installations you may wish to combine the statistics with a visualizer such as [Graphana](https://grafana.com/) + +There are two types of metrics [`StreamMetrics`](https://github.com/libp2p/js-libp2p-interfaces/blob/master/packages/interface-metrics/src/index.ts#L66-L115) and [`ComponentMetrics`](https://github.com/libp2p/js-libp2p-interfaces/blob/master/packages/interface-metrics/src/index.ts#L183-L193). `StreamMetrics` track data in and out of streams, `ComponentMetrics` allow system components to record metrics that are of interest to the observer. + +Although designed to primarily integrate with tools such as [Prometheus](https://prometheus.io/) it does not introduce any dependencies that require you to use any particular tool to store or graph metrics. ## Tracking + - When a transport hands off a connection for upgrading, Metrics are hooked up if enabled. - When a stream is created, Metrics will be tracked on that stream and associated to that streams protocol. - Tracked Metrics are associated to a specific peer, and count towards global bandwidth Metrics. -### Metrics Processing +### Enable metrics + +First enable metrics tracking: + +```js +import { createLibp2pNode } from 'libp2p' + +const node = await createLibp2pNode({ + metrics: { + enabled: true + } + //... other config +}) +``` + +### Stream Metrics + - The main Metrics object consists of individual `Stats` objects - The following categories are tracked: - Global stats; every byte in and out @@ -26,3 +62,158 @@ - When the queue is processed: - The data length is added to either the `in` or `out` stat - The moving averages is calculated since the last queue processing (based on most recently processed item timestamp) + +### Component Metrics + +To define component metrics first get a reference to the metrics object: + +```ts +import type { Metrics } from '@libp2p/interface-metrics' + +interface MyClassComponents { + metrics: Metrics +} + +class MyClass { + private readonly components: MyClassComponents + + constructor (components: MyClassComponents) { + this.components = components + } + + myMethod () { + // here we will set metrics + } +} +``` + +Metrics are updated by calling [`Metrics.updateComponentMetric`](https://github.com/libp2p/js-libp2p-interfaces/blob/master/packages/interface-metrics/src/index.ts#L192) and passing an object that conforms to the [`ComponentMetricsUpdate`](https://github.com/libp2p/js-libp2p-interfaces/blob/master/packages/interface-metrics/src/index.ts#L122-L152) interface: + +```ts +metrics.updateComponentMetric({ + system: 'libp2p', + component: 'connection-manager', + metric: 'incoming-connections', + value: 5 +}) +``` + +If several metrics should be grouped together (e.g. for graphing purposes) the `value` field can be a [`ComponentMetricsGroup`](https://github.com/libp2p/js-libp2p-interfaces/blob/master/packages/interface-metrics/src/index.ts#L159): + +```ts +metrics.updateComponentMetric({ + system: 'libp2p', + component: 'connection-manager', + metric: 'connections', + value: { + incoming: 5, + outgoing: 10 + } +}) +``` + +If the metrics are expensive to calculate, a [`CalculateComponentMetric`](https://github.com/libp2p/js-libp2p-interfaces/blob/master/packages/interface-metrics/src/index.ts#L164) function can be set as the value instead - this will need to be invoked to collect the metrics (see [Extracting metrics](#extracting-metrics) below): + +```ts +metrics.updateComponentMetric({ + system: 'libp2p', + component: 'connection-manager', + metric: 'something-expensive', + value: () => { + // valid return types are: + // number + // Promise + // ComponentMetricsGroup + // Promise + } +}) +``` + +### Application metrics + +You can of course piggy-back your own metrics on to the lib2p metrics object, just specify a different `system` as part of your `ComponentMetricsUpdate`: + +```ts +metrics.updateComponentMetric({ + system: 'my-app', + component: 'my-component', + metric: 'important-metric', + value: 5 +}) +``` + +### Integration + +To help with integrating with metrics gathering software, a `label` and `help` can also be added to your `ComponentMetricsUpdate`. These are expected by certain tools such as [Prometheus](https://prometheus.io/). + +```ts +metrics.updateComponentMetric({ + system: 'libp2p', + component: 'connection-manager', + metric: 'incoming-connections', + value: 5, + label: 'label', + help: 'help' +}) +``` + +## Extracting metrics + +Metrics can be extracted from the metrics object and supplied to a tracking system such as [Prometheus](https://prometheus.io/). This code is borrowed from the `js-ipfs` metrics endpoint which uses [prom-client](https://www.npmjs.com/package/prom-client) to format metrics: + +```ts +import client from 'prom-client' + +const libp2p = createLibp2pNode({ + metrics: { + enabled: true + } + //... other config +}) + +// A handler invoked by express/hapi or your http framework of choice +export default async function metricsEndpoint (req, res) { + const metrics = libp2p.metrics + + if (metrics) { + // update the prometheus client with the recorded metrics + for (const [system, components] of metrics.getComponentMetrics().entries()) { + for (const [component, componentMetrics] of components.entries()) { + for (const [metricName, trackedMetric] of componentMetrics.entries()) { + // set the relevant gauges + const name = `${system}-${component}-${metricName}`.replace(/-/g, '_') + const labelName = trackedMetric.label ?? metricName.replace(/-/g, '_') + const help = trackedMetric.help ?? metricName.replace(/-/g, '_') + const gaugeOptions = { name, help } + const metricValue = await trackedMetric.calculate() + + if (typeof metricValue !== 'number') { + // metric group + gaugeOptions.labelNames = [ + labelName + ] + } + + if (!gauges[name]) { + // create metric if it's not been seen before + gauges[name] = new client.Gauge(gaugeOptions) + } + + if (typeof metricValue !== 'number') { + // metric group + Object.entries(metricValue).forEach(([key, value]) => { + gauges[name].set({ [labelName]: key }, value) + }) + } else { + // metric value + gauges[name].set(metricValue) + } + } + } + } + } + + // done updating, write the metrics into the response + res.send(await client.register.metrics()) +} +``` From 931e042228d286dfc604f91951316b83fa1734f3 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Mon, 17 Oct 2022 11:04:26 +0100 Subject: [PATCH 445/447] fix: enable identify service all the time (#1440) We only enable identify if stream muxers are configured, but new transports can be their own muxers so just enable it all the time. --- src/libp2p.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/libp2p.ts b/src/libp2p.ts index fe42acfd2d..18b878e835 100644 --- a/src/libp2p.ts +++ b/src/libp2p.ts @@ -165,14 +165,11 @@ export class Libp2pNode extends EventEmitter implements Libp2p { this.components.transportManager.add(this.configureComponent(fn(this.components))) }) - // Attach stream multiplexers - if (init.streamMuxers != null && init.streamMuxers.length > 0) { - // Add the identify service since we can multiplex - this.identifyService = new IdentifyService(this.components, { - ...init.identify - }) - this.configureComponent(this.identifyService) - } + // Add the identify service + this.identifyService = new IdentifyService(this.components, { + ...init.identify + }) + this.configureComponent(this.identifyService) // dht provided components (peerRouting, contentRouting, dht) if (init.dht != null) { From c69e452c7e7b71c79dfe9b3e6e0c45ecaf5ba41b Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Mon, 17 Oct 2022 11:34:21 +0100 Subject: [PATCH 446/447] deps: update it-* deps to ESM versions (#1444) Updates all `it-*` deps to esm only versions --- package.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 53dceefa33..11d3a3f56a 100644 --- a/package.json +++ b/package.json @@ -135,18 +135,18 @@ "events": "^3.3.0", "hashlru": "^2.3.0", "interface-datastore": "^7.0.0", - "it-all": "^1.0.6", - "it-drain": "^1.0.5", - "it-filter": "^1.0.3", - "it-first": "^1.0.6", - "it-foreach": "^0.1.1", + "it-all": "^2.0.0", + "it-drain": "^2.0.0", + "it-filter": "^2.0.0", + "it-first": "^2.0.0", + "it-foreach": "^1.0.0", "it-handshake": "^4.1.2", "it-length-prefixed": "^8.0.2", - "it-map": "^1.0.6", - "it-merge": "^1.0.3", + "it-map": "^2.0.0", + "it-merge": "^2.0.0", "it-pair": "^2.0.2", "it-pipe": "^2.0.3", - "it-sort": "^1.0.1", + "it-sort": "^2.0.0", "it-stream-types": "^1.0.4", "merge-options": "^3.0.4", "multiformats": "^10.0.0", @@ -196,7 +196,7 @@ "execa": "^6.1.0", "go-libp2p": "^0.0.6", "it-pushable": "^3.0.0", - "it-to-buffer": "^2.0.2", + "it-to-buffer": "^3.0.0", "npm-run-all": "^4.1.5", "p-defer": "^4.0.0", "p-event": "^5.0.1", From 9fcaff8b5de4e8ddd28870acb296ab034c7e00dd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 13:51:52 +0100 Subject: [PATCH 447/447] chore: release 0.40.0 (#1410) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 24 ++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da5af2cb2a..95b9119fcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,30 @@ +## [0.40.0](https://www.github.com/libp2p/js-libp2p/compare/v0.39.5...v0.40.0) (2022-10-17) + + +### ⚠ BREAKING CHANGES + +* modules no longer implement `Initializable` instead switching to constructor injection +* the old behaviour was to dial any peer we discover, now we just add them to the peer store instead + +### Features + +* allow skipping encryption and custom muxer factory in upgrader ([#1411](https://www.github.com/libp2p/js-libp2p/issues/1411)) ([6615efa](https://www.github.com/libp2p/js-libp2p/commit/6615efa683f55425f90c70815467ec5ddfed1fcb)) +* deny incoming connections and add allow/deny lists ([#1398](https://www.github.com/libp2p/js-libp2p/issues/1398)) ([c185ef5](https://www.github.com/libp2p/js-libp2p/commit/c185ef549f599510f258d5d67883f7062c1c944b)) + + +### Bug Fixes + +* add after upgrade inbound method ([#1422](https://www.github.com/libp2p/js-libp2p/issues/1422)) ([487b942](https://www.github.com/libp2p/js-libp2p/commit/487b94240e244e31ebadb2f8229c1465717454eb)) +* add pending connection limit ([#1423](https://www.github.com/libp2p/js-libp2p/issues/1423)) ([b717beb](https://www.github.com/libp2p/js-libp2p/commit/b717bebf6db1483fc52595a2a137685162d29dca)) +* close stream after sending identify ([#1424](https://www.github.com/libp2p/js-libp2p/issues/1424)) ([a74d22a](https://www.github.com/libp2p/js-libp2p/commit/a74d22a2cddf9ffdca26447fe21a62b5d13e773d)) +* do not auto-dial peers ([#1397](https://www.github.com/libp2p/js-libp2p/issues/1397)) ([ca30192](https://www.github.com/libp2p/js-libp2p/commit/ca3019283497040314603d9ca7c0b65c64d1680c)) +* enable identify service all the time ([#1440](https://www.github.com/libp2p/js-libp2p/issues/1440)) ([931e042](https://www.github.com/libp2p/js-libp2p/commit/931e042228d286dfc604f91951316b83fa1734f3)) +* regenerate protobuf defs ([#1439](https://www.github.com/libp2p/js-libp2p/issues/1439)) ([e10eea2](https://www.github.com/libp2p/js-libp2p/commit/e10eea24d40243c12584429a3b3012488f82bd00)) +* remove @libp2p/components ([#1427](https://www.github.com/libp2p/js-libp2p/issues/1427)) ([a3847f2](https://www.github.com/libp2p/js-libp2p/commit/a3847f2d1725b1c92d5e0ef7bcdf840ea8428a75)) + ### [0.39.5](https://www.github.com/libp2p/js-libp2p/compare/v0.39.4...v0.39.5) (2022-10-05) diff --git a/package.json b/package.json index 11d3a3f56a..41c0cae288 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.39.5", + "version": "0.40.0", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p#readme",