Home

Awesome

⛔️ DEPRECATED: libp2p-switch is now included in js-libp2p

libp2p-switch JavaScript implementation

Discourse posts Travis CI codecov Dependency Status js-standard-style

libp2p-switch is a dialer machine, it leverages the multiple libp2p transports, stream muxers, crypto channels and other connection upgrades to dial to peers in the libp2p network. It also supports Protocol Multiplexing through a multicodec and multistream-select handshake.

libp2p-switch is used by libp2p but it can be also used as a standalone module.

Lead Maintainer

Jacob Heun

Table of Contents

Install

> npm install libp2p-switch --save

Usage

Create a libp2p Switch

const switch = require('libp2p-switch')

const sw = new switch(peerInfo , peerBook [, options])

If defined, options should be an object with the following keys and respective values:

Private Networks

libp2p-switch supports private networking. In order to enabled private networks, the switch.protector must be set and must contain a protect method. You can see an example of this in the private network tests.

API

switch.connection

switch.connection.addUpgrade()

A connection upgrade must be able to receive and return something that implements the interface-connection specification.

WIP

switch.connection.addStreamMuxer(muxer)

Upgrading a connection to use a stream muxer is still considered an upgrade, but a special case since once this connection is applied, the returned obj will implement the interface-stream-muxer spec.

switch.connection.reuse()

Enable the identify protocol.

switch.connection.crypto([tag, encrypt])

Enable a specified crypto protocol. By default no encryption is used, aka plaintext. If called with no arguments it resets to use plaintext.

You can use for example libp2p-secio like this

const secio = require('libp2p-secio')
switch.connection.crypto(secio.tag, secio.encrypt)
switch.connection.enableCircuitRelay(options, callback)

Enable circuit relaying.

switch.dial(peer, protocol, callback)

dial uses the best transport (whatever works first, in the future we can have some criteria), and jump starts the connection until the point where we have to negotiate the protocol. If a muxer is available, then drop the muxer onto that connection. Good to warm up connections or to check for connectivity. If we have already a muxer for that peerInfo, then do nothing.

switch.dialFSM(peer, protocol, callback)

works like dial, but calls back with a Connection State Machine

Connection State Machine

Connection state machines emit a number of events that can be used to determine the current state of the connection and to received the underlying connection that can be used to transfer data.

switch.dialer.connect(peer, options, callback)

a low priority dial to the provided peer. Calls to dial and dialFSM will take priority. This should be used when an application only wishes to establish connections to new peers, such as during peer discovery when there is a low peer count. Currently, anything greater than the HIGH_PRIORITY (10) will be placed into the cold call queue, and anything less than or equal to the HIGH_PRIORITY will be added to the normal queue.

Events

switch.handle(protocol, handlerFunc, matchFunc)

Handle a new protocol.

switch.hangUp(peer, callback)

Hang up the muxed connection we have with the peer.

switch.on('error', (err) => {})

Emitted when the switch encounters an error.

switch.on('peer-mux-established', (peer) => {})

switch.on('peer-mux-closed', (peer) => {})

switch.on('connection:start', (peer) => {})

This will be triggered anytime a new connection is created.

switch.on('connection:end', (peer) => {})

This will be triggered anytime an existing connection, regardless of state, is removed from the switch's internal connection tracking.

switch.on('start', () => {})

Emitted when the switch has successfully started.

switch.on('stop', () => {})

Emitted when the switch has successfully stopped.

switch.start(callback)

Start listening on all added transports that are available on the current peerInfo.

switch.stop(callback)

Close all the listeners and muxers.

Stats API

switch.stats.emit('update')

Every time any stat value changes, this object emits an update event.

Global stats

switch.stats.global.snapshot

Should return a stats snapshot, which is an object containing the following keys and respective values:

switch.stats.global.movingAverages

Returns an object containing the following keys:

Each one of them contains an object that has a key for each interval (60000, 300000 and 900000 miliseconds).

Each one of these values is an exponential moving-average instance.

Per-transport stats

switch.stats.transports()

Returns an array containing the tags (string) for each observed transport.

switch.stats.forTransport(transportTag).snapshot

Should return a stats snapshot, which is an object containing the following keys and respective values:

