# rust-libp2p — Repo Map (Rank 2 candidate)

Source: `docs/research/inspiration/repos/libp2p__rust-libp2p/` (~8.2 MB).
Path prefix omitted below — all paths are relative to that root.
PR markdown: `docs/research/inspiration/repos/libp2p__rust-libp2p__pull-3964.md`.
libp2p meta crate version pinned in tree: `0.57.0` (`libp2p/Cargo.toml:6`). libp2p-quic `0.14.0`.

## Brief refs: [^10][^11][^12][^13][^14][^15][^16]

Cited stack: modular Swarm (`swarm/`), DCUtR hole punch ~60% direct (`protocols/dcutr/`), Kademlia DHT (`protocols/kad/`), gossipsub (`protocols/gossipsub/`), QUIC HP via PR #3964 (`transports/quic/src/hole_punching.rs`), Protocol Labs bootstrap nodes configurable per peer (no hardcoded list in core crate — examples only).

## Workspace layout

Root `Cargo.toml` declares a workspace. Top-level dirs:

- `libp2p/` — meta-crate re-export + `SwarmBuilder` DSL (`libp2p/src/lib.rs`, `libp2p/src/builder.rs`, `libp2p/src/builder/phase/*.rs`).
- `core/` — `Transport`, `Multiaddr`, `PeerId`, upgrade pipeline (`libp2p-core`).
- `swarm/` — `Swarm`, `NetworkBehaviour`, `ConnectionHandler`, `StreamProtocol` (`libp2p-swarm`); derive in `swarm-derive/`.
- `identity/` — `Keypair`, `PeerId`, ed25519/secp256k1/ecdsa/rsa (`libp2p-identity`).
- `transports/` — `tcp/ quic/ websocket/ webrtc/ webrtc-websys/ webtransport-websys/ dns/ uds/ noise/ tls/ plaintext/ pnet/`.
- `muxers/` — `yamux/ mplex/`.
- `protocols/` — `autonat/ autonatv2/ dcutr/ floodsub/ gossipsub/ identify/ kad/ mdns/ ping/ relay/ rendezvous/ request-response/ stream/ upnp/ perf/`.
- `misc/` — helpers (`allow-block-list`, `connection-limits`, `memory-connection-limits`, `metrics`, `quick-protobuf-codec`, ...).
- `examples/` — 17 runnable demos (see below).
- `interop-tests/`, `hole-punching-tests/`, `wasm-tests/` — black-box test harnesses.

## Integration points for SPT

### Transport: QUIC
- Crate `libp2p-quic`, dir `transports/quic/`.
- Entry: `transports/quic/src/lib.rs:71-76` — `pub use config::Config; pub use transport::GenTransport; pub use provider::tokio` (cfg tokio).
- Builder DSL: `libp2p/src/builder/phase/quic.rs:23` `with_quic()` (zero-arg) and `:32` `with_quic_config(|cfg| cfg)` to mutate `libp2p_quic::Config`.
- Listen addr form: `/ip4/0.0.0.0/udp/0/quic-v1` (see `examples/chat/src/main.rs:98`, `examples/dcutr/src/main.rs:112`).
- Single feature flag on the meta crate: `quic = ["dep:libp2p-quic"]` (`libp2p/Cargo.toml:72`). Runtime flag on the sub-crate: `tokio = [...]` (`transports/quic/Cargo.toml:28`); no async-std (removed in 0.13.0, `transports/quic/CHANGELOG.md:8`).
- Backed by `quinn 0.11.9` + `rustls 0.23` (`transports/quic/Cargo.toml:18-20`). Built-in TLS via `libp2p-tls`; QUIC connections are *not* upgraded (no separate Noise/yamux) — note in `transports/quic/src/lib.rs:57-59`.

### NAT traversal: DCUtR
- Crate `libp2p-dcutr`, dir `protocols/dcutr/`.
- Public API: `protocols/dcutr/src/lib.rs:36` `pub use behaviour::{Behaviour, Error, Event}; pub use protocol::PROTOCOL_NAME;`.
- Constructor: `protocols/dcutr/src/behaviour.rs:88` `Behaviour::new(local_peer_id: PeerId)`.
- Wire name: `protocols/dcutr/src/protocol.rs:25` `PROTOCOL_NAME: StreamProtocol = StreamProtocol::new("/libp2p/dcutr")`.
- Hard-coded retry cap: `protocols/dcutr/src/behaviour.rs:46` `MAX_NUMBER_OF_UPGRADE_ATTEMPTS: u8 = 3`.
- Feature flag: `dcutr = ["dep:libp2p-dcutr", "libp2p-metrics?/dcutr"]` (`libp2p/Cargo.toml:55`).
- Requires a relay: must compose with `libp2p-relay` client behaviour + `libp2p-identify`. Reference composition: `examples/dcutr/src/main.rs:81-109` (`relay_client + ping + identify + dcutr`, transport stack TCP+yamux+noise, then `.with_quic().with_dns().with_relay_client(...)`).

