@@ -88,7 +88,7 @@ type ReceivedMessageResult =
88
88
| ( { code : MessageStatus . invalid , msgIdStr ?: MsgIdStr } & RejectReasonObj )
89
89
| { code : MessageStatus . valid , messageId : MessageId , msg : Message }
90
90
91
- export const multicodec : string = constants . GossipsubIDv11
91
+ export const multicodec : string = constants . GossipsubIDv12
92
92
93
93
export interface GossipsubOpts extends GossipsubOptsSpec , PubSubInit {
94
94
/** if dial should fallback to floodsub */
@@ -211,6 +211,20 @@ export interface GossipsubOpts extends GossipsubOptsSpec, PubSubInit {
211
211
* It should be a number between 0 and 1, with a reasonable default of 0.25
212
212
*/
213
213
gossipFactor : number
214
+
215
+ /**
216
+ * The minimum message size in bytes to be considered for sending IDONTWANT messages
217
+ *
218
+ * @default 512
219
+ */
220
+ idontwantMinDataSize ?: number
221
+
222
+ /**
223
+ * The maximum number of IDONTWANT messages per heartbeat per peer
224
+ *
225
+ * @default 512
226
+ */
227
+ idontwantMaxMessages ?: number
214
228
}
215
229
216
230
export interface GossipsubMessage {
@@ -274,7 +288,7 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
274
288
* The signature policy to follow by default
275
289
*/
276
290
public readonly globalSignaturePolicy : typeof StrictSign | typeof StrictNoSign
277
- public multicodecs : string [ ] = [ constants . GossipsubIDv11 , constants . GossipsubIDv10 ]
291
+ public multicodecs : string [ ] = [ constants . GossipsubIDv12 , constants . GossipsubIDv11 , constants . GossipsubIDv10 ]
278
292
279
293
private publishConfig : PublishConfig | undefined
280
294
@@ -409,11 +423,24 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
409
423
*/
410
424
readonly gossipTracer : IWantTracer
411
425
426
+ /**
427
+ * Tracks IDONTWANT messages received by peers in the current heartbeat
428
+ */
429
+ private readonly idontwantCounts = new Map < PeerIdStr , number > ( )
430
+
431
+ /**
432
+ * Tracks IDONTWANT messages received by peers and the heartbeat they were received in
433
+ *
434
+ * idontwants are stored for `mcacheLength` heartbeats before being pruned,
435
+ * so this map is bounded by peerCount * idontwantMaxMessages * mcacheLength
436
+ */
437
+ private readonly idontwants = new Map < PeerIdStr , Map < MsgIdStr , number > > ( )
438
+
412
439
private readonly components : GossipSubComponents
413
440
414
441
private directPeerInitial : ReturnType < typeof setTimeout > | null = null
415
442
416
- public static multicodec : string = constants . GossipsubIDv11
443
+ public static multicodec : string = constants . GossipsubIDv12
417
444
418
445
// Options
419
446
readonly opts : Required < GossipOptions >
@@ -462,6 +489,8 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
462
489
opportunisticGraftTicks : constants . GossipsubOpportunisticGraftTicks ,
463
490
directConnectTicks : constants . GossipsubDirectConnectTicks ,
464
491
gossipFactor : constants . GossipsubGossipFactor ,
492
+ idontwantMinDataSize : constants . GossipsubIdontwantMinDataSize ,
493
+ idontwantMaxMessages : constants . GossipsubIdontwantMaxMessages ,
465
494
...options ,
466
495
scoreParams : createPeerScoreParams ( options . scoreParams ) ,
467
496
scoreThresholds : createPeerScoreThresholds ( options . scoreThresholds )
@@ -750,6 +779,8 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
750
779
this . seenCache . clear ( )
751
780
if ( this . fastMsgIdCache != null ) this . fastMsgIdCache . clear ( )
752
781
if ( this . directPeerInitial != null ) clearTimeout ( this . directPeerInitial )
782
+ this . idontwantCounts . clear ( )
783
+ this . idontwants . clear ( )
753
784
754
785
this . log ( 'stopped' )
755
786
}
@@ -956,6 +987,9 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
956
987
this . control . delete ( id )
957
988
// Remove from backoff mapping
958
989
this . outbound . delete ( id )
990
+ // Remove from idontwant tracking
991
+ this . idontwantCounts . delete ( id )
992
+ this . idontwants . delete ( id )
959
993
960
994
// Remove from peer scoring
961
995
this . score . removePeer ( id )
@@ -1019,6 +1053,10 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
1019
1053
prune : this . decodeRpcLimits . maxControlMessages ,
1020
1054
prune$ : {
1021
1055
peers : this . decodeRpcLimits . maxPeerInfos
1056
+ } ,
1057
+ idontwant : this . decodeRpcLimits . maxControlMessages ,
1058
+ idontwant$ : {
1059
+ messageIDs : this . decodeRpcLimits . maxIdontwantMessageIDs
1022
1060
}
1023
1061
}
1024
1062
}
@@ -1310,6 +1348,11 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
1310
1348
this . seenCache . put ( msgIdStr )
1311
1349
}
1312
1350
1351
+ // possibly send IDONTWANTs to mesh peers
1352
+ if ( ( rpcMsg . data ?. length ?? 0 ) >= this . opts . idontwantMinDataSize ) {
1353
+ this . sendIDontWants ( msgId , rpcMsg . topic , propagationSource . toString ( ) )
1354
+ }
1355
+
1313
1356
// (Optional) Provide custom validation here with dynamic validators per topic
1314
1357
// NOTE: This custom topicValidator() must resolve fast (< 100ms) to allow scores
1315
1358
// to not penalize peers for long validation times.
@@ -1359,10 +1402,11 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
1359
1402
return
1360
1403
}
1361
1404
1362
- const iwant = ( controlMsg . ihave != null ) ? this . handleIHave ( id , controlMsg . ihave ) : [ ]
1363
- const ihave = ( controlMsg . iwant != null ) ? this . handleIWant ( id , controlMsg . iwant ) : [ ]
1364
- const prune = ( controlMsg . graft != null ) ? await this . handleGraft ( id , controlMsg . graft ) : [ ]
1365
- ; ( controlMsg . prune != null ) && ( await this . handlePrune ( id , controlMsg . prune ) )
1405
+ const iwant = ( controlMsg . ihave ?. length > 0 ) ? this . handleIHave ( id , controlMsg . ihave ) : [ ]
1406
+ const ihave = ( controlMsg . iwant ?. length > 0 ) ? this . handleIWant ( id , controlMsg . iwant ) : [ ]
1407
+ const prune = ( controlMsg . graft ?. length > 0 ) ? await this . handleGraft ( id , controlMsg . graft ) : [ ]
1408
+ ; ( controlMsg . prune ?. length > 0 ) && ( await this . handlePrune ( id , controlMsg . prune ) )
1409
+ ; ( controlMsg . idontwant ?. length > 0 ) && this . handleIdontwant ( id , controlMsg . idontwant )
1366
1410
1367
1411
if ( ( iwant . length === 0 ) && ( ihave . length === 0 ) && ( prune . length === 0 ) ) {
1368
1412
return
@@ -1691,6 +1735,39 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
1691
1735
}
1692
1736
}
1693
1737
1738
+ private handleIdontwant ( id : PeerIdStr , idontwant : RPC . ControlIDontWant [ ] ) : void {
1739
+ let idontwantCount = this . idontwantCounts . get ( id ) ?? 0
1740
+ // return early if we have already received too many IDONTWANT messages from the peer
1741
+ if ( idontwantCount >= this . opts . idontwantMaxMessages ) {
1742
+ return
1743
+ }
1744
+ const startIdontwantCount = idontwantCount
1745
+
1746
+ let idontwants = this . idontwants . get ( id )
1747
+ if ( idontwants == null ) {
1748
+ idontwants = new Map ( )
1749
+ this . idontwants . set ( id , idontwants )
1750
+ }
1751
+ let idonthave = 0
1752
+ // eslint-disable-next-line no-labels
1753
+ out: for ( const { messageIDs } of idontwant ) {
1754
+ for ( const msgId of messageIDs ) {
1755
+ if ( idontwantCount >= this . opts . idontwantMaxMessages ) {
1756
+ // eslint-disable-next-line no-labels
1757
+ break out
1758
+ }
1759
+ idontwantCount ++
1760
+
1761
+ const msgIdStr = this . msgIdToStrFn ( msgId )
1762
+ idontwants . set ( msgIdStr , this . heartbeatTicks )
1763
+ if ( ! this . mcache . msgs . has ( msgIdStr ) ) idonthave ++
1764
+ }
1765
+ }
1766
+ this . idontwantCounts . set ( id , idontwantCount )
1767
+ const total = idontwantCount - startIdontwantCount
1768
+ this . metrics ?. onIdontwantRcv ( total , idonthave )
1769
+ }
1770
+
1694
1771
/**
1695
1772
* Add standard backoff log for a peer in a topic
1696
1773
*/
@@ -2353,6 +2430,27 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
2353
2430
this . sendRpc ( id , out )
2354
2431
}
2355
2432
2433
+ private sendIDontWants ( msgId : Uint8Array , topic : string , source : PeerIdStr ) : void {
2434
+ const ids = this . mesh . get ( topic )
2435
+ if ( ids == null ) {
2436
+ return
2437
+ }
2438
+
2439
+ // don't send IDONTWANT to:
2440
+ // - the source
2441
+ // - peers that don't support v1.2
2442
+ const tosend = new Set ( ids )
2443
+ tosend . delete ( source )
2444
+ for ( const id of tosend ) {
2445
+ if ( this . streamsOutbound . get ( id ) ?. protocol !== constants . GossipsubIDv12 ) {
2446
+ tosend . delete ( id )
2447
+ }
2448
+ }
2449
+
2450
+ const idontwantRpc = createGossipRpc ( [ ] , { idontwant : [ { messageIDs : [ msgId ] } ] } )
2451
+ this . sendRpcInBatch ( tosend , idontwantRpc )
2452
+ }
2453
+
2356
2454
/**
2357
2455
* Send an rpc object to a peer
2358
2456
*/
@@ -2701,6 +2799,18 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
2701
2799
// apply IWANT request penalties
2702
2800
this . applyIwantPenalties ( )
2703
2801
2802
+ // clean up IDONTWANT counters
2803
+ this . idontwantCounts . clear ( )
2804
+
2805
+ // clean up old tracked IDONTWANTs
2806
+ for ( const idontwants of this . idontwants . values ( ) ) {
2807
+ for ( const [ msgId , heartbeatTick ] of idontwants ) {
2808
+ if ( this . heartbeatTicks - heartbeatTick >= this . opts . mcacheLength ) {
2809
+ idontwants . delete ( msgId )
2810
+ }
2811
+ }
2812
+ }
2813
+
2704
2814
// ensure direct peers are connected
2705
2815
if ( this . heartbeatTicks % this . opts . directConnectTicks === 0 ) {
2706
2816
// we only do this every few ticks to allow pending connections to complete and account for restarts/downtime
@@ -3069,6 +3179,12 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
3069
3179
}
3070
3180
metrics . cacheSize . set ( { cache : 'backoff' } , backoffSize )
3071
3181
3182
+ let idontwantsCount = 0
3183
+ for ( const idontwant of this . idontwants . values ( ) ) {
3184
+ idontwantsCount += idontwant . size
3185
+ }
3186
+ metrics . cacheSize . set ( { cache : 'idontwants' } , idontwantsCount )
3187
+
3072
3188
// Peer counts
3073
3189
3074
3190
for ( const [ topicStr , peers ] of this . topics ) {
0 commit comments