Skip to content

feat: add SPREAD anonymous gossip protocol (opt-in)#717

Open
MatheusFranco99 wants to merge 2 commits into
libp2p:masterfrom
d-card:spread-pr
Open

feat: add SPREAD anonymous gossip protocol (opt-in)#717
MatheusFranco99 wants to merge 2 commits into
libp2p:masterfrom
d-card:spread-pr

Conversation

@MatheusFranco99

@MatheusFranco99 MatheusFranco99 commented Jun 25, 2026

Copy link
Copy Markdown

Overview

This PR adds SPREAD, an anonymity-preserving, efficiency-oriented gossip mode layered on top of GossipSub. It is fully opt-in and a complete no-op for default GossipSub:

  • Enabled per-router via WithProtocolChoice(SPREAD).
  • Triggered per-message via msg.Spread.
  • When inactive (the default), routing is byte-for-byte the existing GossipSub behaviour.

SPREAD biases message forwarding toward a peer's local cluster (peers that are close in network-coordinate space) plus a sampled set of inter-cluster peers, trading a controllable amount of redundancy for reduced source-identifiability while preserving dissemination.

How it works

  • Network coordinates (vivaldi/). A small Vivaldi "height-vector" coordinate service estimates pairwise RTT/proximity by timing a lightweight request/response over a dedicated stream protocol. An optional Newton variant adds robustness checks.
  • Clustering + selection (spread_state.go, spread_propagation.go). Per topic, SPREAD-capable peers are partitioned into a local cluster (closest ClusterPct) and inter-cluster peers (optionally bucketed by angle). The forwarding set is then sampled with configurable intra/inter fanout and probabilities.
  • Router hook (gossipsub.go). In rpcs(), when SPREAD is active for a message, the SPREAD-selected peer set replaces the default forwarding set. If SPREAD selects no eligible peers, the router keeps the default GossipSub set, so messages are never dropped.
  • Extension wiring (extensions.go, pb/rpc.proto). SPREAD plugs into the existing peer-extension handshake: a peer is registered into SPREAD state only once both sides advertise the Spread extension. An optional SpreadExtension wire field is added to the RPC.

Public API

  • WithProtocolChoice(GOSSIPSUB | SPREAD)
  • WithSpreadPropagationConfig(*SpreadConfig) — intra/inter fanout and probabilities
  • WithSpreadClusteringConfig(*SpreadClusteringConfig) — cluster sizing and angular bucketing
  • WithVivaldi(*vivaldi.Service, *VivaldiConfig) — enable network-coordinate estimation
  • New vivaldi package for the coordinate service.

All exported symbols carry godoc comments; spread.md documents the algorithm, caching, and selection semantics.

Testing

  • Unit tests for clustering/config sanitization and propagation selection (spread_clustering_test.go).
  • An end-to-end test exercising the SPREAD selection delivery path (TestGossipsubSpreadSelectionDelivers) and the duplicate re-propagation budget.
  • Vivaldi update/service unit tests.
  • The full existing pubsub test suite passes unchanged.

Notes

  • This is a research output: SPREAD is an MSc thesis project by Diogo Cardoso (co-authored on the commit).
  • The change is additive and opt-in; default GossipSub behaviour and wire format are unaffected unless WithProtocolChoice(SPREAD) is set and messages are explicitly marked.

MatheusFranco99 and others added 2 commits June 25, 2026 23:26
SPREAD is an anonymity-preserving, efficiency-oriented gossip mode layered on
top of GossipSub. It is fully opt-in: enabled per-router via
WithProtocolChoice(SPREAD) and per-message via msg.Spread, and is a no-op for
default GossipSub.

Core pieces:
- spread_state.go / spread_propagation.go: topic-local clustering over Vivaldi
  network-coordinate distance and the intra/inter-cluster forwarding selector.
- vivaldi/: Vivaldi 'height-vector' coordinate service (RTT-estimated
  proximity) used to derive local clusters.
- extensions.go: WithVivaldi / WithSpreadClusteringConfig wiring into the
  existing peer-extension handshake; SPREAD peers register into spread state
  once both sides advertise the Spread extension.
- gossipsub.go: WithProtocolChoice / WithSpreadPropagationConfig, and the
  rpcs() hook that replaces the default forwarding set with the SPREAD
  selection when active (falling back to the default set only if SPREAD
  selects no peers, so messages are never dropped).
- pb/rpc.proto: optional SpreadExtension wire field.
- Tests: clustering/config unit tests plus an end-to-end SPREAD selection
  delivery test. spread.md documents the algorithm; exported symbols carry
  godoc comments.

Co-authored-by: Diogo Cardoso <me@dcard.pt>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The generated code and runtime already rely on SpreadExtension's
sourceIsSpreadNode field: gossipsub sets it when forwarding a SPREAD message
and pubsub reads it back to keep msg.Spread set on relays, which is what lets
SPREAD propagate past the first hop. The .proto, however, declared the message
as empty, so regenerating from it would have dropped the field. Declare the
field in rpc.proto, regenerate, and drop the now-answered TODO. No wire or
behaviour change.

Co-authored-by: Diogo Cardoso <me@dcard.pt>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@yiannisbot

Copy link
Copy Markdown

Thanks for the input - opt-in anonymity would be a valuable addition to Gossipsub. Do you have a longer write-up that explains in more detail how the protocol actually works?

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