### Hole punching (QUIC) — PR #3964

**Summary (from `libp2p__rust-libp2p__pull-3964.md`).**
- Author `arpankapoor`, reviewed by `mxinden` + `thomaseizinger` + `kpp`. Merged into `libp2p-quic`; `libp2p-quic` is *not* part of the `libp2p` meta release, so it ships independently.
- Approach: when DCUtR coordinates a sim-connect for QUIC, the listener-side calls `Transport::dial_as_listener` which (a) starts a "hole puncher" task spraying random 64-byte UDP packets to the remote `SocketAddr` every 10–200 ms to open the NAT mapping (`hole_punching.rs:26-43`), and (b) waits for an *inbound* QUIC connection from that same `SocketAddr`. The inbound is matched on address (not PeerId — too late in the handshake) and then handed back to the dialer side via a `oneshot::channel`. PeerId is verified post-hoc and a `tracing::warn!` is emitted on mismatch (`transport.rs:353-360`).
- Why random packets vs. simultaneous-open: QUIC's TLS handshake fixes client/server roles, so true sim-open requires QUIC-stack support. Random UDP punches the NAT then ride the natural inbound — simpler (`pull-3964.md:330-345`).
- DPI risk acknowledged: QUIC payload is encrypted, so DPI has little surface; `mxinden` notes future work could spoof unencrypted bits (`pull-3964.md:399-401`).
- Sister-PR dependency: PR #3954 changed `identify` from auto-adding observed addresses to emitting `NewExternalAddrCandidate`; relay servers must now call `swarm.add_external_address(observed_addr)` explicitly on `identify::Event::Received` (`pull-3964.md:225-244, 253-256`). Visible in `examples/relay-server/src/main.rs:88-95`.

**Now-in-main files (after merge):**
- `transports/quic/src/hole_punching.rs:12` `hole_puncher::<P>(socket, remote_addr, timeout) -> Error` — async loop of random UDP sends.
- `transports/quic/src/hole_punching.rs:26` `punch_holes::<P>(socket, remote_addr)` — inner `loop` sending 64 bytes then sleeping 10–200 ms.
- `transports/quic/src/transport.rs:54` `use hole_punching::hole_puncher`.
- `transports/quic/src/transport.rs:84` field `hole_punch_attempts: HashMap<SocketAddr, oneshot::Sender<Connecting>>` on `GenTransport`.
- `transports/quic/src/transport.rs:311-366` — `dial` `(Endpoint::Listener, _)` arm: spawns `hole_puncher`, races receiver vs hole-puncher future via `futures::future::select`.
- `transports/quic/src/transport.rs:387-394` — `poll()` intercepts `TransportEvent::Incoming`, matches `socket_addr` against pending hole-punch sender, and routes the upgrade into the oneshot.
- Error variant: `transports/quic/src/lib.rs:103` `Error::HolePunchInProgress(SocketAddr)`.
- Provider trait abstraction for the UDP send/sleep: `transports/quic/src/provider.rs` (`P::send_to`, `P::sleep`); tokio impl in `transports/quic/src/provider/tokio.rs`.

### Kademlia DHT (private namespace)

- Crate `libp2p-kad`, dir `protocols/kad/`.
- Public API: `protocols/kad/src/lib.rs:60-103` re-exports `Behaviour`, `Config`, `Event`, `QueryId`, `Record`, `RecordKey`, `store`; `PROTOCOL_NAME = StreamProtocol::new("/ipfs/kad/1.0.0")` constant.
- **Private DHT namespace**: change the wire name. Two constructor paths:
  - `Behaviour::new(id, store)` — `protocols/kad/src/behaviour.rs:465` — uses default `/ipfs/kad/1.0.0`.
  - `Behaviour::with_config(id, store, Config)` — `protocols/kad/src/behaviour.rs:475`.
  - `Config::new(protocol_name: StreamProtocol)` — `protocols/kad/src/behaviour.rs:222`. Pass e.g. `StreamProtocol::new("/spt/kad/1.0.0")` to fork the network namespace; only peers using the same `StreamProtocol` will gossip.
