Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.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 |
Expand Down
25 changes: 25 additions & 0 deletions doc/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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

Metrics are disabled in libp2p by default. You can enable and configure them as follows:
Expand Down
5 changes: 5 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
const mergeOptions = require('merge-options')
const Constants = require('./constants')

const { FaultTolerance } = require('./transport-manager')

const DefaultConfig = {
addresses: {
listen: [],
Expand All @@ -12,6 +14,9 @@ const DefaultConfig = {
connectionManager: {
minPeers: 25
},
transportManager: {
faultTolerance: FaultTolerance.FATAL_ALL
},
dialer: {
maxParallelDials: Constants.MAX_PARALLEL_DIALS,
maxDialsPerPeer: Constants.MAX_PER_PEER_DIALS,
Expand Down
3 changes: 2 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ class Libp2p extends EventEmitter {
// Setup the transport manager
this.transportManager = new TransportManager({
libp2p: this,
upgrader: this.upgrader
upgrader: this.upgrader,
faultTolerance: this._options.transportManager.faultTolerance
})

// Create the Registrar
Expand Down
17 changes: 15 additions & 2 deletions src/transport-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.faultTolerance = FAULT_TOLERANCE.FATAL_ALL] Address listen error tolerance.
*/
constructor ({ libp2p, upgrader }) {
constructor ({ libp2p, upgrader, faultTolerance = FAULT_TOLERANCE.FATAL_ALL }) {
this.libp2p = libp2p
this.upgrader = upgrader
this._transports = new Map()
this._listeners = new Map()
this.faultTolerance = faultTolerance
}

/**
Expand Down Expand Up @@ -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.faultTolerance === FAULT_TOLERANCE.FATAL_ALL) {
throw errCode(new Error(message), codes.ERR_NO_VALID_ADDRESSES)
}
log(`libp2p in dial mode only: ${message}`)
}
}

Expand Down Expand Up @@ -212,4 +218,11 @@ class TransportManager {
}
}

const FAULT_TOLERANCE = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add some jsdocs to describe each of these as they're not specified anywhere.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not find any standard to document the Enum values. I added them in the main body description for now. If we increase this list, we should rethink this.

FATAL_ALL: 0,
NO_FATAL: 1
}

TransportManager.FaultTolerance = FAULT_TOLERANCE

module.exports = TransportManager
54 changes: 54 additions & 0 deletions test/transports/transport-manager.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -165,3 +167,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: {
faultTolerance: FaultTolerance.NO_FATAL
},
modules: {
transport: [Transport]
}
})

await libp2p.start()
})
})