From 086b37abdaee64b1bff66bd82c573585e3072c7b Mon Sep 17 00:00:00 2001 From: ch4r10t33r Date: Sun, 28 Jun 2026 18:07:44 +0100 Subject: [PATCH] =?UTF-8?q?gossipsub:=20gate=20flood-publish=20by=20messag?= =?UTF-8?q?e=20size=20=E2=80=94=20fix=20drive-loop=20saturation=20+=20outb?= =?UTF-8?q?ox=20overflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After v0.2.52 (which fixed the dominant non-delivery path, lifting coverage to ~28), the residual +0/-N decay (late=none) traced to DRIVE-LOOP SATURATION, not churn (the churn is downstream). Live steady-state log: SLOW drive iter inbound_streams=120-134ms + persistent gossip priority(1024)/bulk(64) outbox caps hit (dropping attestation/block frames). ROOT: v0.2.51 flood_publish applied to EVERY topic with no gating. On the dense block topic (~31 subscribers, ~3 MiB blocks) the proposer fanned its block to all 31 peers (~4x mesh_n) on the 64-slot bulk lane → bulk cap hit (block drops) + ~4x inbound fan-in → advanceInboundStreams 134ms → 200ms iters → ACK starvation → no-ACK teardown churn. flood on the dense/large block topic is net-harmful (blocks propagate fine via mesh; coverage is about small attestations) — zero coverage benefit for the volume. Fix: gate flood_publish to messages <= flood_publish_max_message_bytes (128 KiB, configurable). Small attestations/aggregations still flood (the intended first-hop-coverage benefit); large blocks fall back to the proven mesh-only forward path (pre-flood behavior). Cuts the dominant volume → relieves BOTH the outbox overflow AND the 134ms saturation (confirmed dominant lever by two independent live investigations). Build clean; 504/506 tests. Pure zig-libp2p. --- src/protocols/gossipsub/runtime.zig | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/protocols/gossipsub/runtime.zig b/src/protocols/gossipsub/runtime.zig index 1f237c1..dea1f2c 100644 --- a/src/protocols/gossipsub/runtime.zig +++ b/src/protocols/gossipsub/runtime.zig @@ -118,6 +118,15 @@ pub const GossipsubConfig = struct { /// message delivers it to ALL subscribed peers (remote_interest), not just /// the current mesh — decouples first-hop coverage from mesh convergence. flood_publish: bool = true, + /// Only flood-publish messages at/below this encoded wire size; larger ones + /// (e.g. ~3 MiB blocks) fall back to mesh-forward. flood_publish on a DENSE + /// topic (block: ~31 subscribers) multiplies the originator's outbound by the + /// subscriber count — for large messages that saturates the single drive loop + /// and overflows the bulk outbox (dropping frames), for ZERO coverage benefit: + /// coverage is about small attestations, and blocks propagate fine via the + /// mesh. So flood only the small (attestation/aggregation) messages where it + /// fixes first-hop coverage. Tunable. + flood_publish_max_message_bytes: usize = 128 * 1024, /// Extra heartbeats a pruned peer stays GRAFT-ineligible past exact backoff /// expiry (rust-libp2p backoff_slack). Prevents lockstep re-GRAFT flap. 0 = /// exact (no slack). @@ -1565,7 +1574,7 @@ pub const Gossipsub = struct { if (self.subs.contains(topic)) { try self.ensureTopicMesh(topic); const mp = self.mesh.getPtr(topic).?; - const ri: ?*TopicMesh = if (self.cfg.flood_publish) self.remote_interest.getPtr(topic) else null; + const ri: ?*TopicMesh = if (self.cfg.flood_publish and inner.len <= self.cfg.flood_publish_max_message_bytes) self.remote_interest.getPtr(topic) else null; if (ri) |rip| { // rust-libp2p flood_publish (default on): the ORIGINATOR delivers // its own message to EVERY subscribed peer, not just the current