- Live example: `examples/ipfs-kad/src/main.rs:48` `IPFS_PROTO_NAME: StreamProtocol = StreamProtocol::new("/ipfs/kad/1.0.0");` then `kad::Config::new(IPFS_PROTO_NAME)` at `:69`.
- Routing-table tuning (`protocols/kad/src/behaviour.rs`): `set_query_timeout:246`, `disjoint_query_paths:285`, `set_record_filtering:308`, `set_replication_interval:330`, `set_publication_interval:348`, `set_kbucket_inserts:394`, `set_caching:405`.
- Bootstrap fanout (`protocols/kad/src/lib.rs:91-101`): `K_VALUE = 20`, `ALPHA_VALUE = 3`.
- Add a known peer + addr: `Behaviour::add_address(&mut, &PeerId, Multiaddr) -> RoutingUpdate` (`protocols/kad/src/behaviour.rs:581`).
- Manual bootstrap kick: `Behaviour::bootstrap() -> Result<QueryId, NoKnownPeers>` (`protocols/kad/src/behaviour.rs:984`). Auto bootstrap throttle: `protocols/kad/src/bootstrap.rs:10` `DEFAULT_AUTOMATIC_THROTTLE = 500 ms`.
- Queries: `get_closest_peers:727`, `get_record:797`, `put_record:867`, `put_record_to:907`.
- Record store: `kad::store::MemoryStore::new(peer_id)` — see `examples/ipfs-kad/src/main.rs:71`. Disk store available in `record/store/` module.
- Cargo features: `protocols/kad/Cargo.toml:46` — only `serde` (opt-in). Always compiled when `libp2p` `kad` feature is on (`libp2p/Cargo.toml:63`).

### Gossipsub messaging

- Crate `libp2p-gossipsub`, dir `protocols/gossipsub/`.
- Constructors (`protocols/gossipsub/src/behaviour.rs`): `Behaviour::new(privacy: MessageAuthenticity, Config) -> Result<Self, &'static str>` `:383`; variants `new_with_subscription_filter:400`, `new_with_transform:422`, `new_with_subscription_filter_and_transform:444`.
- `MessageAuthenticity::Signed(keypair)` for SPT (vs `Anonymous`/`Author`/`RandomAuthor`). See `examples/chat/src/main.rs:78-80`.
- Topic API: `gossipsub::IdentTopic::new("spt-bus")`; `Behaviour::subscribe(&Topic):550`, `publish(topic, bytes):643`, `add_explicit_peer(&PeerId):1035`, `set_topic_params:1100`.
- Config builder (`protocols/gossipsub/src/config.rs`): `ConfigBuilder::default()` + `.heartbeat_interval(Duration):753`, `.max_transmit_size(usize):780`, `.validation_mode(ValidationMode)`, `.message_id_fn(fn)`, `.build():1092`.
- **Private network namespace**: `ConfigBuilder::protocol_id_prefix("/spt/meshsub")` `:574` — installs both `/spt/meshsub/1.1.0` (Gossipsubv1.1) and `/spt/meshsub/1.0.0`. Or `protocol_id(full, Version):605` for a single exact id.
- Validation mode default `Strict` requires signed messages with valid PeerId (`config.rs:34-54`).
- Cargo features (`protocols/gossipsub/Cargo.toml:13-15`): `metrics` (prometheus-client), `partial_messages`.

### Bootstrap node config

- **The library ships zero hardcoded bootstrap nodes.** The famous IPFS bootnodes appear only as example constants:
  - `examples/ipfs-kad/src/main.rs:41-46` `BOOTNODES: [&str; 4] = ["QmNn...", ...]` paired with `"/dnsaddr/bootstrap.libp2p.io"`.
  - Wiring loop: `examples/ipfs-kad/src/main.rs:79-83` `swarm.behaviour_mut().add_address(&peer.parse()?, "/dnsaddr/bootstrap.libp2p.io".parse()?)`.
- For SPT: just call `kad::Behaviour::add_address(my_peer_id, my_multiaddr)` per known seed and `bootstrap()` (`protocols/kad/src/behaviour.rs:984`). DNS resolution of `/dnsaddr/` requires the `libp2p-dns` transport wrapper — see `.with_dns()` builder phase (`examples/ipfs-kad/src/main.rs:66`).
- Relay reservation for DCUtR uses `Multiaddr::with(Protocol::P2pCircuit)` (`examples/dcutr/src/main.rs:179-186`); seed relay addr is fully user-supplied via CLI (`--relay-address`).

