Getting Started

Welcome to py-libp2p! This guide will walk you through setting up a fully functional libp2p node in Python 🚀

Install

The first step is to install py-libp2p in your project. Follow the installation steps in the Install guide.

Configuring libp2p

If you’re new to libp2p, we recommend configuring your node in stages, as this can make troubleshooting configuration issues much easier. In this guide, we’ll do just that.

Basic Setup

Now that we have py-libp2p installed, let’s configure the minimum needed to get your node running. The only modules libp2p requires are a Transport and Crypto module. However, we recommend that a basic setup should also have a Stream Multiplexer configured. Let’s start by setting up a Transport.

Transports

Libp2p uses Transports to establish connections between peers over the network. Transports are the components responsible for performing the actual exchange of data between libp2p nodes. You can configure any number of Transports, but you only need 1 to start with.

For Python, the most common transport is TCP. Here’s how to set up a basic TCP transport:

import secrets

import trio

from libp2p import (
    new_host,
)
from libp2p.crypto.secp256k1 import (
    create_new_key_pair,
)
from libp2p.utils.address_validation import (
    get_available_interfaces,
    get_optimal_binding_address,
)


async def main():
    # Create a key pair for the host
    secret = secrets.token_bytes(32)
    key_pair = create_new_key_pair(secret)

    # Create a host with the key pair
    host = new_host(key_pair=key_pair)

    # Configure the listening address using the new paradigm
    port = 8000
    listen_addrs = get_available_interfaces(port)
    optimal_addr = get_optimal_binding_address(port)

    # Start the host
    async with host.run(listen_addrs=listen_addrs):
        print("libp2p has started with TCP transport")
        print("libp2p is listening on:", host.get_addrs())
        print(f"Optimal address: {optimal_addr}")
        # Keep the host running
        await trio.sleep_forever()


# Run the async function
trio.run(main)

Also, QUIC is a modern transport protocol that provides built-in TLS security and stream multiplexing over UDP:

import secrets

import trio

from libp2p import (
    new_host,
)
from libp2p.crypto.secp256k1 import (
    create_new_key_pair,
)
from libp2p.utils.address_validation import (
    get_available_interfaces,
    get_optimal_binding_address,
)


async def main():
    # Create a key pair for the host
    secret = secrets.token_bytes(32)
    key_pair = create_new_key_pair(secret)

    # Create a host with the key pair
    host = new_host(key_pair=key_pair, enable_quic=True)

    # Configure the listening address using the new paradigm
    port = 8000
    listen_addrs = get_available_interfaces(port, protocol="udp")
    # Convert TCP addresses to QUIC-v1 addresses
    quic_addrs = []
    for addr in listen_addrs:
        addr_str = str(addr).replace("/tcp/", "/udp/") + "/quic-v1"
        from multiaddr import Multiaddr

        quic_addrs.append(Multiaddr(addr_str))

    optimal_addr = get_optimal_binding_address(port, protocol="udp")
    optimal_quic_str = str(optimal_addr).replace("/tcp/", "/udp/") + "/quic-v1"

    # Start the host
    async with host.run(listen_addrs=quic_addrs):
        print("libp2p has started with QUIC transport", flush=True)
        print("libp2p is listening on:", host.get_addrs(), flush=True)
        print(f"Optimal address: {optimal_quic_str}", flush=True)
        # Keep the host running
        await trio.sleep_forever()


# Run the async function
trio.run(main)

Connection Encryption

Encryption is an important part of communicating on the libp2p network. Every connection should be encrypted to help ensure security for everyone. As such, Connection Encryption (Crypto) is a recommended component of libp2p.

py-libp2p provides several security transport options:

  1. Noise - A modern, flexible, and secure protocol for encryption and authentication

  2. SECIO - The legacy security protocol used in older versions of libp2p

  3. Insecure - A transport that provides no encryption (not recommended for production use)

For most applications, we recommend using the Noise protocol for encryption:

import secrets

import trio

from libp2p import (
    new_host,
)
from libp2p.crypto.secp256k1 import (
    create_new_key_pair,
)
from libp2p.crypto.x25519 import create_new_key_pair as create_new_x25519_key_pair
from libp2p.security.noise.transport import (
    PROTOCOL_ID as NOISE_PROTOCOL_ID,
    Transport as NoiseTransport,
)
from libp2p.utils.address_validation import (
    get_available_interfaces,
    get_optimal_binding_address,
)


