From 18423b4d0c88614df8cd75295fd3b9f836495b75 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 18 May 2020 12:39:17 +0200 Subject: [PATCH 1/3] feat: support dial only on transport manager to tolerate errors --- doc/API.md | 1 + doc/CONFIGURATION.md | 23 ++++++++++ src/config.js | 3 ++ src/index.js | 3 +- src/transport-manager.js | 10 ++++- test/transports/transport-manager.spec.js | 52 +++++++++++++++++++++++ 6 files changed, 89 insertions(+), 3 deletions(-) diff --git a/doc/API.md b/doc/API.md index 9c55bbfd5d..a1544a1369 100644 --- a/doc/API.md +++ b/doc/API.md @@ -89,6 +89,7 @@ Creates an instance of Libp2p. | [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.connectionManager] | [`object`](./CONFIGURATION.md#configuring-connection-manager) | libp2p Connection Manager configuration | +| [options.transportManager] | [`object`](./CONFIGURATION.md#configuring-transport-manager) | libp2p transport manager configuration | | [options.datastore] | `object` | must implement [ipfs/interface-datastore](https://github.com/ipfs/interface-datastore) (in memory datastore will be used if not provided) | | [options.dialer] | [`object`](./CONFIGURATION.md#configuring-dialing) | libp2p Dialer configuration | [options.keychain] | [`object`](./CONFIGURATION.md#setup-with-keychain) | keychain configuration | diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 2613054fcd..99499fa583 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -23,6 +23,7 @@ - [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) @@ -517,6 +518,28 @@ const node = await Libp2p.create({ }) ``` +#### Configuring Transport Manager + +The Transport Manager is responsible for managing the libp2p transports life cycle. This includes starting listeners for the provided listen addresses, closing these listeners and dialing using the provided transports. By default, if a libp2p node has a list of multiaddrs for listenning on and there are no valid transports for those multiaddrs, libp2p will throw an error on startup and shutdown. However, for some applications it is perfectly acceptable for libp2p nodes to start in dial only mode if all the listen multiaddrs failed. This error tolerance can be enabled as follows: + +```js +const Libp2p = require('libp2p') +const TCP = require('libp2p-tcp') +const MPLEX = require('libp2p-mplex') +const SECIO = require('libp2p-secio') + +const node = await Libp2p.create({ + modules: { + transport: [TCP], + streamMuxer: [MPLEX], + connEncryption: [SECIO] + }, + transportManager: { + supportDialOnly: true + } +}) +``` + #### Configuring Metrics Metrics are disabled in libp2p by default. You can enable and configure them as follows: diff --git a/src/config.js b/src/config.js index 53c9c4c2b5..c250f3f500 100644 --- a/src/config.js +++ b/src/config.js @@ -12,6 +12,9 @@ const DefaultConfig = { connectionManager: { minPeers: 25 }, + transportManager: { + supportDialOnly: false + }, dialer: { maxParallelDials: Constants.MAX_PARALLEL_DIALS, maxDialsPerPeer: Constants.MAX_PER_PEER_DIALS, diff --git a/src/index.js b/src/index.js index b4ceafc977..8fb41a257c 100644 --- a/src/index.js +++ b/src/index.js @@ -101,7 +101,8 @@ class Libp2p extends EventEmitter { // Setup the transport manager this.transportManager = new TransportManager({ libp2p: this, - upgrader: this.upgrader + upgrader: this.upgrader, + supportDialOnly: this._options.transportManager.supportDialOnly }) // Create the Registrar diff --git a/src/transport-manager.js b/src/transport-manager.js index b1994938fb..cffc1cf612 100644 --- a/src/transport-manager.js +++ b/src/transport-manager.js @@ -13,12 +13,14 @@ class TransportManager { * @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.supportDialOnly Address listen error tolerance supporting dial only mode. */ - constructor ({ libp2p, upgrader }) { + constructor ({ libp2p, upgrader, supportDialOnly }) { this.libp2p = libp2p this.upgrader = upgrader this._transports = new Map() this._listeners = new Map() + this.supportDialOnly = supportDialOnly } /** @@ -173,7 +175,11 @@ class TransportManager { // 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) { - throw errCode(new Error(`no valid addresses were provided for transports [${couldNotListen}]`), codes.ERR_NO_VALID_ADDRESSES) + const message = `no valid addresses were provided for transports [${couldNotListen}]` + if (!this.supportDialOnly) { + throw errCode(new Error(message), codes.ERR_NO_VALID_ADDRESSES) + } + log(`libp2p in dial mode only: ${message}`) } } diff --git a/test/transports/transport-manager.spec.js b/test/transports/transport-manager.spec.js index 4211bd06d3..cea0e107b5 100644 --- a/test/transports/transport-manager.spec.js +++ b/test/transports/transport-manager.spec.js @@ -165,3 +165,55 @@ describe('libp2p.transportManager', () => { 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: [multiaddr('/ip4/127.0.0.1/tcp/0')] + }, + modules: { + transport: [Transport] + } + }) + + try { + await libp2p.start() + } catch (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 multiaddr fails to listen when supporting dial only mode', async () => { + libp2p = new Libp2p({ + peerId, + addresses: { + listen: [multiaddr('/ip4/127.0.0.1/tcp/0')] + }, + transportManager: { + supportDialOnly: true + }, + modules: { + transport: [Transport] + } + }) + + await libp2p.start() + }) +}) From 3f840df6bafc73663cd6f400bbb12330f8973334 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 25 May 2020 16:05:37 +0200 Subject: [PATCH 2/3] chore: address review --- doc/CONFIGURATION.md | 4 +++- src/config.js | 4 +++- src/index.js | 2 +- src/transport-manager.js | 15 +++++++++++---- test/transports/transport-manager.spec.js | 4 +++- 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 99499fa583..6e7b7d1b11 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -528,6 +528,8 @@ const TCP = require('libp2p-tcp') const MPLEX = require('libp2p-mplex') const SECIO = require('libp2p-secio') +const { FaultTolerance } = require('libp2p/src/transport-manager')} + const node = await Libp2p.create({ modules: { transport: [TCP], @@ -535,7 +537,7 @@ const node = await Libp2p.create({ connEncryption: [SECIO] }, transportManager: { - supportDialOnly: true + faultTolerance: FaultTolerance.NO_FATAL } }) ``` diff --git a/src/config.js b/src/config.js index c250f3f500..f586c6dc87 100644 --- a/src/config.js +++ b/src/config.js @@ -3,6 +3,8 @@ const mergeOptions = require('merge-options') const Constants = require('./constants') +const { FaultTolerance } = require('./transport-manager') + const DefaultConfig = { addresses: { listen: [], @@ -13,7 +15,7 @@ const DefaultConfig = { minPeers: 25 }, transportManager: { - supportDialOnly: false + faultTolerance: FaultTolerance.FATAL_ALL }, dialer: { maxParallelDials: Constants.MAX_PARALLEL_DIALS, diff --git a/src/index.js b/src/index.js index 8fb41a257c..ad7a78281c 100644 --- a/src/index.js +++ b/src/index.js @@ -102,7 +102,7 @@ class Libp2p extends EventEmitter { this.transportManager = new TransportManager({ libp2p: this, upgrader: this.upgrader, - supportDialOnly: this._options.transportManager.supportDialOnly + faultTolerance: this._options.transportManager.faultTolerance }) // Create the Registrar diff --git a/src/transport-manager.js b/src/transport-manager.js index cffc1cf612..467483def6 100644 --- a/src/transport-manager.js +++ b/src/transport-manager.js @@ -13,14 +13,14 @@ class TransportManager { * @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.supportDialOnly Address listen error tolerance supporting dial only mode. + * @param {boolean} [options.faultTolerance = FAULT_TOLERANCE.FATAL_ALL] Address listen error tolerance. */ - constructor ({ libp2p, upgrader, supportDialOnly }) { + constructor ({ libp2p, upgrader, faultTolerance = FAULT_TOLERANCE.FATAL_ALL }) { this.libp2p = libp2p this.upgrader = upgrader this._transports = new Map() this._listeners = new Map() - this.supportDialOnly = supportDialOnly + this.faultTolerance = faultTolerance } /** @@ -176,7 +176,7 @@ class TransportManager { // 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.supportDialOnly) { + if (this.faultTolerance === FAULT_TOLERANCE.FATAL_ALL) { throw errCode(new Error(message), codes.ERR_NO_VALID_ADDRESSES) } log(`libp2p in dial mode only: ${message}`) @@ -218,4 +218,11 @@ class TransportManager { } } +const FAULT_TOLERANCE = { + FATAL_ALL: 0, + NO_FATAL: 1 +} + +TransportManager.FaultTolerance = FAULT_TOLERANCE + module.exports = TransportManager diff --git a/test/transports/transport-manager.spec.js b/test/transports/transport-manager.spec.js index cea0e107b5..0acc4b8660 100644 --- a/test/transports/transport-manager.spec.js +++ b/test/transports/transport-manager.spec.js @@ -15,6 +15,8 @@ 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') @@ -207,7 +209,7 @@ describe('libp2p.transportManager (dial only)', () => { listen: [multiaddr('/ip4/127.0.0.1/tcp/0')] }, transportManager: { - supportDialOnly: true + faultTolerance: FaultTolerance.NO_FATAL }, modules: { transport: [Transport] From 53e898391f6f487714e692880f363a9ec6078ab6 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 25 May 2020 16:37:43 +0200 Subject: [PATCH 3/3] chore: add jsdoc to transport manager tolerance errors --- src/transport-manager.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/transport-manager.js b/src/transport-manager.js index 467483def6..beeb6bb6e2 100644 --- a/src/transport-manager.js +++ b/src/transport-manager.js @@ -218,6 +218,13 @@ class TransportManager { } } +/** + * Enum Transport Manager Fault Tolerance values. + * FATAL_ALL should be used for failing in any listen circumstance. + * NO_FATAL should be used for not failing when not listening. + * @readonly + * @enum {number} + */ const FAULT_TOLERANCE = { FATAL_ALL: 0, NO_FATAL: 1