Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ jobs:
version: ${{ env.ZIG_VERSION }}

# `timeout` (coreutils) fails the step with 124 if the build or test binary never finishes.
- name: QUIC transport tests (in-repo lsquic + BoringSSL)
- name: QUIC transport tests (zquic)
run: timeout 40m zig build test-quic --summary all

large-network-rs:
Expand Down
27 changes: 13 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# zig-ethp2p

Zig helpers for the wire formats of **[ethp2p](https://github.com/ethp2p/ethp2p)** — the reference implementation is **[github.com/ethp2p/ethp2p](https://github.com/ethp2p/ethp2p)** ([specs directory](https://github.com/ethp2p/ethp2p/tree/main/specs)). This repo tracks that code and the protobuf definitions under `broadcast/pb`, `protocol/pb`, and `broadcast/rs/pb`. **QUIC** transport (TLS 1.3, ALPN `eth-ec-broadcast`, unidirectional streams) is always compiled via **lsquic + BoringSSL** (no build flags needed); see [QUIC transport](#quic-transport-lsquic-build).
Zig helpers for the wire formats of **[ethp2p](https://github.com/ethp2p/ethp2p)** — the reference implementation is **[github.com/ethp2p/ethp2p](https://github.com/ethp2p/ethp2p)** ([specs directory](https://github.com/ethp2p/ethp2p/tree/main/specs)). This repo tracks that code and the protobuf definitions under `broadcast/pb`, `protocol/pb`, and `broadcast/rs/pb`. **QUIC** transport (TLS 1.3, ALPN `eth-ec-broadcast`, unidirectional streams) is built on pure-Zig **[zquic](https://github.com/ch4r10t33r/zquic)** via `build.zig.zon` (no alternate stack); see [QUIC transport](#quic-transport-zquic).

## Implementation status (vs reference)

Expand Down Expand Up @@ -39,7 +39,7 @@ Zig helpers for the wire formats of **[ethp2p](https://github.com/ethp2p/ethp2p)
| `PartialMessagesExtension` (nested in `RPC.partial`) | libp2p `rpc.proto` field 10 body | `encodePartialMessagesExtension`, `decodePartialMessagesExtensionOwned` |
| Unsigned-varint length prefix before `RPC` body | common libp2p framing | `encodeRpcLengthPrefixed`, `decodeRpcLengthPrefixedPrefix` |
| In-process duplex for length-prefixed `RPC` (simnet-style, no TCP/QUIC) | pair of `Endpoint`s over bounded byte queues | `sim.gossipsub_rpc_host` (`Link`, `Endpoint.sendRpc` / `recvRpcOwned`) |
| QUIC transport + UNI stream alignment | [`sim/host.go`](https://github.com/ethp2p/ethp2p/blob/main/sim/host.go), `peer.go`, `peer_ctrl.go`, `peer_in.go` | `transport.eth_ec_quic`: IETF QUIC, TLS 1.3, ALPN `eth-ec-broadcast`, **unidirectional** BCAST/SESS/CHUNK streams matching `OpenUniStream`/`AcceptUniStream`; `PeerConn` + `broadcast.engine_quic` (`EngineQuicHost`) wire inbound SESS/CHUNK into `Engine` / `ChannelRs`. See [QUIC transport](#quic-transport-lsquic-build). |
| QUIC transport + UNI stream alignment | [`sim/host.go`](https://github.com/ethp2p/ethp2p/blob/main/sim/host.go), `peer.go`, `peer_ctrl.go`, `peer_in.go` | `transport.eth_ec_quic`: IETF QUIC, TLS 1.3, ALPN `eth-ec-broadcast`, **unidirectional** BCAST/SESS/CHUNK streams matching `OpenUniStream`/`AcceptUniStream`; `PeerConn` + `broadcast.engine_quic` (`EngineQuicHost`) wire inbound SESS/CHUNK into `Engine` / `ChannelRs`. See [QUIC transport](#quic-transport-zquic). |
| **Still open** | — | [Pending work](#pending-work) |

## Scope on `main` (at a glance)
Expand All @@ -52,42 +52,41 @@ This is **what is already implemented** — not the backlog. Per-module mapping
- **EC scheme id:** `layer.ec_scheme` (`EcSchemeKind`, `"reed-solomon"` wire name); only Reed–Solomon is wired end-to-end ([#14](https://github.com/ch4r10t33r/zig-ethp2p/issues/14) tracks RLNC and further schemes).
- **Abstract RS mesh:** heap-backed graphs and `PeerSessionStats` (`sim.rs_mesh`): 2-node, 4-node ring, 6-node `TestNetwork`-style topology, **partition/heal** line test, chunk-len variant; with `ZIG_ETHP2P_STRESS=1`, larger six-node budget plus **8-** and **16-node** rings.
- **Gossipsub (sim / wire helpers):** transport, protocol, broadcast, interop, `RPC` encode/decode (including **`partial`** / `PartialMessagesExtension`), full `ControlMessage`, varint length prefix, in-process **`gossipsub_rpc_host`** for tests (`sim.gossipsub_*`, `broadcast.gossip`).
- **QUIC:** `transport.eth_ec_quic` — IETF QUIC, TLS 1.3, ALPN `eth-ec-broadcast`, **unidirectional** BCAST/SESS/CHUNK streams matching the ethp2p Go reference (`peer.go` `OpenUniStream`/`AcceptUniStream`). `PeerConn` is poll-driven; `broadcast.engine_quic.EngineQuicHost` connects inbound streams to the RS channel path. See [QUIC transport](#quic-transport-lsquic-build).
- **CI:** aligned with [ethp2p's `ci.yml`](https://github.com/ethp2p/ethp2p/blob/main/.github/workflows/ci.yml): `zig build test-broadcast`, `test-sim-rs`, `test-sim-gossipsub` (Debug + TSan), `test-quic` (**`quic-transport`** job: vendored TLS, **45m** job timeout + **`timeout 40m`** on the command so a hung poll loop cannot exhaust the runner), `test-stress-ci` on **`main` only**, plus lint (`zig fmt --check`, `zig build`, `zig ast-check`). `build.zig.zon` **`minimum_zig_version`** must match workflow **`ZIG_VERSION`**; `just check-zig-ci-align` checks that locally.
- **QUIC:** `transport.eth_ec_quic` — IETF QUIC, TLS 1.3, ALPN `eth-ec-broadcast`, **unidirectional** BCAST/SESS/CHUNK streams matching the ethp2p Go reference (`peer.go` `OpenUniStream`/`AcceptUniStream`). `PeerConn` is poll-driven; `broadcast.engine_quic.EngineQuicHost` connects inbound streams to the RS channel path. See [QUIC transport](#quic-transport-zquic).
- **CI:** aligned with [ethp2p's `ci.yml`](https://github.com/ethp2p/ethp2p/blob/main/.github/workflows/ci.yml): `zig build test-broadcast`, `test-sim-rs`, `test-sim-gossipsub` (Debug + TSan), `test-quic` (**`quic-transport`** job: **45m** job timeout + **`timeout 40m`** on the command so a hung poll loop cannot exhaust the runner), `test-stress-ci` on **`main` only**, plus lint (`zig fmt --check`, `zig build`, `zig ast-check`). `build.zig.zon` **`minimum_zig_version`** must match workflow **`ZIG_VERSION`**; `just check-zig-ci-align` checks that locally.
- **One-shot local verification:** `zig build test` runs the full suite.

## QUIC transport (lsquic build)
## QUIC transport (zquic)

lsquic + BoringSSL are always compiled — no build flag is needed. **Windows** targets are not supported (lsquic_zig build is Linux/macOS focused).
**[zquic](https://github.com/ch4r10t33r/zquic)** is pulled as a Zig package dependency (`build.zig.zon`); there is no C QUIC stack in this repo. **Windows** targets are not supported (same constraint as `build.zig`).

**Layout**

| Piece | Role |
|-------|------|
| `build.zig` | Always pulls **`lsquic_zig`** (LiteSpeed **lsquic** + **BoringSSL**), exposes Zig module `quic` rooted at `src/transport/lsquic_quic_shim.zig`, links **zlib** (+ **pthread** / **m** on Unix). |
| `lsquic_quic_shim.zig` | Endpoint/connection/stream API: UDP I/O, `poll`, `connect`, `tryAccept`, `handshakeComplete`, `streamMake` (bidi), `streamMakeUni` (UNI), `tryAcceptIncomingUniStream`, stream read/write/cancel helpers. |
| `build.zig` | Resolves **`zquic`**, exposes Zig module `quic` rooted at `src/transport/zquic_quic_shim.zig` (thin wrapper over zquic I/O). |
| `zquic_quic_shim.zig` | Endpoint/connection/stream API: UDP I/O, `poll`, `connect`, `tryAccept`, `handshakeComplete`, `streamMake` (bidi), `streamMakeUni` (UNI), `tryAcceptIncomingUniStream`, stream read/write/cancel helpers. |
| `eth_ec_quic_common.zig` / `eth_ec_quic_enabled.zig` | Shared config and ALPN string; **enabled** path implements `listenImpl` / `dialImpl` and integration tests. |
| `eth_ec_quic.zig` | Public `transport.eth_ec_quic`: `listen`, `dial`, `logInit` (programmatic lsquic logger), listener wrapper. |
| `eth_ec_quic.zig` | Public `transport.eth_ec_quic`: `listen`, `dial`, `logInit` (reserved no-op for future zquic hooks), listener wrapper. |
| `eth_ec_quic_peer.zig` | `PeerConn` poll-driven state machine: `idle → handshaking → active → closed`; symmetric BCAST UNI handshake + `runAcceptLoop` dispatch by protocol selector byte. |
| `broadcast/engine_quic.zig` | `EngineQuicHost`: SESS `session_open` → `ChannelRs.attachRelaySession`, CHUNK → `relayIngestChunkVerifiedEngine` (issue #37). |
| `vendor/lsquic_zig/patch_uni.sh` | Build-time patch that removes `static` from `create_uni_stream_out` in lsquic and appends a public `lsquic_conn_make_uni_stream()` wrapper (lsquic 4.3 has no public API for outgoing UNI streams). |

**Operation**

Callers must **drive the engine**: after I/O, use **`quic.poll(endpoint, timeout_ms)`**. For `timeout_ms == 0` (tight loops), the shim advances wall clock using **`lsquic_engine_earliest_adv_tick`** (capped micro-sleep) so PTO-style timers can fire.
Callers must **drive the stack**: after scheduling work, use **`quic.poll(endpoint, timeout_ms)`** so UDP receive, TLS, and QUIC state machines run (see `zquic_quic_shim.zig`).

**Unidirectional streams**

All ethp2p application protocols use UNI streams — both peers independently open their own send stream (`peer.go` `OpenUniStream`), avoiding simultaneous-open ambiguity inherent in bidirectional streams for P2P protocols. See `UPSTREAM.md` for the full rationale.

**TLS notes**

- **Ed25519 server certs:** BoringSSL's default TLS 1.3 client handshake may omit Ed25519; the client **`SSL_CTX`** sets **`SSL_CTX_set_verify_algorithm_prefs`** so Ed25519 certificates work (otherwise TLS alert 40).
- **Test certs:** `eth_ec_quic_test_certs.zig` embeds Ed25519 material; zquic’s TLS must accept the same algorithms as the Go reference for those identities.
- **SNI:** Clients should pass a hostname that matches the server certificate (see `eth_ec_quic_test_certs.zig` for embedded test identity).

**Debugging**

Call **`quic.logInit("debug")`** (or any `lsquic_set_log_level` level) to enable lsquic's stderr logger programmatically. Alternatively, set the env var **`ZIG_ETHP2P_LSQUIC_LOG=1`** (or **`LSQUIC_LOG_LEVEL`**) before the first `listen`/`dial`; the shim picks that up on first use.
`quic.logInit` is currently a **no-op** (zquic has no global log level API wired here yet).

## Pending work

Expand All @@ -109,7 +108,7 @@ zig build test-stress # `ZIG_ETHP2P_STRESS=1` (longer RS mesh + 8-/16-node
zig build test-broadcast # CI split: wire + layer + broadcast (TSan)
zig build test-sim-rs # CI split: RS mesh (TSan)
zig build test-sim-gossipsub
zig build test-quic # lsquic + BoringSSL handshake / transport tests; CI `quic-transport`
zig build test-quic # zquic handshake / transport tests; CI `quic-transport`
zig build test-stress-ci # full suite + stress + TSan (same as `large-network-rs` on main)
```

Expand Down
17 changes: 3 additions & 14 deletions UPSTREAM.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ When updating:

## QUIC / UDP transport

`src/transport/eth_ec_quic.zig` mirrors **ALPN** `eth-ec-broadcast` and high-level **quic-go-style** limits from ethp2p `sim/host.go`. lsquic + BoringSSL are always linked via `vendor/lsquic_zig` (no build flag needed).
`src/transport/eth_ec_quic.zig` mirrors **ALPN** `eth-ec-broadcast` and high-level **quic-go-style** limits from ethp2p `sim/host.go`. The implementation uses pure-Zig **[zquic](https://github.com/ch4r10t33r/zquic)** (`build.zig.zon` dependency); there is no vendored C QUIC/TLS stack in this repository.

### Why raw QUIC and why unidirectional streams

Expand All @@ -74,21 +74,10 @@ The ethp2p reference uses **unidirectional** QUIC streams for all application pr

Zig alignment:

- `lsquic_quic_shim.zig` detects stream type via `lsquic_stream_id() & 0x2` (bit 1 = unidirectional per RFC 9000 §2.1)
- `incoming_uni_streams` queue holds peer-initiated UNI streams; `tryAcceptIncomingUniStream` pops them
- `streamMakeUni` opens an outgoing UNI stream via `lsquic_conn_make_uni_stream`
- `zquic_quic_shim.zig` classifies incoming streams by QUIC stream ID (client vs server stream numbering per RFC 9000; bidi vs uni queues) and exposes `tryAcceptIncomingUniStream` for peer-initiated UNI streams
- `streamMakeUni` opens an outgoing UNI stream via zquic
- `eth_ec_quic_peer.zig` implements the `PeerConn` poll-driven state machine (handshake + accept-loop); `broadcast/engine_quic.zig` (`EngineQuicHost`) forwards inbound SESS/CHUNK into `Engine` / `ChannelRs` (#37)

### lsquic vendor patch

lsquic 4.3 has no public API for user-initiated outgoing unidirectional streams (`lsquic_conn_make_stream` always creates bidirectional streams). The internal `create_uni_stream_out` function in `lsquic_full_conn_ietf.c` is `static`.

Fix: `vendor/lsquic_zig/build.zig` runs `vendor/lsquic_zig/patch_uni.sh` as a build step. The script:
1. Removes `static` from `create_uni_stream_out` in a patched copy of `lsquic_full_conn_ietf.c`
2. Appends a public `lsquic_conn_make_uni_stream(lsquic_conn_t *)` wrapper at the end of the file

The public declaration lives in `vendor/lsquic_zig/lsquic_ethp2p_ext.h`. The upstream source file is untouched.

### libp2p boundary

The ethp2p `sim/` QUIC transport is illustrative. Production deployments layer **libp2p** on top (Noise handshake, multistream-select, Yamux multiplexer, identify protocol). That layer is out of scope for `zig-ethp2p`; zeam handles it via **rust-libp2p**.
Expand Down
1 change: 0 additions & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
"build.zig.zon",
"src",
"proto",
"vendor",
"UPSTREAM.md",
"README.md",
"LICENSE",
Expand Down
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ test-sim-rs:
test-sim-gossipsub:
zig build test-sim-gossipsub --summary all

# QUIC transport tests (lsquic + BoringSSL); same as CI job `quic-transport`.
# QUIC transport tests (zquic); same as CI job `quic-transport`.
test-quic:
zig build test-quic --summary all

Expand Down
2 changes: 1 addition & 1 deletion src/transport/zquic_quic_shim.zig
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub const QuicConfig = struct {
};

pub fn logInit(_: []const u8) void {
// zquic has no global log level API comparable to lsquic; keep hook for callers.
// Reserved for future zquic logging hooks; keep signature for callers.
}

pub const QuicEndpoint = struct {
Expand Down
Loading
Loading