async def main():
    # Create a key pair for the host
    secret = secrets.token_bytes(32)
    key_pair = create_new_key_pair(secret)
    noise_key_pair = create_new_x25519_key_pair()

    # Create a Noise security transport
    noise_transport = NoiseTransport(
        # libp2p_keypair: libp2p identity (distinct from the Noise static key per spec)
        libp2p_keypair=key_pair,
        # noise_privkey: X25519 static key for Noise DH (libp2p Noise spec)
        noise_privkey=noise_key_pair.private_key,
        # early_data: Optional data to send during the handshake
        # (None means no early data)
        early_data=None,
    )

    # Create a security options dictionary mapping protocol ID to transport
    security_options = {NOISE_PROTOCOL_ID: noise_transport}

    # Create a host with the key pair and Noise security
    host = new_host(key_pair=key_pair, sec_opt=security_options)

    # Configure the listening address using the new paradigm
    port = 8000
    listen_addrs = get_available_interfaces(port)
    optimal_addr = get_optimal_binding_address(port)

    # Start the host
    async with host.run(listen_addrs=listen_addrs):
        print("libp2p has started with Noise encryption")
        print("libp2p is listening on:", host.get_addrs())
        print(f"Optimal address: {optimal_addr}")
        # Keep the host running
        await trio.sleep_forever()


# Run the async function
trio.run(main)

If you need to use SECIO (for compatibility with older libp2p implementations):

import secrets

import trio

from libp2p import (
    new_host,
)
from libp2p.crypto.secp256k1 import (
    create_new_key_pair,
)
from libp2p.security.secio.transport import (
    ID as SECIO_PROTOCOL_ID,
    Transport as SecioTransport,
)
from libp2p.utils.address_validation import (
    get_available_interfaces,
    get_optimal_binding_address,
)


async def main():
    # Create a key pair for the host
    secret = secrets.token_bytes(32)
    key_pair = create_new_key_pair(secret)

    # Create a SECIO security transport
    secio_transport = SecioTransport(
        # local_key_pair: The key pair used for libp2p identity and authentication
        local_key_pair=key_pair,
    )

    # Create a security options dictionary mapping protocol ID to transport
    security_options = {SECIO_PROTOCOL_ID: secio_transport}

    # Create a host with the key pair and SECIO security
    host = new_host(key_pair=key_pair, sec_opt=security_options)

    # Configure the listening address using the new paradigm
    port = 8000
    listen_addrs = get_available_interfaces(port)
    optimal_addr = get_optimal_binding_address(port)

    # Start the host
    async with host.run(listen_addrs=listen_addrs):
        print("libp2p has started with SECIO encryption")
        print("libp2p is listening on:", host.get_addrs())
        print(f"Optimal address: {optimal_addr}")
        # Keep the host running
        await trio.sleep_forever()


# Run the async function
trio.run(main)

For development or testing purposes only, you can use the insecure transport:

import secrets

import trio

from libp2p import (
    new_host,
)
from libp2p.crypto.secp256k1 import (
    create_new_key_pair,
)
from libp2p.security.insecure.transport import PLAINTEXT_PROTOCOL_ID, InsecureTransport
from libp2p.utils.address_validation import (
    get_available_interfaces,
    get_optimal_binding_address,
)


async def main():
    # Create a key pair for the host
    secret = secrets.token_bytes(32)
    key_pair = create_new_key_pair(secret)

    # Create an insecure transport (not recommended for production)
    insecure_transport = InsecureTransport(
        # local_key_pair: The key pair used for libp2p identity
        local_key_pair=key_pair,
        # secure_bytes_provider: Optional function to generate secure random bytes
        # (defaults to secrets.token_bytes)
        secure_bytes_provider=None,  # Use default implementation
        # peerstore: Optional peerstore to store peer IDs and public keys
        # (defaults to None)
        peerstore=None,
    )

    # Create a security options dictionary mapping protocol ID to transport
    security_options = {PLAINTEXT_PROTOCOL_ID: insecure_transport}

    # Create a host with the key pair and insecure transport
    host = new_host(key_pair=key_pair, sec_opt=security_options)

    # Configure the listening address using the new paradigm
    port = 8000
    listen_addrs = get_available_interfaces(port)
    optimal_addr = get_optimal_binding_address(port)

    # Start the host
    async with host.run(listen_addrs=listen_addrs):
        print(
            "libp2p has started with insecure transport "
            "(not recommended for production)"
        )
        print("libp2p is listening on:", host.get_addrs())
        print(f"Optimal address: {optimal_addr}")
        # Keep the host running
        await trio.sleep_forever()


