@@ -4,8 +4,8 @@ import { ConnectionManager, FilterCore } from "@waku/core";
4
4
import {
5
5
type Callback ,
6
6
type ContentTopic ,
7
- CoreProtocolResult ,
8
- CreateSubscriptionResult ,
7
+ type CoreProtocolResult ,
8
+ type CreateSubscriptionResult ,
9
9
EConnectionStateEvents ,
10
10
type IAsyncIterator ,
11
11
type IDecodedMessage ,
@@ -14,13 +14,14 @@ import {
14
14
type IProtoMessage ,
15
15
type ISubscriptionSDK ,
16
16
type Libp2p ,
17
+ type PeerIdStr ,
17
18
type ProtocolCreateOptions ,
18
19
ProtocolError ,
19
- ProtocolUseOptions ,
20
+ type ProtocolUseOptions ,
20
21
type PubsubTopic ,
21
- SDKProtocolResult ,
22
+ type SDKProtocolResult ,
22
23
type ShardingParams ,
23
- SubscribeOptions ,
24
+ type SubscribeOptions ,
24
25
type Unsubscribe
25
26
} from "@waku/interfaces" ;
26
27
import { messageHashStr } from "@waku/message-hash" ;
@@ -40,9 +41,17 @@ type SubscriptionCallback<T extends IDecodedMessage> = {
40
41
callback : Callback < T > ;
41
42
} ;
42
43
44
+ type ReceivedMessageHashes = {
45
+ all : Set < string > ;
46
+ nodes : {
47
+ [ peerId : PeerIdStr ] : Set < string > ;
48
+ } ;
49
+ } ;
50
+
43
51
const log = new Logger ( "sdk:filter" ) ;
44
52
45
53
const DEFAULT_MAX_PINGS = 3 ;
54
+ const DEFAULT_MAX_MISSED_MESSAGES_THRESHOLD = 3 ;
46
55
const DEFAULT_KEEP_ALIVE = 30 * 1000 ;
47
56
48
57
const DEFAULT_SUBSCRIBE_OPTIONS = {
@@ -52,9 +61,12 @@ const DEFAULT_SUBSCRIBE_OPTIONS = {
52
61
export class SubscriptionManager implements ISubscriptionSDK {
53
62
readonly receivedMessagesHashStr : string [ ] = [ ] ;
54
63
private keepAliveTimer : number | null = null ;
64
+ private readonly receivedMessagesHashes : ReceivedMessageHashes ;
55
65
private peerFailures : Map < string , number > = new Map ( ) ;
66
+ private missedMessagesByPeer : Map < string , number > = new Map ( ) ;
56
67
private maxPingFailures : number = DEFAULT_MAX_PINGS ;
57
68
private subscribeOptions : SubscribeOptions = DEFAULT_SUBSCRIBE_OPTIONS ;
69
+ private maxMissedMessagesThreshold = DEFAULT_MAX_MISSED_MESSAGES_THRESHOLD ;
58
70
59
71
private contentTopics : ContentTopic [ ] = [ ] ;
60
72
private subscriptionCallbacks : Map <
@@ -68,14 +80,35 @@ export class SubscriptionManager implements ISubscriptionSDK {
68
80
private readonly connectionManager : ConnectionManager ,
69
81
private readonly getPeers : ( ) => Peer [ ] ,
70
82
private readonly renewPeer : ( peerToDisconnect : PeerId ) => Promise < Peer >
71
- ) { }
83
+ ) {
84
+ const allPeerIdStr = this . getPeers ( ) . map ( ( p ) => p . id . toString ( ) ) ;
85
+ this . receivedMessagesHashes = {
86
+ all : new Set ( ) ,
87
+ nodes : {
88
+ ...Object . fromEntries ( allPeerIdStr . map ( ( peerId ) => [ peerId , new Set ( ) ] ) )
89
+ }
90
+ } ;
91
+ allPeerIdStr . forEach ( ( peerId ) => this . missedMessagesByPeer . set ( peerId , 0 ) ) ;
92
+ }
93
+
94
+ private addHash ( hash : string , peerIdStr ?: string ) : void {
95
+ this . receivedMessagesHashes . all . add ( hash ) ;
96
+
97
+ if ( peerIdStr ) {
98
+ this . receivedMessagesHashes . nodes [ peerIdStr ] . add ( hash ) ;
99
+ }
100
+ }
72
101
73
102
public async subscribe < T extends IDecodedMessage > (
74
103
decoders : IDecoder < T > | IDecoder < T > [ ] ,
75
104
callback : Callback < T > ,
76
105
options : SubscribeOptions = DEFAULT_SUBSCRIBE_OPTIONS
77
106
) : Promise < SDKProtocolResult > {
107
+ this . keepAliveTimer = options . keepAlive || DEFAULT_KEEP_ALIVE ;
78
108
this . maxPingFailures = options . pingsBeforePeerRenewed || DEFAULT_MAX_PINGS ;
109
+ this . maxMissedMessagesThreshold =
110
+ options . maxMissedMessagesThreshold ||
111
+ DEFAULT_MAX_MISSED_MESSAGES_THRESHOLD ;
79
112
80
113
const decodersArray = Array . isArray ( decoders ) ? decoders : [ decoders ] ;
81
114
@@ -179,11 +212,49 @@ export class SubscriptionManager implements ISubscriptionSDK {
179
212
return finalResult ;
180
213
}
181
214
182
- async processIncomingMessage ( message : WakuMessage ) : Promise < void > {
215
+ private async validateMessage ( ) : Promise < void > {
216
+ for ( const hash of this . receivedMessagesHashes . all ) {
217
+ for ( const [ peerIdStr , hashes ] of Object . entries (
218
+ this . receivedMessagesHashes . nodes
219
+ ) ) {
220
+ if ( ! hashes . has ( hash ) ) {
221
+ this . incrementMissedMessageCount ( peerIdStr ) ;
222
+ if ( this . shouldRenewPeer ( peerIdStr ) ) {
223
+ log . info (
224
+ `Peer ${ peerIdStr } has missed too many messages, renewing.`
225
+ ) ;
226
+ const peerId = this . getPeers ( ) . find (
227
+ ( p ) => p . id . toString ( ) === peerIdStr
228
+ ) ?. id ;
229
+ if ( ! peerId ) {
230
+ log . error (
231
+ `Unexpected Error: Peer ${ peerIdStr } not found in connected peers.`
232
+ ) ;
233
+ continue ;
234
+ }
235
+ try {
236
+ await this . renewAndSubscribePeer ( peerId ) ;
237
+ } catch ( error ) {
238
+ log . error ( `Failed to renew peer ${ peerIdStr } : ${ error } ` ) ;
239
+ }
240
+ }
241
+ }
242
+ }
243
+ }
244
+ }
245
+
246
+ async processIncomingMessage (
247
+ message : WakuMessage ,
248
+ peerIdStr : string
249
+ ) : Promise < void > {
183
250
const hashedMessageStr = messageHashStr (
184
251
this . pubsubTopic ,
185
252
message as IProtoMessage
186
253
) ;
254
+
255
+ this . addHash ( hashedMessageStr , peerIdStr ) ;
256
+ void this . validateMessage ( ) ;
257
+
187
258
if ( this . receivedMessagesHashStr . includes ( hashedMessageStr ) ) {
188
259
log . info ( "Message already received, skipping" ) ;
189
260
return ;
@@ -276,15 +347,29 @@ export class SubscriptionManager implements ISubscriptionSDK {
276
347
}
277
348
}
278
349
279
- private async renewAndSubscribePeer ( peerId : PeerId ) : Promise < Peer > {
280
- const newPeer = await this . renewPeer ( peerId ) ;
281
- await this . protocol . subscribe (
282
- this . pubsubTopic ,
283
- newPeer ,
284
- Array . from ( this . subscriptionCallbacks . keys ( ) )
285
- ) ;
350
+ private async renewAndSubscribePeer (
351
+ peerId : PeerId
352
+ ) : Promise < Peer | undefined > {
353
+ try {
354
+ const newPeer = await this . renewPeer ( peerId ) ;
355
+ await this . protocol . subscribe (
356
+ this . pubsubTopic ,
357
+ newPeer ,
358
+ Array . from ( this . subscriptionCallbacks . keys ( ) )
359
+ ) ;
286
360
287
- return newPeer ;
361
+ this . receivedMessagesHashes . nodes [ newPeer . id . toString ( ) ] = new Set ( ) ;
362
+ this . missedMessagesByPeer . set ( newPeer . id . toString ( ) , 0 ) ;
363
+
364
+ return newPeer ;
365
+ } catch ( error ) {
366
+ log . warn ( `Failed to renew peer ${ peerId . toString ( ) } : ${ error } .` ) ;
367
+ return ;
368
+ } finally {
369
+ this . peerFailures . delete ( peerId . toString ( ) ) ;
370
+ this . missedMessagesByPeer . delete ( peerId . toString ( ) ) ;
371
+ delete this . receivedMessagesHashes . nodes [ peerId . toString ( ) ] ;
372
+ }
288
373
}
289
374
290
375
private startBackgroundProcess ( options : SubscribeOptions ) : void {
@@ -370,6 +455,16 @@ export class SubscriptionManager implements ISubscriptionSDK {
370
455
this . subscribeOptions ?. keepAlive || DEFAULT_SUBSCRIBE_OPTIONS . keepAlive
371
456
) ;
372
457
}
458
+
459
+ private incrementMissedMessageCount ( peerIdStr : string ) : void {
460
+ const currentCount = this . missedMessagesByPeer . get ( peerIdStr ) || 0 ;
461
+ this . missedMessagesByPeer . set ( peerIdStr , currentCount + 1 ) ;
462
+ }
463
+
464
+ private shouldRenewPeer ( peerIdStr : string ) : boolean {
465
+ const missedMessages = this . missedMessagesByPeer . get ( peerIdStr ) || 0 ;
466
+ return missedMessages > this . maxMissedMessagesThreshold ;
467
+ }
373
468
}
374
469
375
470
class FilterSDK extends BaseProtocolSDK implements IFilterSDK {
@@ -385,7 +480,7 @@ class FilterSDK extends BaseProtocolSDK implements IFilterSDK {
385
480
) {
386
481
super (
387
482
new FilterCore (
388
- async ( pubsubTopic : PubsubTopic , wakuMessage : WakuMessage ) => {
483
+ async ( pubsubTopic , wakuMessage , peerIdStr ) => {
389
484
const subscription = this . getActiveSubscription ( pubsubTopic ) ;
390
485
if ( ! subscription ) {
391
486
log . error (
@@ -394,7 +489,7 @@ class FilterSDK extends BaseProtocolSDK implements IFilterSDK {
394
489
return ;
395
490
}
396
491
397
- await subscription . processIncomingMessage ( wakuMessage ) ;
492
+ await subscription . processIncomingMessage ( wakuMessage , peerIdStr ) ;
398
493
} ,
399
494
libp2p ,
400
495
options
0 commit comments