switch.stats.forTransport(transportTag).movingAverages

Returns an object containing the following keys:

dataSent dataReceived

Each one of them contains an object that has a key for each interval (60000, 300000 and 900000 miliseconds).

Each one of these values is an exponential moving-average instance.

Per-protocol stats

switch.stats.protocols()

Returns an array containing the tags (string) for each observed protocol.

switch.stats.forProtocol(protocolTag).snapshot

Should return a stats snapshot, which is an object containing the following keys and respective values:

switch.stats.forProtocol(protocolTag).movingAverages

Returns an object containing the following keys:

Each one of them contains an object that has a key for each interval (60000, 300000 and 900000 miliseconds).

Each one of these values is an exponential moving-average instance.

Per-peer stats

switch.stats.peers()

Returns an array containing the peerIDs (B58-encoded string) for each observed peer.

switch.stats.forPeer(peerId:String).snapshot

Should return a stats snapshot, which is an object containing the following keys and respective values:

switch.stats.forPeer(peerId:String).movingAverages

Returns an object containing the following keys:

Each one of them contains an object that has a key for each interval (60000, 300000 and 900000 miliseconds).

Each one of these values is an exponential moving-average instance.

Stats update interval

Stats are not updated in real-time. Instead, measurements are buffered and stats are updated at an interval. The maximum interval can be defined through the Switch constructor option stats.computeThrottleTimeout, defined in miliseconds.

switch.unhandle(protocol)

Unhandle a protocol.

Internal Transports API

switch.transport.add(key, transport, options)

libp2p-switch expects transports that implement interface-transport. For example libp2p-tcp.

switch.transport.dial(key, multiaddrs, callback)

Dial to a peer on a specific transport.

switch.transport.listen(key, options, handler, callback)

Set a transport to start listening mode.

switch.transport.close(key, callback)

Close the listeners of a given transport.

Design Notes

Multitransport

libp2p is designed to support multiple transports at the same time. While peers are identified by their ID (which are generated from their public keys), the addresses of each pair may vary, depending the device where they are being run or the network in which they are accessible through.

In order for a transport to be supported, it has to follow the interface-transport spec.

Connection upgrades

Each connection in libp2p follows the interface-connection spec. This design decision enables libp2p to have upgradable transports.

We think of upgrade as a very important notion when we are talking about connections, we can see mechanisms like: stream multiplexing, congestion control, encrypted channels, multipath, simulcast, etc, as upgrades to a connection. A connection can be a simple and with no guarantees, drop a packet on the network with a destination thing, a transport in the other hand can be a connection and or a set of different upgrades that are mounted on top of each other, giving extra functionality to that connection and therefore upgrading it.

Types of upgrades to a connection:

We also want to enable flexibility when it comes to upgrading a connection, for example, we might that all dialed transports pass through the encrypted channel upgrade, but not the congestion flow, specially when a transport might have already some underlying properties (UDP vs TCP vs WebRTC vs every other transport protocol)

Identify

Identify is a protocol that switchs mounts on top of itself, to identify the connections between any two peers. E.g:

In addition to this, we also share the "observed addresses" by the other peer, which is extremely useful information for different kinds of network topologies.

Notes

To avoid the confusion between connection, stream, transport, and other names that represent an abstraction of data flow between two points, we use terms as:

This module uses pull-streams

We expose a streaming interface based on pull-streams, rather then on the Node.js core streams implementation (aka Node.js streams). pull-streams offers us a better mechanism for error handling and flow control guarantees. If you would like to know more about why we did this, see the discussion at this issue.

You can learn more about pull-streams at:

Converting pull-streams to Node.js Streams

If you are a Node.js streams user, you can convert a pull-stream to a Node.js stream using the module pull-stream-to-stream, giving you an instance of a Node.js stream that is linked to the pull-stream. For example:

const pullToStream = require('pull-stream-to-stream')

const nodeStreamInstance = pullToStream(pullStreamInstance)
// nodeStreamInstance is an instance of a Node.js Stream

To learn more about this utility, visit https://pull-stream.github.io/#pull-stream-to-stream.

Contribute

This module is actively under development. Please check out the issues and submit PRs!

License

MIT © Protocol Labs