# Run the async function
trio.run(main)

Multiplexing

Multiplexers are a required component of libp2p connections. They enable multiple logical streams to be carried over a single connection, which is essential for efficient peer-to-peer communication. The multiplexer layer is a mandatory part of the connection upgrade process, alongside the transport and security layers.

Adding a multiplexer to your configuration allows libp2p to run several of its internal protocols, like Identify, as well as enables your application to run any number of protocols over a single connection.

For Python, we can use mplex as our multiplexer:

import secrets

import trio

from libp2p import (
    new_host,
)
from libp2p.crypto.secp256k1 import (
    create_new_key_pair,
)
from libp2p.crypto.x25519 import create_new_key_pair as create_new_x25519_key_pair
from libp2p.security.noise.transport import (
    PROTOCOL_ID as NOISE_PROTOCOL_ID,
    Transport as NoiseTransport,
)
from libp2p.utils.address_validation import (
    get_available_interfaces,
    get_optimal_binding_address,
)


async def main():
    # Create a key pair for the host
    secret = secrets.token_bytes(32)
    key_pair = create_new_key_pair(secret)
    noise_key_pair = create_new_x25519_key_pair()

    # Create a Noise security transport
    noise_transport = NoiseTransport(
        # libp2p_keypair: libp2p identity (distinct from the Noise static key per spec)
        libp2p_keypair=key_pair,
        # noise_privkey: X25519 static key for Noise DH (libp2p Noise spec)
        noise_privkey=noise_key_pair.private_key,
        # early_data: Optional data to send during the handshake
        # (None means no early data)
        early_data=None,
    )

    # Create a security options dictionary mapping protocol ID to transport
    security_options = {NOISE_PROTOCOL_ID: noise_transport}

    # Create a host with the key pair, Noise security, and mplex multiplexer
    host = new_host(key_pair=key_pair, sec_opt=security_options)

    # Configure the listening address using the new paradigm
    port = 8000
    listen_addrs = get_available_interfaces(port)
    optimal_addr = get_optimal_binding_address(port)

    # Start the host
    async with host.run(listen_addrs=listen_addrs):
        print("libp2p has started with Noise encryption and mplex multiplexing")
        print("libp2p is listening on:", host.get_addrs())
        print(f"Optimal address: {optimal_addr}")
        # Keep the host running
        await trio.sleep_forever()


# Run the async function
trio.run(main)

Running Libp2p

Now that you have configured a Transport, Crypto and Stream Multiplexer module, you can start your libp2p node:

import secrets

import trio

from libp2p import (
    new_host,
)
from libp2p.crypto.secp256k1 import (
    create_new_key_pair,
)
from libp2p.crypto.x25519 import create_new_key_pair as create_new_x25519_key_pair
from libp2p.security.noise.transport import (
    PROTOCOL_ID as NOISE_PROTOCOL_ID,
    Transport as NoiseTransport,
)
from libp2p.utils.address_validation import (
    get_available_interfaces,
    get_optimal_binding_address,
)


async def main():
    # Create a key pair for the host
    secret = secrets.token_bytes(32)
    key_pair = create_new_key_pair(secret)
    noise_key_pair = create_new_x25519_key_pair()

    # Create a Noise security transport
    noise_transport = NoiseTransport(
        # libp2p_keypair: libp2p identity (distinct from the Noise static key per spec)
        libp2p_keypair=key_pair,
        # noise_privkey: X25519 static key for Noise DH (libp2p Noise spec)
        noise_privkey=noise_key_pair.private_key,
        # early_data: Optional data to send during the handshake
        # (None means no early data)
        early_data=None,
    )

    # Create a security options dictionary mapping protocol ID to transport
    security_options = {NOISE_PROTOCOL_ID: noise_transport}

    # Create a host with the key pair, Noise security, and mplex multiplexer
    host = new_host(key_pair=key_pair, sec_opt=security_options)

    # Configure the listening address using the new paradigm
    port = 8000
    listen_addrs = get_available_interfaces(port)
    optimal_addr = get_optimal_binding_address(port)

    # Start the host
    async with host.run(listen_addrs=listen_addrs):
        print("libp2p has started")
        print("libp2p is listening on:", host.get_addrs())
        print(f"Optimal address: {optimal_addr}")
        # Keep the host running
        await trio.sleep_forever()


# Run the async function
trio.run(main)

Bind address and IPv6

