erigontech / erigontech/erigon

cl: Caplin has no QUIC transport — TCP-only libp2p host, QUIC bootnodes and ENRs ignored

Open
#23,398 1 comment 0 reactions 0 assignees View on GitHub
Caplin Networking
Dominant language
Go
Stars
3.6k
Forks
1.5k
Avg merge
1d 18h
Merged PRs (30d)
432

Description

Caplin's libp2p host is TCP-only. Every other major CL (Lighthouse, Prysm, Teku, Nimbus) supports QUIC and advertises the `quic` ENR key, so Caplin is the only client in the network that never gets QUIC's stream multiplexing, faster handshake, or NAT traversal. QUIC-only peers are unreachable, and QUIC bootnodes are dropped outright.

Confirmed on `main`, `release/3.6` and `release/3.5`.

## Current state

The host declares exactly one transport, so go-libp2p's defaults (which include QUIC) never apply:

```go
// cl/p2p/config.go
options := []libp2p.Option{
privKeyOption(privateKey),
libp2p.ListenAddrs(listen), // /ip4//tcp/ only
libp2p.UserAgent("erigon/caplin/" + version.NodeVersion()),
libp2p.Transport(tcp.NewTCPTransport),
...
}
```

`go-libp2p/p2p/transport/quic` is imported nowhere. `quic-go` is in the module graph only via `db/downloader` → `quic-go/http3` (torrent), not libp2p.

TCP is assumed at each layer below that too:

| Layer | Behaviour |
|---|---|
| Listen addr | `multiAddressBuilder` (`cl/p2p/config.go`) hardcodes `/tcp/` |
| ENR published | `setupENR` sets eth2/attnets/syncnets/cgc/nfd; `tcp`+`udp` come from the discv5 local node. No `quic` key, although `p2p/enr/entries.go` defines `QUIC`/`QUIC6` for devp2p |
| Discovered peers | `ConvertToSingleMultiAddr` errors out when `node.TCP() == 0`, so QUIC-only ENRs are skipped |
| Static peers | `ParseStaticPeer` requires `P_TCP`: *"libp2p static peer must use a direct TCP address"* |
| Bootstrap nodes | `ParseBootstrapNodes` bins anything non-TCP into `unsupportedPeers` |

## Observable today

The Gnosis bootnode list in `cl/clparams/config.go` contains a QUIC entry:

```
/ip4/51.68.224.153/udp/9001/quic-v1/p2p/16Uiu2HAkxcBE3LK7zhnyZERguonkKmXLgYPRcuDPaF6C2vaigYuT
```

Caplin logs it at startup and moves on:

```
WARN Ignoring unsupported Consensus bootstrap node bootnode=/ip4/51.68.224.153/udp/9001/quic-v1/...
```

The remaining Gnosis bootnodes are TCP, so this costs one bootnode rather than connectivity. Likewise the Hoodi bootnode ENRs carry a `quic` key that Caplin ignores in favour of the peer's TCP port. Since the spec keeps TCP mandatory and QUIC optional, peering still works — Caplin just never uses the better path.

## Proposed change

1. Add `libp2p.Transport(libp2pquic.NewTransport)` alongside TCP and a `/udp//quic-v1` listen addr.
2. Give QUIC its own UDP port. `--caplin.discovery.port` (9000) is already discv5's; 9001 is the convention other clients use.
3. Publish the `quic` ENR key in `setupENR`.
4. Build a QUIC multiaddr in `ConvertToSingleMultiAddr` when the discovered ENR carries a `quic` entry, preferring it over TCP.
5. Relax the `P_TCP` assertions in `ParseStaticPeer` / `ParseBootstrapNodes` to accept `quic-v1`.

The connection gater needs no change — `InterceptAccept` is already the hook QUIC transports call directly.

Open questions for whoever picks this up:

- Opt-in behind a flag first (`--caplin.quic`) or on by default? Other clients shipped it opt-in, then flipped the default.
- Port choice: reuse the discv5 UDP port via multiplexing, or a separate one? Separate is simpler and matches other clients.
- Dial preference when a peer advertises both: QUIC-first with TCP fallback, or leave it to libp2p's happy-eyeballs ranking.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.