@@ -4,22 +4,23 @@ 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
type IAsyncIterator ,
10
10
type IDecodedMessage ,
11
11
type IDecoder ,
12
12
type IFilterSDK ,
13
13
type IProtoMessage ,
14
14
type ISubscriptionSDK ,
15
15
type Libp2p ,
16
+ type PeerIdStr ,
16
17
type ProtocolCreateOptions ,
17
18
ProtocolError ,
18
- ProtocolUseOptions ,
19
+ type ProtocolUseOptions ,
19
20
type PubsubTopic ,
20
- SDKProtocolResult ,
21
+ type SDKProtocolResult ,
21
22
type ShardingParams ,
22
- SubscribeOptions ,
23
+ type SubscribeOptions ,
23
24
type Unsubscribe
24
25
} from "@waku/interfaces" ;
25
26
import { messageHashStr } from "@waku/message-hash" ;
@@ -39,9 +40,17 @@ type SubscriptionCallback<T extends IDecodedMessage> = {
39
40
callback : Callback < T > ;
40
41
} ;
41
42
43
+ type ReceivedMessageHashes = {
44
+ all : Set < string > ;
45
+ nodes : {
46
+ [ peerId : PeerIdStr ] : Set < string > ;
47
+ } ;
48
+ } ;
49
+
42
50
const log = new Logger ( "sdk:filter" ) ;
43
51
44
52
const DEFAULT_MAX_PINGS = 3 ;
53
+ const DEFAULT_MAX_MISSED_MESSAGES_THRESHOLD = 3 ;
45
54
const DEFAULT_KEEP_ALIVE = 30 * 1000 ;
46
55
47
56
const DEFAULT_SUBSCRIBE_OPTIONS = {
@@ -51,8 +60,11 @@ export class SubscriptionManager implements ISubscriptionSDK {
51
60
private readonly pubsubTopic : PubsubTopic ;
52
61
readonly receivedMessagesHashStr : string [ ] = [ ] ;
53
62
private keepAliveTimer : number | null = null ;
63
+ private readonly receivedMessagesHashes : ReceivedMessageHashes ;
54
64
private peerFailures : Map < string , number > = new Map ( ) ;
65
+ private missedMessagesByPeer : Map < string , number > = new Map ( ) ;
55
66
private maxPingFailures : number = DEFAULT_MAX_PINGS ;
67
+ private maxMissedMessagesThreshold = DEFAULT_MAX_MISSED_MESSAGES_THRESHOLD ;
56
68
57
69
private subscriptionCallbacks : Map <
58
70
ContentTopic ,
@@ -67,14 +79,38 @@ export class SubscriptionManager implements ISubscriptionSDK {
67
79
) {
68
80
this . pubsubTopic = pubsubTopic ;
69
81
this . subscriptionCallbacks = new Map ( ) ;
82
+ const allPeerIdStr = this . getPeers ( ) . map ( ( p ) => p . id . toString ( ) ) ;
83
+ this . receivedMessagesHashes = {
84
+ all : new Set ( ) ,
85
+ nodes : {
86
+ ...Object . fromEntries ( allPeerIdStr . map ( ( peerId ) => [ peerId , new Set ( ) ] ) )
87
+ }
88
+ } ;
89
+ allPeerIdStr . forEach ( ( peerId ) => this . missedMessagesByPeer . set ( peerId , 0 ) ) ;
90
+ }
91
+
92
+ get messageHashes ( ) : string [ ] {
93
+ return [ ...this . receivedMessagesHashes . all ] ;
94
+ }
95
+
96
+ private addHash ( hash : string , peerIdStr ?: string ) : void {
97
+ this . receivedMessagesHashes . all . add ( hash ) ;
98
+
99
+ if ( peerIdStr ) {
100
+ this . receivedMessagesHashes . nodes [ peerIdStr ] . add ( hash ) ;
101
+ }
70
102
}
71
103
72
104
public async subscribe < T extends IDecodedMessage > (
73
105
decoders : IDecoder < T > | IDecoder < T > [ ] ,
74
106
callback : Callback < T > ,
75
107
options : SubscribeOptions = DEFAULT_SUBSCRIBE_OPTIONS
76
108
) : Promise < SDKProtocolResult > {
109
+ this . keepAliveTimer = options . keepAlive || DEFAULT_KEEP_ALIVE ;
77
110
this . maxPingFailures = options . pingsBeforePeerRenewed || DEFAULT_MAX_PINGS ;
111
+ this . maxMissedMessagesThreshold =
112
+ options . maxMissedMessagesThreshold ||
113
+ DEFAULT_MAX_MISSED_MESSAGES_THRESHOLD ;
78
114
79
115
const decodersArray = Array . isArray ( decoders ) ? decoders : [ decoders ] ;
80
116
@@ -146,8 +182,10 @@ export class SubscriptionManager implements ISubscriptionSDK {
146
182
const results = await Promise . allSettled ( promises ) ;
147
183
const finalResult = this . handleResult ( results , "unsubscribe" ) ;
148
184
149
- if ( this . subscriptionCallbacks . size === 0 && this . keepAliveTimer ) {
150
- this . stopKeepAlivePings ( ) ;
185
+ if ( this . subscriptionCallbacks . size === 0 ) {
186
+ if ( this . keepAliveTimer ) {
187
+ this . stopKeepAlivePings ( ) ;
188
+ }
151
189
}
152
190
153
191
return finalResult ;
@@ -180,11 +218,49 @@ export class SubscriptionManager implements ISubscriptionSDK {
180
218
return finalResult ;
181
219
}
182
220
183
- async processIncomingMessage ( message : WakuMessage ) : Promise < void > {
221
+ private async validateMessage ( ) : Promise < void > {
222
+ for ( const hash of this . receivedMessagesHashes . all ) {
223
+ for ( const [ peerIdStr , hashes ] of Object . entries (
224
+ this . receivedMessagesHashes . nodes
225
+ ) ) {
226
+ if ( ! hashes . has ( hash ) ) {
227
+ this . incrementMissedMessageCount ( peerIdStr ) ;
228
+ if ( this . shouldRenewPeer ( peerIdStr ) ) {
229
+ log . info (
230
+ `Peer ${ peerIdStr } has missed too many messages, renewing.`
231
+ ) ;
232
+ const peerId = this . getPeers ( ) . find (
233
+ ( p ) => p . id . toString ( ) === peerIdStr
234
+ ) ?. id ;
235
+ if ( ! peerId ) {
236
+ log . error (
237
+ `Unexpected Error: Peer ${ peerIdStr } not found in connected peers.`
238
+ ) ;
239
+ continue ;
240
+ }
241
+ try {
242
+ await this . renewAndSubscribePeer ( peerId ) ;
243
+ } catch ( error ) {
244
+ log . error ( `Failed to renew peer ${ peerIdStr } : ${ error } ` ) ;
245
+ }
246
+ }
247
+ }
248
+ }
249
+ }
250
+ }
251
+
252
+ async processIncomingMessage (
253
+ message : WakuMessage ,
254
+ peerIdStr : string
255
+ ) : Promise < void > {
184
256
const hashedMessageStr = messageHashStr (
185
257
this . pubsubTopic ,
186
258
message as IProtoMessage
187
259
) ;
260
+
261
+ this . addHash ( hashedMessageStr , peerIdStr ) ;
262
+ void this . validateMessage ( ) ;
263
+
188
264
if ( this . receivedMessagesHashStr . includes ( hashedMessageStr ) ) {
189
265
log . info ( "Message already received, skipping" ) ;
190
266
return ;
@@ -277,15 +353,29 @@ export class SubscriptionManager implements ISubscriptionSDK {
277
353
}
278
354
}
279
355
280
- private async renewAndSubscribePeer ( peerId : PeerId ) : Promise < Peer > {
281
- const newPeer = await this . renewPeer ( peerId ) ;
282
- await this . protocol . subscribe (
283
- this . pubsubTopic ,
284
- newPeer ,
285
- Array . from ( this . subscriptionCallbacks . keys ( ) )
286
- ) ;
356
+ private async renewAndSubscribePeer (
357
+ peerId : PeerId
358
+ ) : Promise < Peer | undefined > {
359
+ try {
360
+ const newPeer = await this . renewPeer ( peerId ) ;
361
+ await this . protocol . subscribe (
362
+ this . pubsubTopic ,
363
+ newPeer ,
364
+ Array . from ( this . subscriptionCallbacks . keys ( ) )
365
+ ) ;
366
+
367
+ this . receivedMessagesHashes . nodes [ newPeer . id . toString ( ) ] = new Set ( ) ;
368
+ this . missedMessagesByPeer . set ( newPeer . id . toString ( ) , 0 ) ;
287
369
288
- return newPeer ;
370
+ return newPeer ;
371
+ } catch ( error ) {
372
+ log . warn ( `Failed to renew peer ${ peerId . toString ( ) } : ${ error } .` ) ;
373
+ return ;
374
+ } finally {
375
+ this . peerFailures . delete ( peerId . toString ( ) ) ;
376
+ this . missedMessagesByPeer . delete ( peerId . toString ( ) ) ;
377
+ delete this . receivedMessagesHashes . nodes [ peerId . toString ( ) ] ;
378
+ }
289
379
}
290
380
291
381
private startKeepAlivePings ( options : SubscribeOptions ) : void {
@@ -312,6 +402,16 @@ export class SubscriptionManager implements ISubscriptionSDK {
312
402
clearInterval ( this . keepAliveTimer ) ;
313
403
this . keepAliveTimer = null ;
314
404
}
405
+
406
+ private incrementMissedMessageCount ( peerIdStr : string ) : void {
407
+ const currentCount = this . missedMessagesByPeer . get ( peerIdStr ) || 0 ;
408
+ this . missedMessagesByPeer . set ( peerIdStr , currentCount + 1 ) ;
409
+ }
410
+
411
+ private shouldRenewPeer ( peerIdStr : string ) : boolean {
412
+ const missedMessages = this . missedMessagesByPeer . get ( peerIdStr ) || 0 ;
413
+ return missedMessages > this . maxMissedMessagesThreshold ;
414
+ }
315
415
}
316
416
317
417
class FilterSDK extends BaseProtocolSDK implements IFilterSDK {
@@ -326,7 +426,7 @@ class FilterSDK extends BaseProtocolSDK implements IFilterSDK {
326
426
) {
327
427
super (
328
428
new FilterCore (
329
- async ( pubsubTopic : PubsubTopic , wakuMessage : WakuMessage ) => {
429
+ async ( pubsubTopic , wakuMessage , peerIdStr ) => {
330
430
const subscription = this . getActiveSubscription ( pubsubTopic ) ;
331
431
if ( ! subscription ) {
332
432
log . error (
@@ -335,7 +435,7 @@ class FilterSDK extends BaseProtocolSDK implements IFilterSDK {
335
435
return ;
336
436
}
337
437
338
- await subscription . processIncomingMessage ( wakuMessage ) ;
438
+ await subscription . processIncomingMessage ( wakuMessage , peerIdStr ) ;
339
439
} ,
340
440
libp2p ,
341
441
options
0 commit comments