
py-libp2p has moved beyond its experimental roots and is steadily progressing toward production readiness. The core features are stable, and weβre focused on refining performance, expanding protocol support, and ensuring smooth interop with other libp2p implementations. We welcome contributions and real-world usage feedback to help us reach full production maturity.
Read more in the documentation on ReadTheDocs. View the release notes.
Currently maintained by @pacrob, @seetadev and @dhuseby. Please reach out to us for collaboration or active feedback. If you have questions, feel free to open a new discussion. We are also available on the libp2p Discord β join us at #py-libp2p sub-channel.
py-libp2p aims for conformity with the standard libp2p modules. Below is a breakdown of the modules we have developed, are developing, and may develop in the future.
Legend: β : Done π οΈ: In Progress/Usable π± Prototype/Unstable β: Missing
| Transport | Status | Source |
|---|---|---|
libp2p-tcp |
β | source |
libp2p-quic |
β | source |
libp2p-websocket |
β | source |
libp2p-webtransport |
π± | source |
libp2p-webrtc-browser-to-server |
π± | |
libp2p-webrtc-private-to-private |
π± |
WebRTC-Direct (browser/server) uses the spec STUN path by default: inbound dials hit a shared UDP port and the listener infers the offer from the first STUN packet; outbound dials synthesise an ICE-Lite answer from the multiaddr with no default public STUN servers. This is the only signalling path that interoperates with go-libp2p / js-libp2p / browsers. An experimental HTTP POST /sdp harness (WebRTCTransportConfig(enable_sdp_http_harness=True)) is for pyβpy debugging only and is not interoperable β leave it off in production.
Choosing a dial version. The listener always accepts both v1 and v2; the dialer picks with WebRTCTransportConfig(webrtc_direct_dial_version=1|2):
NoSdpMangleUfrag support is widespread. The default flips to v2 once specs#715 lands.| NAT Traversal | Status | Source |
|---|---|---|
libp2p-circuit-relay-v2 |
β | source |
libp2p-autonat |
β | source |
libp2p-hole-punching |
β | source |
| Secure Communication | Status | Source |
|---|---|---|
libp2p-noise |
β | source |
libp2p-tls |
β |
| Discovery | Status | Source |
|---|---|---|
bootstrap |
β | source |
random-walk |
β | source |
mdns-discovery |
β | source |
rendezvous |
β | source |
| Peer Routing | Status | Source |
|---|---|---|
libp2p-kad-dht |
β | source |
| Publish/Subscribe | Status | Source |
|---|---|---|
libp2p-floodsub |
β | source |
libp2p-gossipsub |
β | source |
| Stream Muxers | Status | Source |
|---|---|---|
libp2p-yamux |
β | source |
libp2p-mplex |
β | source |
| Storage | Status |
|---|---|
libp2p-record |
β |
| Utility/Datatype | Status | Source |
|---|---|---|
libp2p-ping |
β | source |
libp2p-peer |
β | source |
libp2p-identify |
β | source |
For a stable Peer ID across restarts, use opt-in identity helpers (default new_host() behavior is unchanged and still generates a random Ed25519 key):
libp2p.identity_utils β save_identity / load_identity / create_identity_from_seed (libp2p protobuf private-key format; Unix mode 0600).new_host(key_pair_provider=...) accepts Callable[[], KeyPair] as described in issue #312.FileSystemKeyStore stores named keys under a directory; pass new_host(keystore=..., identity_name="default") to load or auto-persist.Prefer these APIs over the legacy PEM helpers save_keypair / load_keypair (Ed25519-only, fixed path).
Example (keystore)::
from pathlib import Path
from libp2p import new_host
from libp2p.crypto.keystore import FileSystemKeyStore
store = FileSystemKeyStore(Path.home() / ".config" / "py-libp2p" / "keystore")
host = new_host(keystore=store, identity_name="default")
# Subsequent runs with the same store/name reuse the same Peer ID.
(non-normative, useful for team notes, not a reference)
Several components of the libp2p stack take part when establishing a connection between two nodes:
(non-normative, useful for team notes, not a reference)
Initiate the connection: A host is simply a node in the libp2p network that is able to communicate with other nodes in the network. In order for X and Y to communicate with one another, one of the hosts must initiate the connection. Let's say that X is going to initiate the connection. X will first open a connection to Y. This connection is where all of the actual communication will take place.
Communication over one connection with multiple protocols: X and Y can communicate over the same connection using different protocols and the multiplexer will appropriately route messages for a given protocol to a particular handler function for that protocol, which allows for each host to handle different protocols with separate functions. Furthermore, we can use multiple streams for a given protocol that allow for the same protocol and same underlying connection to be used for communication about separate topics between nodes X and Y.
Why use multiple streams?: The purpose of using the same connection for multiple streams to communicate over is to avoid the overhead of having multiple connections between X and Y. In order for X and Y to differentiate between messages on different streams and different protocols, a multiplexer is used to encode the messages when a message will be sent and decode a message when a message is received. The multiplexer encodes the message by adding a header to the beginning of any message to be sent that contains the stream id (along with some other info). Then, the message is sent across the raw connection and the receiving host will use its multiplexer to decode the message, i.e. determine which stream id the message should be routed to.