libp2p-switch JavaScript implementation

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.
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:
-
stats
: an object with the following keys and respective values:
maxOldPeersRetention
: maximum old peers retention. For when peers disconnect and keeping the stats around in case they reconnect. Defaults to 100
.
computeThrottleMaxQueueSize
: maximum queue size to perform stats computation throttling. Defaults to 1000
.
computeThrottleTimeout
: Throttle timeout, in miliseconds. Defaults to 2000
,
movingAverageIntervals
: Array containin the intervals, in miliseconds, for which moving averages are calculated. Defaults to:
[
60 * 1000,
5 * 60 * 1000,
15 * 60 * 1000
]
API
- peerInfo is a PeerInfo object that has the peer information.
- peerBook is a PeerBook object that stores all the known peers.
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.
peer
: can be an instance of [PeerInfo][], [PeerId][] or [multiaddr][]
protocol
callback
switch.hangUp(peer, callback)
Hang up the muxed connection we have with the peer.
peer
: can be an instance of [PeerInfo][], [PeerId][] or [multiaddr][]
callback
switch.handle(protocol, handlerFunc, matchFunc)
Handle a new protocol.
protocol
handlerFunc
- function called when we receive a dial on protocol. Signature must be
function (protocol, conn) {}`
matchFunc
- matchFunc for multistream-select
switch.unhandle(protocol)
Unhandle a protocol.
switch.on('peer-mux-established', (peer) => {})
peer
: is instance of [PeerInfo][] that has info of the peer we have just established a muxed connection with.
switch.on('peer-mux-closed', (peer) => {})
peer
: is instance of [PeerInfo][] that has info of the peer we have just closed a muxed connection.
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.
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.
options
- enabled - activates relay dialing and listening functionality
- hop - an object with two properties
- enabled - enables circuit relaying
- active - is it an active or passive relay (default false)
callback
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:
- dataSent: amount of bytes sent, Big number
- dataReceived: amount of bytes received, Big number
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:
- dataSent: amount of bytes sent, Big number
- dataReceived: amount of bytes received, Big number
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:
- dataSent: amount of bytes sent, Big number
- dataReceived: amount of bytes received, Big number
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:
- dataSent: amount of bytes sent, Big number
- dataReceived: amount of bytes received, Big number
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.
Internal Transports API
switch.transport.add(key, transport, options)
libp2p-switch expects transports that implement interface-transport. For example libp2p-tcp.
key
- the transport identifier.
transport
-
options
-
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.
key
options
handler
callback
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:
- encrypted channel (with TLS for e.g)
- congestion flow (some transports don't have it by default)
- multipath (open several connections and abstract it as a single connection)
- simulcast (still really thinking this one through, it might be interesting to send a packet through different connections under some hard network circumstances)
- stream-muxer - this a special case, because once we upgrade a connection to a stream-muxer, we can open more streams (multiplex them) on a single stream, also enabling us to reuse the underlying dialed transport
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:
- a) peer A dials a conn to peer B
- b) that conn gets upgraded to a stream multiplexer that both peers agree
- c) peer B executes de identify protocol
- d) peer B now can open streams to peer A, knowing which is the
identity of peer A
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:
- connection - something that implements the transversal expectations of a stream between two peers, including the benefits of using a stream plus having a way to do half duplex, full duplex
- transport - something that as a dial/listen interface and return objs that implement a connection interface
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)
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