### Identity (PeerId, Keypair)

- Crate `libp2p-identity`, dir `identity/`.
- Key types: `identity/src/keypair.rs:79` `pub struct Keypair`; `identity/src/peer_id.rs:50` `pub struct PeerId(Multihash<64>)`.
- Constructors: `Keypair::generate_ed25519():103`, `generate_secp256k1():111`, `from_protobuf_encoding(&[u8]):262`, `ed25519_from_bytes(&[u8; 32])` (used in `examples/dcutr/src/main.rs:231` for deterministic test ids).
- `Keypair::public() -> PublicKey:193`, `Keypair::to_peer_id() -> PeerId:636`.
- Feature flags on this crate: `ed25519` (default-ish), `ecdsa`, `secp256k1`, `rsa`, `rand`, `serde`. SPT only needs `ed25519` — drop the rest. Toggled via `libp2p/Cargo.toml:57-59,76-77`.
- `PeerId` is a SHA-256 multihash of the protobuf-encoded public key, with inline-multihash optimisation for keys ≤42 bytes (`identity/src/peer_id.rs:41-44`).

## Examples worth copying

All under `examples/<name>/src/main.rs`:

- `ping` — minimal Swarm + TCP + Noise + yamux + `ping::Behaviour`; the official "hello world" (and target of the in-source tutorial `libp2p/src/tutorials/ping.rs`).
- `chat` — gossipsub + mDNS over TCP+QUIC; subscribe one topic, publish stdin lines. Closest analogue to an SPT bus.
- `dcutr` — TCP+QUIC+DNS+relay-client+identify+dcutr+ping — the canonical hole-punching demo. CLI selects `--mode client-listen` or `client-dial` against `--relay-address`. Reference for relay-+-DCUtR wiring (`examples/dcutr/src/main.rs:81-189`).
- `relay-server` — minimal relay+identify+ping listening on both TCP and `/quic-v1`. Pattern for an SPT super-perch (`examples/relay-server/src/main.rs:50-83`).
- `ipfs-kad` — Kademlia client speaking `/ipfs/kad/1.0.0` against the public IPFS DHT; `GetClosestPeers` + `PutPkRecord` subcommands. Direct template for a private-namespace DHT.
- `ipfs-private` — TCP + custom pnet (PSK from `~/.ipfs/swarm.key`) + gossipsub + identify + ping. Template for *symmetric-key* private overlays (alt to changing protocol IDs).
- `distributed-key-value-store` — Kademlia + mDNS chat-style kv get/put.
- `file-sharing` — Kademlia + request-response; doubles as the "embedding libp2p in a larger app" tutorial (per `examples/README.md:31-34`).
- `rendezvous` — peer discovery via rendezvous protocol when DHT/mDNS are unavailable.
- `autonat`, `autonatv2` — NAT class detection (cone vs symmetric).
- `identify` — bare `/ipfs/id/1.0.0` exchange.
- `upnp` — UPnP IGD port mapping.
- `metrics` — Prometheus scrape endpoint.
- `stream` — raw byte stream over libp2p (`libp2p-stream`).
- `browser-webrtc` — WASM client; less relevant for SPT.

## Binary size knobs

- **Default has no transports/protocols enabled.** `libp2p/Cargo.toml:14-51` defines a `full` feature that turns *everything* on; every protocol and transport is `optional = true` (`:90-134`).
- Per-protocol opt-in: `libp2p/Cargo.toml:53-88` — each capability is its own gate, e.g. `quic`, `dcutr`, `kad`, `gossipsub`, `tcp`, `noise`, `yamux`, `dns`, `relay`, `identify`, `tls`, `tokio`, `macros`.
- Recipe for SPT (QUIC + DCUtR + Kad + gossipsub + relay client, tokio runtime):
  ```toml
  [dependencies]
  libp2p = { version = "0.57", default-features = false, features = [
      "tokio", "macros",
      "quic", "tls", "noise", "yamux", "tcp", "dns",
      "identify", "ping",
      "dcutr", "relay",
      "kad", "gossipsub",
  ] }
  libp2p-identity = { version = "...", default-features = false, features = ["ed25519", "rand"] }
  ```
  Drop `tcp` if you commit to QUIC-only (saves yamux+noise too). Drop `rsa`/`ecdsa`/`secp256k1` from `libp2p-identity` (only `ed25519` is needed for `generate_ed25519`).
