@libp2p/gossipsub 15.0.23 → 16.0.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/src/gossipsub.ts CHANGED
@@ -183,6 +183,9 @@ export class GossipSub extends TypedEventEmitter<GossipSubEvents> implements Typ
183
183
  /** Number of messages we have asked from peer in the last heartbeat */
184
184
  private readonly iasked = new Map<PeerIdStr, number>()
185
185
 
186
+ /** Number of IWANT messages we have received from peer in the last heartbeat */
187
+ private readonly iwantCounts = new Map<PeerIdStr, number>()
188
+
186
189
  /** Prune backoff map */
187
190
  private readonly backoff = new Map<TopicStr, Map<PeerIdStr, number>>()
188
191
 
@@ -409,7 +412,7 @@ export class GossipSub extends TypedEventEmitter<GossipSubEvents> implements Typ
409
412
  this.allowedTopics = (opts.allowedTopics != null) ? new Set(opts.allowedTopics) : null
410
413
  }
411
414
 
412
- readonly [Symbol.toStringTag] = '@chainsafe/libp2p-gossipsub'
415
+ readonly [Symbol.toStringTag] = '@libp2p/gossipsub'
413
416
 
414
417
  readonly [serviceCapabilities]: string[] = [
415
418
  '@libp2p/pubsub'
@@ -592,6 +595,7 @@ export class GossipSub extends TypedEventEmitter<GossipSubEvents> implements Typ
592
595
  this.control.clear()
593
596
  this.peerhave.clear()
594
597
  this.iasked.clear()
598
+ this.iwantCounts.clear()
595
599
  this.backoff.clear()
596
600
  this.outbound.clear()
597
601
  this.gossipTracer.clear()
@@ -850,6 +854,29 @@ export class GossipSub extends TypedEventEmitter<GossipSubEvents> implements Typ
850
854
 
851
855
  // MESSAGE METHODS
852
856
 
857
+ /**
858
+ * Decode an inbound RPC, enforcing this.decodeRpcLimits.
859
+ */
860
+ private decodeRpc (rpcBytes: Uint8Array | Uint8ArrayList): RPC {
861
+ return RPC.decode(rpcBytes, {
862
+ limits: {
863
+ subscriptions: this.decodeRpcLimits.maxSubscriptions,
864
+ messages: this.decodeRpcLimits.maxMessages,
865
+ control: {
866
+ ihave: this.decodeRpcLimits.maxControlMessages,
867
+ ihave$: { messageIDs: this.decodeRpcLimits.maxIhaveMessageIDs },
868
+ iwant: this.decodeRpcLimits.maxControlMessages,
869
+ iwant$: { messageIDs: this.decodeRpcLimits.maxIwantMessageIDs },
870
+ graft: this.decodeRpcLimits.maxControlMessages,
871
+ prune: this.decodeRpcLimits.maxControlMessages,
872
+ prune$: { peers: this.decodeRpcLimits.maxPeerInfos },
873
+ idontwant: this.decodeRpcLimits.maxControlMessages,
874
+ idontwant$: { messageIDs: this.decodeRpcLimits.maxIdontwantMessageIDs }
875
+ }
876
+ }
877
+ })
878
+ }
879
+
853
880
  /**
854
881
  * Responsible for processing each RPC message received by other peers.
855
882
  */
@@ -862,25 +889,7 @@ export class GossipSub extends TypedEventEmitter<GossipSubEvents> implements Typ
862
889
  const rpcBytes = data.subarray()
863
890
  // Note: This function may throw, it must be wrapped in a try {} catch {} to prevent closing the stream.
864
891
  // TODO: What should we do if the entire RPC is invalid?
865
- const rpc = RPC.decode(rpcBytes, {
866
- limits: {
867
- subscriptions: this.decodeRpcLimits.maxSubscriptions,
868
- messages: this.decodeRpcLimits.maxMessages,
869
- control$: {
870
- ihave: this.decodeRpcLimits.maxIhaveMessageIDs,
871
- iwant: this.decodeRpcLimits.maxIwantMessageIDs,
872
- graft: this.decodeRpcLimits.maxControlMessages,
873
- prune: this.decodeRpcLimits.maxControlMessages,
874
- prune$: {
875
- peers: this.decodeRpcLimits.maxPeerInfos
876
- },
877
- idontwant: this.decodeRpcLimits.maxControlMessages,
878
- idontwant$: {
879
- messageIDs: this.decodeRpcLimits.maxIdontwantMessageIDs
880
- }
881
- }
882
- }
883
- })
892
+ const rpc = this.decodeRpc(rpcBytes)
884
893
 
885
894
  this.metrics?.onRpcRecv(rpc, rpcBytes.length)
886
895
 
@@ -1316,23 +1325,32 @@ export class GossipSub extends TypedEventEmitter<GossipSubEvents> implements Typ
1316
1325
  // string msgId => msgId
1317
1326
  const iwant = new Map<MsgIdStr, Uint8Array>()
1318
1327
 
1319
- ihave.forEach(({ topicID, messageIDs }) => {
1328
+ // Cap the message ids we examine per call at GossipsubMaxIHaveLength
1329
+ let processed = 0
1330
+ // eslint-disable-next-line no-labels
1331
+ out: for (const { topicID, messageIDs } of ihave) {
1320
1332
  if (topicID == null || (messageIDs == null) || !this.mesh.has(topicID)) {
1321
- return
1333
+ continue
1322
1334
  }
1323
1335
 
1324
1336
  let idonthave = 0
1325
1337
 
1326
- messageIDs.forEach((msgId) => {
1338
+ for (const msgId of messageIDs) {
1339
+ if (processed >= constants.GossipsubMaxIHaveLength) {
1340
+ // eslint-disable-next-line no-labels
1341
+ break out
1342
+ }
1343
+ processed++
1344
+
1327
1345
  const msgIdStr = this.msgIdToStrFn(msgId)
1328
1346
  if (!this.seenCache.has(msgIdStr)) {
1329
1347
  iwant.set(msgIdStr, msgId)
1330
1348
  idonthave++
1331
1349
  }
1332
- })
1350
+ }
1333
1351
 
1334
1352
  this.metrics?.onIhaveRcv(topicID, messageIDs.length, idonthave)
1335
- })
1353
+ }
1336
1354
 
1337
1355
  if (iwant.size === 0) {
1338
1356
  return []
@@ -1378,29 +1396,49 @@ export class GossipSub extends TypedEventEmitter<GossipSubEvents> implements Typ
1378
1396
  return []
1379
1397
  }
1380
1398
 
1399
+ // IWANT flood protection
1400
+ const iwantCount = (this.iwantCounts.get(id) ?? 0) + 1
1401
+ this.iwantCounts.set(id, iwantCount)
1402
+ if (iwantCount > constants.GossipsubMaxIWantMessages) {
1403
+ this.log('IWANT: peer %s has requested too many times within this heartbeat interval; ignoring', id)
1404
+ return []
1405
+ }
1406
+
1381
1407
  const ihave = new Map<MsgIdStr, RPC.Message>()
1382
1408
  const iwantByTopic = new Map<TopicStr, number>()
1383
1409
  let iwantDonthave = 0
1384
1410
 
1385
- iwant.forEach(({ messageIDs }) => {
1386
- messageIDs?.forEach((msgId) => {
1411
+ // Cap the message ids we examine per call at GossipsubMaxIHaveLength
1412
+ let processed = 0
1413
+ // eslint-disable-next-line no-labels
1414
+ out: for (const { messageIDs } of iwant) {
1415
+ if (messageIDs == null) {
1416
+ continue
1417
+ }
1418
+ for (const msgId of messageIDs) {
1419
+ if (processed >= constants.GossipsubMaxIHaveLength) {
1420
+ // eslint-disable-next-line no-labels
1421
+ break out
1422
+ }
1423
+ processed++
1424
+
1387
1425
  const msgIdStr = this.msgIdToStrFn(msgId)
1388
1426
  const entry = this.mcache.getWithIWantCount(msgIdStr, id)
1389
1427
  if (entry == null) {
1390
1428
  iwantDonthave++
1391
- return
1429
+ continue
1392
1430
  }
1393
1431
 
1394
1432
  iwantByTopic.set(entry.msg.topic, 1 + (iwantByTopic.get(entry.msg.topic) ?? 0))
1395
1433
 
1396
1434
  if (entry.count > constants.GossipsubGossipRetransmission) {
1397
1435
  this.log('IWANT: Peer %s has asked for message %s too many times: ignoring request', id, msgId)
1398
- return
1436
+ continue
1399
1437
  }
1400
1438
 
1401
1439
  ihave.set(msgIdStr, entry.msg)
1402
- })
1403
- })
1440
+ }
1441
+ }
1404
1442
 
1405
1443
  this.metrics?.onIwantRcv(iwantByTopic, iwantDonthave)
1406
1444
 
@@ -2615,6 +2653,7 @@ export class GossipSub extends TypedEventEmitter<GossipSubEvents> implements Typ
2615
2653
  this.peerhave.clear()
2616
2654
  this.metrics?.cacheSize.set({ cache: 'iasked' }, this.iasked.size)
2617
2655
  this.iasked.clear()
2656
+ this.iwantCounts.clear()
2618
2657
 
2619
2658
  // apply IWANT request penalties
2620
2659
  this.applyIwantPenalties()
@@ -9,11 +9,12 @@ export interface DecodeRPCLimits {
9
9
  }
10
10
 
11
11
  export const defaultDecodeRpcLimits: DecodeRPCLimits = {
12
- maxSubscriptions: Infinity,
13
- maxMessages: Infinity,
14
- maxIhaveMessageIDs: Infinity,
15
- maxIwantMessageIDs: Infinity,
16
- maxIdontwantMessageIDs: Infinity,
17
- maxControlMessages: Infinity,
18
- maxPeerInfos: Infinity
12
+ // 5000 = GossipsubMaxIHaveLength, used as a generous upper bound for these
13
+ maxSubscriptions: 5000,
14
+ maxMessages: 5000,
15
+ maxIhaveMessageIDs: 5000,
16
+ maxIwantMessageIDs: 5000,
17
+ maxControlMessages: 5000,
18
+ maxIdontwantMessageIDs: 512, // GossipsubIdontwantMaxMessages
19
+ maxPeerInfos: 16 // GossipsubPrunePeers
19
20
  }