feat: support dial only on transport manager to tolerate errors (#643)

* feat: support dial only on transport manager to tolerate errors

* chore: address review

* chore: add jsdoc to transport manager tolerance errors
This commit is contained in:
Vasco Santos 2020-05-25 16:49:04 +02:00 committed by Jacob Heun
parent 7f4662f8d8
commit 698c1df1b4
6 changed files with 109 additions and 3 deletions

View File

@ -89,6 +89,7 @@ Creates an instance of Libp2p.
| [options.addresses] | `{ listen: Array<string>, announce: Array<string>, noAnnounce: Array<string> }` | Addresses for transport listening and to advertise to the network | | [options.addresses] | `{ listen: Array<string>, announce: Array<string>, noAnnounce: Array<string> }` | Addresses for transport listening and to advertise to the network |
| [options.config] | `object` | libp2p modules configuration and core configuration | | [options.config] | `object` | libp2p modules configuration and core configuration |
| [options.connectionManager] | [`object`](./CONFIGURATION.md#configuring-connection-manager) | libp2p Connection Manager 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.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.dialer] | [`object`](./CONFIGURATION.md#configuring-dialing) | libp2p Dialer configuration
| [options.keychain] | [`object`](./CONFIGURATION.md#setup-with-keychain) | keychain configuration | | [options.keychain] | [`object`](./CONFIGURATION.md#setup-with-keychain) | keychain configuration |

View File

@ -23,6 +23,7 @@
- [Setup with Keychain](#setup-with-keychain) - [Setup with Keychain](#setup-with-keychain)
- [Configuring Dialing](#configuring-dialing) - [Configuring Dialing](#configuring-dialing)
- [Configuring Connection Manager](#configuring-connection-manager) - [Configuring Connection Manager](#configuring-connection-manager)
- [Configuring Transport Manager](#configuring-transport-manager)
- [Configuring Metrics](#configuring-metrics) - [Configuring Metrics](#configuring-metrics)
- [Configuring PeerStore](#configuring-peerstore) - [Configuring PeerStore](#configuring-peerstore)
- [Customizing Transports](#customizing-transports) - [Customizing Transports](#customizing-transports)
@ -517,6 +518,30 @@ const node = await Libp2p.create({
}) })
``` ```
#### Configuring Transport Manager
The Transport Manager is responsible for managing the libp2p transports life cycle. This includes starting listeners for the provided listen addresses, closing these listeners and dialing using the provided transports. By default, if a libp2p node has a list of multiaddrs for listenning on and there are no valid transports for those multiaddrs, libp2p will throw an error on startup and shutdown. However, for some applications it is perfectly acceptable for libp2p nodes to start in dial only mode if all the listen multiaddrs failed. This error tolerance can be enabled as follows:
```js
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { FaultTolerance } = require('libp2p/src/transport-manager')}
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
},
transportManager: {
faultTolerance: FaultTolerance.NO_FATAL
}
})
```
#### Configuring Metrics #### Configuring Metrics
Metrics are disabled in libp2p by default. You can enable and configure them as follows: Metrics are disabled in libp2p by default. You can enable and configure them as follows:

View File

@ -3,6 +3,8 @@
const mergeOptions = require('merge-options') const mergeOptions = require('merge-options')
const Constants = require('./constants') const Constants = require('./constants')
const { FaultTolerance } = require('./transport-manager')
const DefaultConfig = { const DefaultConfig = {
addresses: { addresses: {
listen: [], listen: [],
@ -12,6 +14,9 @@ const DefaultConfig = {
connectionManager: { connectionManager: {
minPeers: 25 minPeers: 25
}, },
transportManager: {
faultTolerance: FaultTolerance.FATAL_ALL
},
dialer: { dialer: {
maxParallelDials: Constants.MAX_PARALLEL_DIALS, maxParallelDials: Constants.MAX_PARALLEL_DIALS,
maxDialsPerPeer: Constants.MAX_PER_PEER_DIALS, maxDialsPerPeer: Constants.MAX_PER_PEER_DIALS,

View File

@ -101,7 +101,8 @@ class Libp2p extends EventEmitter {
// Setup the transport manager // Setup the transport manager
this.transportManager = new TransportManager({ this.transportManager = new TransportManager({
libp2p: this, libp2p: this,
upgrader: this.upgrader upgrader: this.upgrader,
faultTolerance: this._options.transportManager.faultTolerance
}) })
// Create the Registrar // Create the Registrar

View File

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

View File

@ -15,6 +15,8 @@ const mockUpgrader = require('../utils/mockUpgrader')
const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser') const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser')
const { codes: ErrorCodes } = require('../../src/errors') const { codes: ErrorCodes } = require('../../src/errors')
const Libp2p = require('../../src') const Libp2p = require('../../src')
const { FaultTolerance } = require('../../src/transport-manager')
const Peers = require('../fixtures/peers') const Peers = require('../fixtures/peers')
const PeerId = require('peer-id') const PeerId = require('peer-id')
@ -165,3 +167,55 @@ describe('libp2p.transportManager', () => {
expect(libp2p.transportManager.close.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: [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: {
faultTolerance: FaultTolerance.NO_FATAL
},
modules: {
transport: [Transport]
}
})
await libp2p.start()
})
})