@@ -88,7 +88,7 @@ type ReceivedMessageResult =
8888 | ( { code : MessageStatus . invalid , msgIdStr ?: MsgIdStr } & RejectReasonObj )
8989 | { code : MessageStatus . valid , messageId : MessageId , msg : Message }
9090
91- export const multicodec : string = constants . GossipsubIDv11
91+ export const multicodec : string = constants . GossipsubIDv12
9292
9393export interface GossipsubOpts extends GossipsubOptsSpec , PubSubInit {
9494 /** if dial should fallback to floodsub */
@@ -211,6 +211,20 @@ export interface GossipsubOpts extends GossipsubOptsSpec, PubSubInit {
211211 * It should be a number between 0 and 1, with a reasonable default of 0.25
212212 */
213213 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
214228}
215229
216230export interface GossipsubMessage {
@@ -274,7 +288,7 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
274288 * The signature policy to follow by default
275289 */
276290 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 ]
278292
279293 private publishConfig : PublishConfig | undefined
280294
@@ -409,11 +423,24 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
409423 */
410424 readonly gossipTracer : IWantTracer
411425
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+
412439 private readonly components : GossipSubComponents
413440
414441 private directPeerInitial : ReturnType < typeof setTimeout > | null = null
415442
416- public static multicodec : string = constants . GossipsubIDv11
443+ public static multicodec : string = constants . GossipsubIDv12
417444
418445 // Options
419446 readonly opts : Required < GossipOptions >
@@ -462,6 +489,8 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
462489 opportunisticGraftTicks : constants . GossipsubOpportunisticGraftTicks ,
463490 directConnectTicks : constants . GossipsubDirectConnectTicks ,
464491 gossipFactor : constants . GossipsubGossipFactor ,
492+ idontwantMinDataSize : constants . GossipsubIdontwantMinDataSize ,
493+ idontwantMaxMessages : constants . GossipsubIdontwantMaxMessages ,
465494 ...options ,
466495 scoreParams : createPeerScoreParams ( options . scoreParams ) ,
467496 scoreThresholds : createPeerScoreThresholds ( options . scoreThresholds )
@@ -750,6 +779,8 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
750779 this . seenCache . clear ( )
751780 if ( this . fastMsgIdCache != null ) this . fastMsgIdCache . clear ( )
752781 if ( this . directPeerInitial != null ) clearTimeout ( this . directPeerInitial )
782+ this . idontwantCounts . clear ( )
783+ this . idontwants . clear ( )
753784
754785 this . log ( 'stopped' )
755786 }
@@ -956,6 +987,9 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
956987 this . control . delete ( id )
957988 // Remove from backoff mapping
958989 this . outbound . delete ( id )
990+ // Remove from idontwant tracking
991+ this . idontwantCounts . delete ( id )
992+ this . idontwants . delete ( id )
959993
960994 // Remove from peer scoring
961995 this . score . removePeer ( id )
@@ -1019,6 +1053,10 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
10191053 prune : this . decodeRpcLimits . maxControlMessages ,
10201054 prune$ : {
10211055 peers : this . decodeRpcLimits . maxPeerInfos
1056+ } ,
1057+ idontwant : this . decodeRpcLimits . maxControlMessages ,
1058+ idontwant$ : {
1059+ messageIDs : this . decodeRpcLimits . maxIdontwantMessageIDs
10221060 }
10231061 }
10241062 }
@@ -1310,6 +1348,11 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
13101348 this . seenCache . put ( msgIdStr )
13111349 }
13121350
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+
13131356 // (Optional) Provide custom validation here with dynamic validators per topic
13141357 // NOTE: This custom topicValidator() must resolve fast (< 100ms) to allow scores
13151358 // to not penalize peers for long validation times.
@@ -1359,10 +1402,11 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
13591402 return
13601403 }
13611404
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 )
13661410
13671411 if ( ( iwant . length === 0 ) && ( ihave . length === 0 ) && ( prune . length === 0 ) ) {
13681412 return
@@ -1691,6 +1735,39 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
16911735 }
16921736 }
16931737
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+
16941771 /**
16951772 * Add standard backoff log for a peer in a topic
16961773 */
@@ -2353,6 +2430,27 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
23532430 this . sendRpc ( id , out )
23542431 }
23552432
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+
23562454 /**
23572455 * Send an rpc object to a peer
23582456 */
@@ -2701,6 +2799,18 @@ export class GossipSub extends TypedEventEmitter<GossipsubEvents> implements Pub
27012799 // apply IWANT request penalties
27022800 this . applyIwantPenalties ( )
27032801
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+
27042814 // ensure direct peers are connected
27052815 if ( this . heartbeatTicks % this . opts . directConnectTicks === 0 ) {
27062816 // 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
30693179 }
30703180 metrics . cacheSize . set ( { cache : 'backoff' } , backoffSize )
30713181
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+
30723188 // Peer counts
30733189
30743190 for ( const [ topicStr , peers ] of this . topics ) {
0 commit comments