- Sub-crate runtime gate: `transports/quic/Cargo.toml:28` `tokio = [...]`; pick one runtime (no async-std since `0.13.0`).
- `libp2p-gossipsub` `metrics` and `partial_messages` features are opt-in (`protocols/gossipsub/Cargo.toml:13-15`).
- `serde` feature on `libp2p`, `libp2p-kad`, `libp2p-gossipsub`, `libp2p-core`, `libp2p-identity` is opt-in and pulls in proto encoding deps; skip unless persisting types.
- `mdns` and `upnp` add OS-specific deps (LAN multicast, IGD); omit if not used.
- `quinn 0.11` is a substantial transitive (includes `rustls 0.23` + `ring`). Cannot be avoided if `quic` enabled.
- Workspace uses `[lints] workspace = true` everywhere; check the root `Cargo.toml` `[profile.release]` for size opts before linking (LTO/strip not set in repo default — caller's responsibility).

## Gotchas

- **`libp2p-quic` is *not* part of the `libp2p` meta-crate release line.** It ships its own version (`0.14.0` vs meta `0.57.0`) and breaking changes don't block meta releases (`pull-3964.md:65-71`). SPT must pin both.
- QUIC connections are **never `upgrade()`'d** with Noise/yamux — compile error if you try. QUIC supplies security + muxing natively (`transports/quic/src/lib.rs:57-59`). Don't share the upgrade pipeline between TCP and QUIC.
- DCUtR after PR #3954: relay servers must **explicitly call `swarm.add_external_address(observed_addr)`** on `identify::Event::Received` or `NoAddressesInReservation` errors when clients try to reserve (`examples/relay-server/src/main.rs:88-95`, root cause discussion `pull-3964.md:225-256`).
- DCUtR `MAX_NUMBER_OF_UPGRADE_ATTEMPTS = 3` is a hard constant, not configurable (`protocols/dcutr/src/behaviour.rs:46`). Cap study before changing.
- Gossipsub default `ValidationMode::Strict` will silently drop messages from `MessageAuthenticity::Anonymous` peers (`protocols/gossipsub/src/config.rs:36-42`).
- `kad::Behaviour::add_address` must be called for *each* bootstrap entry *before* `bootstrap()` or you get `NoKnownPeers` (`protocols/kad/src/behaviour.rs:984`).
- `BOOTNODES` constants in examples are **example-only** and use `/dnsaddr/`, which requires `.with_dns()` in the builder phase or the multiaddr won't resolve.
- `multiaddr` strip-peerid quirk: `rust-libp2p`'s `Transport::dial` doesn't take the PeerId, so a trailing `/p2p/<peer>` must be popped before dialing some legacy addresses (`examples/ipfs-private/src/main.rs:63-77`). Pattern shows up when interoperating with go-libp2p tooling.
- Hole-punch inbound matching is **by `SocketAddr` only**, not PeerId — a mismatch is logged at `warn` level and the connection is still returned (`transports/quic/src/transport.rs:353-360`). Defensive PeerId check is the caller's job.
- `Config::support_draft_29` deprecated as of `libp2p-quic 0.13.0` (`transports/quic/CHANGELOG.md:11`).
- Removed `async-std`: tokio is the only supported runtime for `libp2p-quic` ≥ 0.13 (`CHANGELOG.md:8`).

## Tests to study

- `transports/quic/tests/smoke.rs` — basic listen/dial; runtime `tokio` feature gate.
- `transports/quic/tests/stream_compliance.rs` — required-features = `tokio` (`transports/quic/Cargo.toml:45-47`).
- `protocols/dcutr/tests/lib.rs` — relay + dcutr two-node hole-punch with `libp2p-swarm-test` harness; the canonical reference for wiring relay-client + identify + dcutr in tests.
- `protocols/kad/tests/client_mode.rs` — client/server-mode interop; demonstrates `set_mode` toggling.
- `protocols/kad/src/behaviour/test.rs` — in-tree large test module; bootstrap/query mechanics.
- `protocols/gossipsub/tests/smoke.rs` — minimal pub/sub two-peer mesh setup.
- `hole-punching-tests/` — black-box runner exercising the full DCUtR+QUIC stack across containers; useful for SPT integration tests once a relay is operational.
- `interop-tests/` — cross-impl interop against go-libp2p; only relevant if SPT ever needs go interop.