Default listen addresses are controlled by environment variables. Use LIBP2P_BIND for IPv4 (default 127.0.0.1) and LIBP2P_BIND_V6 for IPv6 (default ::1). Invalid values fall back to these secure defaults.

IPv4 (LIBP2P_BIND):

# Listen on all IPv4 interfaces (e.g. for tests)
export LIBP2P_BIND=0.0.0.0
python your_script.py

IPv6 (LIBP2P_BIND_V6):

# Use default IPv6 loopback (::1)
python your_script.py

# Listen on all IPv6 interfaces (e.g. for tests or dual-stack)
export LIBP2P_BIND_V6=::
python your_script.py

# Custom IPv6 address
export LIBP2P_BIND_V6=fd00::1
python your_script.py

Multiaddr formats for IPv6 include /ip6/::1/tcp/PORT and /ip6/::1/tcp/PORT/ws for WebSocket.

Resource Management

py-libp2p enables resource protection by default. When you create a host with new_host() (or a network service with new_swarm()), a default ResourceManager is created automatically if you do not provide one. This guards connections and streams out of the box.

If you want to customize limits, construct and pass your own manager:

from libp2p import new_host
from libp2p.rcmgr import ResourceLimits, new_resource_manager

limits = ResourceLimits(max_connections=512, max_streams=4096, max_memory_mb=256)
rm = new_resource_manager(limits=limits)
host = new_host(resource_manager=rm)

Custom Setup

Once your libp2p node is running, it is time to get it connected to the public network. We can do this via peer discovery.

Peer Discovery

Peer discovery is an important part of creating a well connected libp2p node. A static list of peers will often be used to join the network, but it’s useful to couple other discovery mechanisms to ensure you’re able to discover other peers that are important to your application.

For Python, you can use the bootstrap list to connect to known peers:

import secrets

from multiaddr import Multiaddr
import trio

from libp2p import (
    new_host,
)
from libp2p.crypto.secp256k1 import (
    create_new_key_pair,
)
from libp2p.crypto.x25519 import create_new_key_pair as create_new_x25519_key_pair
from libp2p.peer.peerinfo import (
    info_from_p2p_addr,
)
from libp2p.security.noise.transport import (
    PROTOCOL_ID as NOISE_PROTOCOL_ID,
    Transport as NoiseTransport,
)
from libp2p.utils.address_validation import (
    get_available_interfaces,
    get_optimal_binding_address,
)


async def main():
    # Create a key pair for the host
    secret = secrets.token_bytes(32)
    key_pair = create_new_key_pair(secret)
    noise_key_pair = create_new_x25519_key_pair()

    # Create a Noise security transport
    noise_transport = NoiseTransport(
        # libp2p_keypair: libp2p identity (distinct from the Noise static key per spec)
        libp2p_keypair=key_pair,
        # noise_privkey: X25519 static key for Noise DH (libp2p Noise spec)
        noise_privkey=noise_key_pair.private_key,
        # early_data: Optional data to send during the handshake
        # (None means no early data)
        early_data=None,
    )

    # Create a security options dictionary mapping protocol ID to transport
    security_options = {NOISE_PROTOCOL_ID: noise_transport}

    # Create a host with the key pair, Noise security, and mplex multiplexer
    host = new_host(key_pair=key_pair, sec_opt=security_options)

    # Configure the listening address using the new paradigm
    port = 8000
    listen_addrs = get_available_interfaces(port)
    optimal_addr = get_optimal_binding_address(port)

    # Start the host
    async with host.run(listen_addrs=listen_addrs):
        print("libp2p has started")
        print("libp2p is listening on:", host.get_addrs())
        print(f"Optimal address: {optimal_addr}")

        # Connect to bootstrap peers manually
        bootstrap_list = [
            "/dnsaddr/bootstrap.libp2p.io/p2p/"
            + "QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb",
            "/dnsaddr/bootstrap.libp2p.io/p2p/"
            + "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
        ]

        for addr in bootstrap_list:
            try:
                peer_info = info_from_p2p_addr(Multiaddr(addr))
                await host.connect(peer_info)
                print(f"Connected to {peer_info.peer_id.to_string()}")
            except Exception as e:
                print(f"Failed to connect to {addr}: {e}")

        # Keep the host running
        await trio.sleep_forever()


# Run the async function
trio.run(main)

Bootstrap address formats

