Skip to content

fix(network): restore search delivery to all peers (gossipsub score NaN root cause)#22

Merged
libersoft-org merged 10 commits intomainfrom
fix/gossipsub-score-for-fresh-peers
May 3, 2026
Merged

fix(network): restore search delivery to all peers (gossipsub score NaN root cause)#22
libersoft-org merged 10 commits intomainfrom
fix/gossipsub-score-for-fresh-peers

Conversation

@lukyrys
Copy link
Copy Markdown
Collaborator

@lukyrys lukyrys commented May 2, 2026

Summary

Search RPCs were silently excluding peers from gossipsub floodPublish, causing inconsistent or empty results across peers in the same topic. The root cause was a per-topic gossipsub score configuration that could produce NaN; in JS, NaN >= publishThreshold is false, so affected peers were dropped from the recipient set without an explicit error.

Root Cause

subscribeTopic() registers custom per-topic score parameters for LISH topics. The earlier manual object did not define every numeric field used by @chainsafe/libp2p-gossipsub score decay. Even when meshMessageDeliveriesWeight and meshFailurePenaltyWeight are 0, refreshScores() still runs the decay math:

tstats.meshMessageDeliveries *= tparams.meshMessageDeliveriesDecay;
tstats.meshFailurePenalty *= tparams.meshFailurePenaltyDecay;

If those decay fields are missing, 0 * undefined becomes NaN. That NaN persists and propagates through computeScore(), after which publish filtering evaluates:

this.score.score(id) >= this.opts.scoreThresholds.publishThreshold

For NaN, that comparison is false, so the peer is silently skipped.

Changes

  1. Use createTopicScoreParams() in network.ts
    The topic score overrides now go through the upstream helper from @chainsafe/libp2p-gossipsub/score. This merges our overrides onto upstream defaults, so all required numeric fields stay finite, including fields for disabled weights and any future defaults added by the library.

  2. Keep neutral peers above publish/gossip gates
    appSpecificScore now returns 1 for non-trusted peers and 1000 for trusted PX peers. This is a defensive neutral baseline, not the primary NaN fix. Penalties can still move peers below publish/gossip thresholds.

  3. Keep PX trust threshold above the neutral baseline
    Because neutral peers now score 1, parseAcceptPXThreshold() treats values <= 1 as unsafe and falls back to DEFAULT_ACCEPT_PX_THRESHOLD. This preserves the intended separation between neutral delivery eligibility and trusted PX eligibility, even if an operator weakens config.

  4. Evict dead outbound gossipsub streams on async push() rejection
    The OutboundStream.push() wrapper now catches rejected async writes, reverse-lookups the stream in streamsOutbound, closes it, and deletes the dead entry so later sendRpc calls can attach a fresh stream. This is explicitly marked as a temporary workaround because it reaches into private gossipsub internals; it should be replaced by an upstream ChainSafe fix when available.

  5. Frontend result-row identity fix
    lishSearch.svelte.ts mutates existing LISH rows in place so open detail views keep receiving peer updates in this PR. Follow-up PR refactor(network): look up LISH search detail row by id via $derived #23 replaces this with a cleaner ID-based $derived lookup so the reducer can return to replacing rows by ID.

Verification

  • bun test tests/unit/protocol/network-mesh.test.ts tests/unit/protocol/score-regression.test.ts — 63 pass
  • bun run typecheck in backend
  • bun run check in frontend
  • bun run build in frontend
  • Verified HTTP smoke checks: local FE 200, backend WS endpoints return expected HTTP 400 on plain HTTP

@lukyrys lukyrys force-pushed the fix/gossipsub-score-for-fresh-peers branch from c04ddc8 to 7c43887 Compare May 3, 2026 14:41
@libersoft-org libersoft-org merged commit ebcc198 into main May 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants