@libp2p/gossipsub 16.0.5 → 16.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@libp2p/gossipsub",
3
- "version": "16.0.5",
3
+ "version": "16.1.0",
4
4
  "description": "A typescript implementation of gossipsub",
5
5
  "files": [
6
6
  "src",
@@ -81,9 +81,9 @@
81
81
  "dependencies": {
82
82
  "@libp2p/crypto": "^5.1.21",
83
83
  "@libp2p/interface": "^3.2.5",
84
- "@libp2p/interface-internal": "^3.1.9",
85
- "@libp2p/peer-id": "^6.0.12",
86
- "@libp2p/utils": "^7.3.0",
84
+ "@libp2p/interface-internal": "^3.1.10",
85
+ "@libp2p/peer-id": "^6.0.13",
86
+ "@libp2p/utils": "^7.3.1",
87
87
  "@multiformats/multiaddr": "^13.0.3",
88
88
  "denque": "^2.1.0",
89
89
  "it-length-prefixed": "^11.0.1",
@@ -96,11 +96,11 @@
96
96
  },
97
97
  "devDependencies": {
98
98
  "@chainsafe/as-sha256": "^1.2.0",
99
- "@libp2p/floodsub": "^11.0.26",
100
- "@libp2p/logger": "^6.2.10",
101
- "@libp2p/peer-store": "^12.0.24",
99
+ "@libp2p/floodsub": "^11.0.27",
100
+ "@libp2p/logger": "^6.2.11",
101
+ "@libp2p/peer-store": "^12.0.25",
102
102
  "@types/benchmark": "^2.1.5",
103
- "@types/sinon": "^21.0.1",
103
+ "@types/sinon": "^22.0.0",
104
104
  "abortable-iterator": "^5.1.0",
105
105
  "aegir": "^48.1.1",
106
106
  "benchmark": "^2.1.4",
@@ -113,7 +113,7 @@
113
113
  "p-retry": "^8.0.0",
114
114
  "p-wait-for": "^6.0.0",
115
115
  "protons": "^9.0.1",
116
- "sinon": "^21.0.0",
116
+ "sinon": "^22.0.0",
117
117
  "sinon-ts": "^2.0.0",
118
118
  "time-cache": "^0.3.0"
119
119
  },
package/src/gossipsub.ts CHANGED
@@ -972,6 +972,8 @@ export class GossipSub extends TypedEventEmitter<GossipSubEvents> implements Typ
972
972
  // update peer subscriptions
973
973
 
974
974
  const subscriptions: Array<{ topic: TopicStr, subscribe: boolean }> = []
975
+ const fromStr = from.toString()
976
+ const graftTopics: TopicStr[] = []
975
977
 
976
978
  rpc.subscriptions.forEach((subOpt) => {
977
979
  const topic = subOpt.topic
@@ -986,10 +988,21 @@ export class GossipSub extends TypedEventEmitter<GossipSubEvents> implements Typ
986
988
 
987
989
  this.handleReceivedSubscription(from, topic, subscribe)
988
990
 
991
+ // graft the peer into the mesh now rather than waiting for the next
992
+ // heartbeat, so a message published right after subscribing is not dropped
993
+ if (subscribe && this.graftOnSubscribe(fromStr, topic)) {
994
+ graftTopics.push(topic)
995
+ }
996
+
989
997
  subscriptions.push({ topic, subscribe })
990
998
  }
991
999
  })
992
1000
 
1001
+ // send one batched GRAFT covering every topic grafted while handling this RPC
1002
+ if (graftTopics.length > 0) {
1003
+ this.sendGraft(fromStr, graftTopics)
1004
+ }
1005
+
993
1006
  this.safeDispatchEvent<SubscriptionChangeData>('subscription-change', {
994
1007
  detail: { peerId: from, subscriptions }
995
1008
  })
@@ -1539,10 +1552,7 @@ export class GossipSub extends TypedEventEmitter<GossipSubEvents> implements Typ
1539
1552
  // valid graft
1540
1553
  } else {
1541
1554
  this.log('GRAFT: Add mesh link from %s in %s', id, topicID)
1542
- this.score.graft(id, topicID)
1543
- peersInMesh.add(id)
1544
-
1545
- this.metrics?.onAddToMesh(topicID, InclusionReason.Subscribed, 1)
1555
+ this.addToMesh(id, topicID, InclusionReason.Subscribed)
1546
1556
  }
1547
1557
 
1548
1558
  this.safeDispatchEvent<MeshPeer>('gossipsub:graft', { detail: { peerId: id, topic: topicID, direction: 'inbound' } })
@@ -1877,7 +1887,7 @@ export class GossipSub extends TypedEventEmitter<GossipSubEvents> implements Typ
1877
1887
 
1878
1888
  toAdd.forEach((id) => {
1879
1889
  this.log('JOIN: Add mesh link to %s in %s', id, topic)
1880
- this.sendGraft(id, topic)
1890
+ this.sendGraft(id, [topic])
1881
1891
 
1882
1892
  // rust-libp2p
1883
1893
  // - peer_score.graft()
@@ -2276,16 +2286,55 @@ export class GossipSub extends TypedEventEmitter<GossipSubEvents> implements Typ
2276
2286
  /**
2277
2287
  * Sends a GRAFT message to a peer
2278
2288
  */
2279
- private sendGraft (id: PeerIdStr, topic: string): void {
2280
- const graft = [
2281
- {
2282
- topicID: topic
2283
- }
2284
- ]
2289
+ private sendGraft (id: PeerIdStr, topics: string[]): void {
2290
+ const graft = topics.map((topicID) => ({ topicID }))
2285
2291
  const out = createGossipRpc([], { graft })
2286
2292
  this.sendRpc(id, out)
2287
2293
  }
2288
2294
 
2295
+ /**
2296
+ * Add a peer to a topic mesh, updating peer score, mesh membership and metrics.
2297
+ * Sending the GRAFT is left to the caller: the heartbeat and graft-on-subscribe
2298
+ * send one, which dispatches the outbound gossipsub:graft event via sendRpc,
2299
+ * while the inbound GRAFT handler sends none and dispatches its own event.
2300
+ */
2301
+ private addToMesh (id: PeerIdStr, topic: TopicStr, reason: InclusionReason): void {
2302
+ this.score.graft(id, topic)
2303
+ this.mesh.get(topic)?.add(id)
2304
+ this.metrics?.onAddToMesh(topic, reason, 1)
2305
+ }
2306
+
2307
+ /**
2308
+ * When a peer subscribes to a topic we are meshed on, graft it into the mesh
2309
+ * immediately if there is room, instead of waiting for the next heartbeat.
2310
+ * Without this, a message published before the mesh has formed is forwarded to
2311
+ * nobody and silently dropped. Returns true if the peer was grafted, so the
2312
+ * caller can send one batched GRAFT for every topic grafted from an RPC.
2313
+ */
2314
+ private graftOnSubscribe (id: PeerIdStr, topic: TopicStr): boolean {
2315
+ const mesh = this.mesh.get(topic)
2316
+
2317
+ // only fill a mesh we belong to, and only while it is below the low-water mark
2318
+ if (mesh == null || mesh.has(id) || mesh.size >= this.opts.Dlo) {
2319
+ return false
2320
+ }
2321
+
2322
+ // require an outbound stream on one of our protocols, the same check the
2323
+ // heartbeat applies when selecting mesh peers
2324
+ const peerStreams = this.streamsOutbound.get(id)
2325
+ if (peerStreams == null || !this.protocols.includes(peerStreams.protocol)) {
2326
+ return false
2327
+ }
2328
+
2329
+ // do not graft direct peers, negatively scored peers or backed off peers
2330
+ if (this.direct.has(id) || this.score.score(id) < 0 || (this.backoff.get(topic)?.has(id) ?? false)) {
2331
+ return false
2332
+ }
2333
+
2334
+ this.addToMesh(id, topic, InclusionReason.Subscribed)
2335
+ return true
2336
+ }
2337
+
2289
2338
  /**
2290
2339
  * Sends a PRUNE message to a peer
2291
2340
  */
@@ -2752,13 +2801,9 @@ export class GossipSub extends TypedEventEmitter<GossipSubEvents> implements Typ
2752
2801
 
2753
2802
  const graftPeer = (id: PeerIdStr, reason: InclusionReason): void => {
2754
2803
  this.log('HEARTBEAT: Add mesh link to %s in %s', id, topic)
2755
- // update peer score
2756
- this.score.graft(id, topic)
2757
- // add peer to mesh
2758
- peers.add(id)
2804
+ this.addToMesh(id, topic, reason)
2759
2805
  // when we add a new mesh peer, we don't want to gossip messages to it
2760
2806
  peersToGossip.delete(id)
2761
- this.metrics?.onAddToMesh(topic, reason, 1)
2762
2807
  // add to tograft
2763
2808
  const topics = tograft.get(id)
2764
2809
  if (topics == null) {