Skip to content

v0.1.41

Choose a tag to compare

@ch4r10t33r ch4r10t33r released this 11 Jun 17:57
· 91 commits to main since this release
344d115

v0.1.41

First tagged release since v0.1.17 — rolls up everything merged to main between v0.1.17 and v0.1.41. Highlights from PR #192 (the bug fix that motivated this release):

  • gossipsub: inbound IHAVE now generates IWANT for unseen ids (libp2p v1.1 §3.4); previously dropped silently, breaking lazy-gossip recovery against rust- and go-libp2p peers.
  • gossipsub: GRAFT during active PRUNE back-off now docks the sender's behaviour score (rust-libp2p P7 weight).
  • autonat: v2 amplification cost is now uniformly sampled across [min, max]; the previous std.math.clamp(min, 1, max) always returned the minimum.

See the full changelog below for the 0.1.18 → 0.1.41 history covering security fixes, the /ws WebSocket transport, libp2p-TLS UTCTime parsing fix, AutoNAT v2 + relay (circuit v2) + DCUtR + Kademlia DHT scaffolding, persistent per-peer /meshsub/1.1.0 streams, and matching zquic protocol fixes pulled in along the way.


0.1.41 (2026-06-11)

Fixed

  • gossipsub: handle inbound IHAVE control messages by emitting IWANT for
    unseen ids (libp2p gossipsub v1.1 §3.4). Previously zig-libp2p decoded IHAVE
    and dropped it on the floor, so lazy-gossip recovery against rust- and
    go-libp2p peers was effectively dead: a peer outside our mesh could announce
    message ids but we never pulled them, breaking mesh healing after a partition
    and degrading dissemination redundancy. The new
    runtime.handleIHaveOffer filters against pull_fifo + recent_seen,
    caps the number of ids accepted per RPC (max_ihave_ids_per_rpc) and the
    number requested in the resulting IWANT (max_iwant_ids_per_rpc) — both
    defaulting to 5000 to match rust-libp2p — and exposes
    iwantTxCount / iwantIdsRequestedCount / ihaveIdsCappedCount.

  • gossipsub: dock the sender's behaviour score (configurable via
    graft_during_backoff_score_delta, default -50) when an inbound GRAFT
    arrives during the peer's own active PRUNE back-off window. rust-libp2p
    models this as the P7 weight; we previously refused the GRAFT with a
    PRUNE+remaining-backoff but applied no penalty, so a misbehaving peer
    could flood-GRAFT freely. The unsubscribe-cooldown branch keeps its
    original behaviour (no penalty — that path doesn't represent the same
    flood pattern).

  • autonat: v2 amplification cost is now uniformly sampled across the
    full [amplification_min_bytes, amplification_max_bytes] range from a
    SplitMix64 step over the peer nonce. The previous shape
    std.math.clamp(min, 1, max) always returned exactly the minimum
    (30 KiB by default) and silently ignored amplification_max_bytes,
    making the cost trivially predictable to a client and leaving the
    spec-mandated range knob dead.

0.1.40 (2026-06-11)

Fixed

  • transport/quic_runtime: detect outbound QUIC connection close as soon as
    CONNECTION_CLOSE is received, not after the 3×PTO draining deadline. zquic
    flips conn.draining = true on CONNECTION_CLOSE receipt but keeps
    conn.phase at .connected until the drain timer reaps the slot — the
    client side never reaps at all today. The previous detector only checked
    phase == .closed, so a remote-initiated close (e.g. rust-libp2p ending the
    session due to gossipsub mesh degrade or transport error) was silent on the
    zeam side: outbound_by_peer retained the dead entry, gossip publishes
    drained into the void, and connection_manager never scheduled a redial.
    Now detectOutboundConnectionClose treats phase == .closed || draining
    as terminal and fires host.onConnectionClosed immediately, mirroring the
    listener-side close path.

0.1.39 (2026-06-11)

Fixed

  • transport/quic_runtime: bind the persistent /meshsub/1.1.0 publish stream
    to the outbound (locally-dialed) QUIC connection only. When a rust-libp2p
    peer dials first, opening gossip publish on that inbound leg delivers a few
    frames then stalls once the dialer leg comes up; zeam→ethlambda gossip now
    migrates to the outbound connection and replays SUBSCRIBE there.

0.1.22 (2026-06-10)

Fixed

  • transport/quic_runtime: respond to inbound /ipfs/id/push/1.0.0 streams from
    rust-libp2p identify (ethlambda opens push after the initial identify exchange).
    Previously these streams got naProtocolNegotiationFailed at startup.
    Also log accumulated byte count on other inbound handshake failures for easier
    diagnosis.

0.1.21 (2026-06-10)

Fixed

  • transport/multistream_negotiate, transport/stream_multistream: thread the
    framing detected from the multistream offer line through every subsequent
    token read. The previous per-token first-byte auto-detection
    ('/' ⇒ legacy, otherwise delimited) collided with go-multistream delimited
    framing whenever the varint length byte equalled 0x2F = '/' — i.e. a token
    of total wire length 47 bytes. The lean consensus protocol id
    /leanconsensus/req/blocks_by_root/1/ssz_snappy has a 46-byte body, so its
    delimited wire form starts with '/' and got mis-classified as a legacy
    line. The responder mis-parsed the offer and replied na even though the
    protocol was supported, and the initiator mis-parsed the ack the same way.
    In practice rust-libp2p (ethlambda) observed The remote supports none of the requested protocols for every blocks_by_root request to a
    zig-libp2p peer, blocking chain sync. status and blocks_by_range were
    unaffected because their wire lengths (39 and 48) don't collide with '/'.

Fixed

  • transport/quic_runtime: drain the per-stream multistream-select tail
    into the protocol dispatch accumulators (gossipsub, req/resp, relay) before
    reading new bytes from the raw recv buffer. rust-libp2p and go-libp2p
    routinely flush the protocol ack and the first application bytes in a
    single QUIC STREAM frame; those bytes landed in ms_tail after
    negotiation and were never forwarded, so inbound blocks_by_root /
    status requests timed out on the responder (#184).

0.1.19 (2026-06-10)

Fixed

  • transport/quic_runtime: persist the multistream-select accumulator across
    drive ticks for inbound streams. Previously a partial responder negotiation
    (DialFailed) lost the bytes the helper had already pulled from the raw
    reader, so the second attempt mis-parsed and the peer saw na for
    legitimate protocols (e.g. rust-libp2p blocks_by_root requests).

0.1.18 (2026-06-10)

Fixed

  • transport/quic_runtime: use go-multistream delimited framing on outbound
    req/resp and publish streams so rust-libp2p responders accept the handshake
    (#184). Replace the
    legacy two-newline inbound pre-buffer with incremental responder negotiation.
    Answer inbound /ipfs/id/1.0.0 and /ipfs/ping/1.0.0 streams from
    rust-libp2p peers.