Bootstrap discovery supports both IP and DNS multiaddrs. You can use:

  • IP addresses: /ip4/127.0.0.1/tcp/4001/p2p/PEER_ID or /ip6/::1/tcp/4001/p2p/PEER_ID

  • DNS (any): /dns/bootstrap.example.com/tcp/4001/p2p/PEER_ID

  • DNS IPv4-only: /dns4/bootstrap.example.com/tcp/4001/p2p/PEER_ID

  • DNS IPv6-only: /dns6/bootstrap.example.com/tcp/4001/p2p/PEER_ID

  • DNS multiaddr resolution (dnsaddr): /dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN

If a DNS bootstrap address fails to resolve or returns no results, the bootstrap module logs a warning and continues with the next address. See libp2p.discovery.bootstrap package for API details.

Debugging

When running libp2p you may want to see what things are happening behind the scenes. You can enable debug logging using the LIBP2P_DEBUG environment variable. This allows for fine-grained control over which modules log at which levels.

Basic Usage

To enable debug logging for all modules:

# Method 1: Using export (persists for the shell session)
# On Unix-like systems (Linux, macOS):
export LIBP2P_DEBUG=DEBUG
# On Windows (Command Prompt):
set LIBP2P_DEBUG=DEBUG
# On Windows (PowerShell):
$env:LIBP2P_DEBUG="DEBUG"

# Method 2: Setting for a single command
# On Unix-like systems:
LIBP2P_DEBUG=DEBUG python your_script.py
# On Windows (Command Prompt):
set LIBP2P_DEBUG=DEBUG && python your_script.py
# On Windows (PowerShell):
$env:LIBP2P_DEBUG="DEBUG"; python your_script.py

# Method 3: Redirect logs to /dev/null to suppress console output
# On Unix-like systems:
LIBP2P_DEBUG=DEBUG python your_script.py 2>/dev/null
# On Windows (Command Prompt):
set LIBP2P_DEBUG=DEBUG && python your_script.py >nul 2>&1
# On Windows (PowerShell):
$env:LIBP2P_DEBUG="DEBUG"; python your_script.py *> $null

To enable debug logging for specific modules:

# Debug logging for identity module only
# On Unix-like systems:
export LIBP2P_DEBUG=identity.identify:DEBUG
# On Windows (Command Prompt):
set LIBP2P_DEBUG=identity.identify:DEBUG
# On Windows (PowerShell):
$env:LIBP2P_DEBUG="identity.identify:DEBUG"

# Multiple modules with different levels
# On Unix-like systems:
export LIBP2P_DEBUG=identity.identify:DEBUG,transport:INFO
# On Windows (Command Prompt):
set LIBP2P_DEBUG=identity.identify:DEBUG,transport:INFO
# On Windows (PowerShell):
$env:LIBP2P_DEBUG="identity.identify:DEBUG,transport:INFO"

Log Files

By default, logs are written to a file in the system’s temporary directory with a timestamp and unique identifier. The file path is printed to stderr when logging starts.

When no custom log file is specified:
  • Logs are written to both a default file and to stderr (console output)

  • The default file path is printed to stderr when logging starts

When a custom log file is specified:
  • Logs are written only to the specified file

  • No console output is generated

To specify a custom log file location:

# Method 1: Using export (persists for the shell session)
# On Unix-like systems:
export LIBP2P_DEBUG_FILE=/path/to/your/logfile.log
# On Windows (Command Prompt):
set LIBP2P_DEBUG_FILE=C:\path\to\your\logfile.log
# On Windows (PowerShell):
$env:LIBP2P_DEBUG_FILE="C:\path\to\your\logfile.log"

# Method 2: Setting for a single command
# On Unix-like systems:
LIBP2P_DEBUG=DEBUG LIBP2P_DEBUG_FILE=/path/to/your/logfile.log python your_script.py
# On Windows (Command Prompt):
set LIBP2P_DEBUG=DEBUG && set LIBP2P_DEBUG_FILE=C:\path\to\your\logfile.log && python your_script.py
# On Windows (PowerShell):
$env:LIBP2P_DEBUG="DEBUG"; $env:LIBP2P_DEBUG_FILE="C:\path\to\your\logfile.log"; python your_script.py

Log Format

Log messages follow this format:

timestamp - module_name - level - message

Example output:

2024-01-01 12:00:00,123 - libp2p.identity.identify - DEBUG - Starting identify protocol

What’s Next

There are a lot of other concepts within libp2p that are not covered in this guide. For additional configuration options and examples, check out the Examples guide. If you have any problems getting started, or if anything isn’t clear, please let us know by submitting an issue!