Skip to content

feat(gossipsub): add addDirectPeer/removeDirectPeer/getDirectPeers - #3529

Open
paschal533 wants to merge 12 commits into
libp2p:mainfrom
paschal533:feat/gossipsub-dynamic-direct-peers
Open

feat(gossipsub): add addDirectPeer/removeDirectPeer/getDirectPeers#3529
paschal533 wants to merge 12 commits into
libp2p:mainfrom
paschal533:feat/gossipsub-dynamic-direct-peers

Conversation

@paschal533

@paschal533 paschal533 commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Ports ChainSafe/js-libp2p-gossipsub#541 into the monorepo, adding three new public methods that let applications manage direct peers at runtime — without restarting GossipSub.

Before this PR, the only way to change the directPeers set was to stop GossipSub, construct a new instance with the updated directPeers config array, and start it again (~6 ms, all mesh state lost).

After this PR, three methods handle it at runtime in ~1 ms with no disruption to existing mesh connections.

New API

// Add a peer to the direct set. Stores its addresses and dials immediately
// if GossipSub is already running. Returns the peer ID string on success,
// null if rejected (self-dial, empty addrs, or peerStore failure).
addDirectPeer(peerId: PeerId, addrs: Multiaddr[]): Promise<PeerIdStr | null>

// Remove a peer from the direct set.
// The existing connection ages out through normal mesh pruning.
// Returns true if the peer was removed, false if it was not a direct peer.
removeDirectPeer(peerId: PeerId | string): boolean

// Return a snapshot of the current direct peer ID strings.
getDirectPeers(): PeerIdStr[]

Motivation

  • Aligns with ChainSafe/lodestar#8853 — Lodestar needs to hot-add trusted peers discovered after startup without downtime.
  • Direct peers discovered during operation can now be registered immediately.
  • Misbehaving direct peers can be removed from the list without a restart.

Implementation notes

  • addDirectPeer stores addresses via peerStore.merge before adding to this.direct, so a peerStore failure leaves the set clean.
  • connect() is fire-and-forget (.catch() only) — consistent with the existing directConnect() heartbeat path.
  • removeDirectPeer intentionally does not close the existing connection; it expires naturally through mesh pruning. This matches GossipSub spec behaviour.
  • this.direct remains public readonly for backwards compatibility with existing tests that read it directly.

Benchmark results

Operation Throughput Latency
BEFORE: stop + reconstruct + start ~6 ms
addDirectPeer (new API) 2,813 ops/s ~1 ms
removeDirectPeer 3,351,722 ops/s ~0.0003 ms
getDirectPeers (100-peer set) 1,285,181 ops/s ~0.0008 ms

~6× faster than the restart approach for adding a single peer.

Files changed

File Change
packages/gossipsub/src/gossipsub.ts 3 method implementations
packages/gossipsub/src/index.ts 3 method signatures added to GossipSub interface + imports
packages/gossipsub/test/direct-peers.spec.ts 10 unit tests (all edge cases covered)
packages/gossipsub/test/benchmark/direct-peers.benchmark.ts 5 benchmark cases including BEFORE vs AFTER comparison
packages/gossipsub/test/demo-direct-peers.ts 11-step real-time demo on a live 3-node network
packages/gossipsub/test/types.d.ts benchmark module type declaration (fixes pre-existing TS build error)

Test plan

  • addDirectPeer adds peer to direct set and stores addresses in peerStore
  • addDirectPeer attempts connection when GossipSub is running
  • addDirectPeer returns null when adding self
  • addDirectPeer returns null when no addresses provided
  • addDirectPeer does not connect when GossipSub is stopped (but still adds to set)
  • removeDirectPeer removes peer and returns true (PeerId form)
  • removeDirectPeer removes peer and returns true (string form)
  • removeDirectPeer returns false for non-member
  • getDirectPeers returns all current entries
  • getDirectPeers reflects removals immediately
  • Integration: all three methods verified on a live 3-node connected network

Add addDirectPeer, removeDirectPeer, and getDirectPeers methods to the
GossipSub class and its public interface, enabling applications to manage
direct peers at runtime without restarting gossipsub.

Direct peers bypass GRAFT/PRUNE negotiation and maintain permanent mesh
connections. This mirrors the Lodestar implementation (ChainSafe/lodestar#8853)
and ports the design from ChainSafe/js-libp2p-gossipsub#541 into the monorepo.

Also adds a benchmark module type declaration to fix a pre-existing
TypeScript build error that was blocking the test runner.
…agement

Benchmarks measure:
- BEFORE vs AFTER: stop+reconstruct+start (6ms) vs addDirectPeer (1ms) — 6x speedup
- addDirectPeer throughput: ~2813 ops/s
- removeDirectPeer throughput: ~3.3M ops/s (pure Set.delete)
- getDirectPeers throughput: ~1.3M ops/s (Array.from on Set)
- Integration benchmark on a live 3-node connected network

Demo (dist/test/demo-direct-peers.js) runs 11 end-to-end verification steps
covering all edge cases and the integration scenario.
@paschal533
paschal533 requested a review from a team as a code owner June 1, 2026 12:10
@paschal533 paschal533 changed the title feat(gossipsub): add runtime direct peer management (addDirectPeer / removeDirectPeer / getDirectPeers) feat(gossipsub): add addDirectPeer/removeDirectPeer/getDirectPeers Jun 1, 2026
paschal533 and others added 8 commits June 1, 2026 14:12
- Fix import order (external packages sorted alphabetically)
- Replace .to.be.true/false/null with .to.equal() — required by
  @typescript-eslint/no-unused-expressions
- Remove unused imports (connectPubsubNodes, StubbedInstance)
- Replace console.log with process.stdout.write in benchmark
- Remove demo-direct-peers.ts (replaced by benchmark coverage)
@dozyio

dozyio commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Thanks @paschal533 - made a few changes. Hopefully will get this merged over the next few days

this.log('addDirectPeer: added %s', peerIdStr)

if (this.status.code === GossipStatusCode.started) {
this.connect(peerIdStr).catch((err) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we await this.connect?

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.

3 participants