@waku/core 0.0.26-7eb3375.0 → 0.0.27

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.
Files changed (68) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/bundle/base_protocol-LhsIWF3-.js +308 -0
  3. package/bundle/{browser-9a6558bb.js → browser-BQyFvtq6.js} +579 -699
  4. package/bundle/index-8YyfzF9R.js +650 -0
  5. package/bundle/index.js +3076 -21759
  6. package/bundle/lib/base_protocol.js +3 -3
  7. package/bundle/lib/message/version_0.js +3 -3
  8. package/bundle/lib/predefined_bootstrap_nodes.js +1 -1
  9. package/bundle/{version_0-7190df43.js → version_0-FXfzO8Km.js} +1368 -2459
  10. package/dist/.tsbuildinfo +1 -1
  11. package/dist/index.d.ts +2 -5
  12. package/dist/index.js +2 -5
  13. package/dist/index.js.map +1 -1
  14. package/dist/lib/base_protocol.d.ts +14 -10
  15. package/dist/lib/base_protocol.js +38 -16
  16. package/dist/lib/base_protocol.js.map +1 -1
  17. package/dist/lib/connection_manager.d.ts +2 -2
  18. package/dist/lib/connection_manager.js +16 -6
  19. package/dist/lib/connection_manager.js.map +1 -1
  20. package/dist/lib/filter/index.d.ts +1 -1
  21. package/dist/lib/filter/index.js +146 -85
  22. package/dist/lib/filter/index.js.map +1 -1
  23. package/dist/lib/filterPeers.d.ts +8 -5
  24. package/dist/lib/filterPeers.js +12 -5
  25. package/dist/lib/filterPeers.js.map +1 -1
  26. package/dist/lib/keep_alive_manager.d.ts +2 -3
  27. package/dist/lib/keep_alive_manager.js +2 -2
  28. package/dist/lib/keep_alive_manager.js.map +1 -1
  29. package/dist/lib/light_push/index.js +3 -7
  30. package/dist/lib/light_push/index.js.map +1 -1
  31. package/dist/lib/message/version_0.d.ts +3 -3
  32. package/dist/lib/message/version_0.js +5 -6
  33. package/dist/lib/message/version_0.js.map +1 -1
  34. package/dist/lib/metadata/index.d.ts +3 -0
  35. package/dist/lib/metadata/index.js +81 -0
  36. package/dist/lib/metadata/index.js.map +1 -0
  37. package/dist/lib/predefined_bootstrap_nodes.js.map +1 -1
  38. package/dist/lib/store/history_rpc.js.map +1 -1
  39. package/dist/lib/store/index.js +1 -4
  40. package/dist/lib/store/index.js.map +1 -1
  41. package/dist/lib/stream_manager.d.ts +2 -2
  42. package/dist/lib/stream_manager.js.map +1 -1
  43. package/dist/lib/wait_for_remote_peer.d.ts +1 -1
  44. package/dist/lib/wait_for_remote_peer.js +40 -10
  45. package/dist/lib/wait_for_remote_peer.js.map +1 -1
  46. package/package.json +129 -1
  47. package/src/index.ts +3 -6
  48. package/src/lib/base_protocol.ts +71 -24
  49. package/src/lib/connection_manager.ts +17 -10
  50. package/src/lib/filter/index.ts +237 -139
  51. package/src/lib/filterPeers.ts +15 -7
  52. package/src/lib/keep_alive_manager.ts +4 -5
  53. package/src/lib/light_push/index.ts +11 -11
  54. package/src/lib/message/version_0.ts +16 -8
  55. package/src/lib/metadata/index.ts +142 -0
  56. package/src/lib/store/index.ts +3 -7
  57. package/src/lib/stream_manager.ts +2 -3
  58. package/src/lib/wait_for_remote_peer.ts +58 -12
  59. package/bundle/base_protocol-46017f51.js +0 -468
  60. package/bundle/index-7581d519.js +0 -31
  61. package/dist/lib/constants.d.ts +0 -4
  62. package/dist/lib/constants.js +0 -5
  63. package/dist/lib/constants.js.map +0 -1
  64. package/dist/lib/waku.d.ts +0 -57
  65. package/dist/lib/waku.js +0 -124
  66. package/dist/lib/waku.js.map +0 -1
  67. package/src/lib/constants.ts +0 -4
  68. package/src/lib/waku.ts +0 -204
@@ -1,6 +1,6 @@
1
- import { Stream } from "@libp2p/interface/connection";
2
- import type { Peer } from "@libp2p/interface/peer-store";
3
- import type { IncomingStreamData } from "@libp2p/interface-internal/registrar";
1
+ import { Stream } from "@libp2p/interface";
2
+ import type { Peer } from "@libp2p/interface";
3
+ import type { IncomingStreamData } from "@libp2p/interface-internal";
4
4
  import type {
5
5
  Callback,
6
6
  ContentTopic,
@@ -11,15 +11,18 @@ import type {
11
11
  IProtoMessage,
12
12
  IReceiver,
13
13
  Libp2p,
14
- PeerIdStr,
15
14
  ProtocolCreateOptions,
16
15
  PubsubTopic,
16
+ SingleShardInfo,
17
17
  Unsubscribe
18
18
  } from "@waku/interfaces";
19
+ import { DefaultPubsubTopic } from "@waku/interfaces";
20
+ import { messageHashStr } from "@waku/message-hash";
19
21
  import { WakuMessage } from "@waku/proto";
20
22
  import {
21
23
  ensurePubsubTopicIsConfigured,
22
24
  groupByContentTopic,
25
+ singleShardInfoToPubsubTopic,
23
26
  toAsyncIterator
24
27
  } from "@waku/utils";
25
28
  import { Logger } from "@waku/utils";
@@ -28,7 +31,6 @@ import * as lp from "it-length-prefixed";
28
31
  import { pipe } from "it-pipe";
29
32
 
30
33
  import { BaseProtocol } from "../base_protocol.js";
31
- import { DefaultPubsubTopic } from "../constants.js";
32
34
 
33
35
  import {
34
36
  FilterPushRpc,
@@ -48,10 +50,14 @@ export const FilterCodecs = {
48
50
  PUSH: "/vac/waku/filter-push/2.0.0-beta1"
49
51
  };
50
52
 
53
+ /**
54
+ * A subscription object refers to a subscription to a given pubsub topic.
55
+ */
51
56
  class Subscription {
52
- private readonly peer: Peer;
57
+ readonly peers: Peer[];
53
58
  private readonly pubsubTopic: PubsubTopic;
54
59
  private newStream: (peer: Peer) => Promise<Stream>;
60
+ readonly receivedMessagesHashStr: string[] = [];
55
61
 
56
62
  private subscriptionCallbacks: Map<
57
63
  ContentTopic,
@@ -60,10 +66,10 @@ class Subscription {
60
66
 
61
67
  constructor(
62
68
  pubsubTopic: PubsubTopic,
63
- remotePeer: Peer,
69
+ remotePeers: Peer[],
64
70
  newStream: (peer: Peer) => Promise<Stream>
65
71
  ) {
66
- this.peer = remotePeer;
72
+ this.peers = remotePeers;
67
73
  this.pubsubTopic = pubsubTopic;
68
74
  this.newStream = newStream;
69
75
  this.subscriptionCallbacks = new Map();
@@ -87,53 +93,59 @@ class Subscription {
87
93
  const decodersGroupedByCT = groupByContentTopic(decodersArray);
88
94
  const contentTopics = Array.from(decodersGroupedByCT.keys());
89
95
 
90
- const stream = await this.newStream(this.peer);
96
+ const promises = this.peers.map(async (peer) => {
97
+ const stream = await this.newStream(peer);
91
98
 
92
- const request = FilterSubscribeRpc.createSubscribeRequest(
93
- this.pubsubTopic,
94
- contentTopics
95
- );
96
-
97
- try {
98
- const res = await pipe(
99
- [request.encode()],
100
- lp.encode,
101
- stream,
102
- lp.decode,
103
- async (source) => await all(source)
99
+ const request = FilterSubscribeRpc.createSubscribeRequest(
100
+ this.pubsubTopic,
101
+ contentTopics
104
102
  );
105
103
 
106
- if (!res || !res.length) {
107
- throw Error(
108
- `No response received for request ${request.requestId}: ${res}`
104
+ try {
105
+ const res = await pipe(
106
+ [request.encode()],
107
+ lp.encode,
108
+ stream,
109
+ lp.decode,
110
+ async (source) => await all(source)
109
111
  );
110
- }
111
112
 
112
- const { statusCode, requestId, statusDesc } =
113
- FilterSubscribeResponse.decode(res[0].slice());
113
+ if (!res || !res.length) {
114
+ throw Error(
115
+ `No response received for request ${request.requestId}: ${res}`
116
+ );
117
+ }
118
+
119
+ const { statusCode, requestId, statusDesc } =
120
+ FilterSubscribeResponse.decode(res[0].slice());
121
+
122
+ if (statusCode < 200 || statusCode >= 300) {
123
+ throw new Error(
124
+ `Filter subscribe request ${requestId} failed with status code ${statusCode}: ${statusDesc}`
125
+ );
126
+ }
114
127
 
115
- if (statusCode < 200 || statusCode >= 300) {
128
+ log.info(
129
+ "Subscribed to peer ",
130
+ peer.id.toString(),
131
+ "for content topics",
132
+ contentTopics
133
+ );
134
+ } catch (e) {
116
135
  throw new Error(
117
- `Filter subscribe request ${requestId} failed with status code ${statusCode}: ${statusDesc}`
136
+ "Error subscribing to peer: " +
137
+ peer.id.toString() +
138
+ " for content topics: " +
139
+ contentTopics +
140
+ ": " +
141
+ e
118
142
  );
119
143
  }
144
+ });
120
145
 
121
- log.info(
122
- "Subscribed to peer ",
123
- this.peer.id.toString(),
124
- "for content topics",
125
- contentTopics
126
- );
127
- } catch (e) {
128
- throw new Error(
129
- "Error subscribing to peer: " +
130
- this.peer.id.toString() +
131
- " for content topics: " +
132
- contentTopics +
133
- ": " +
134
- e
135
- );
136
- }
146
+ const results = await Promise.allSettled(promises);
147
+
148
+ this.handleErrors(results, "subscribe");
137
149
 
138
150
  // Save the callback functions by content topics so they
139
151
  // can easily be removed (reciprocally replaced) if `unsubscribe` (reciprocally `subscribe`)
@@ -153,133 +165,206 @@ class Subscription {
153
165
  }
154
166
 
155
167
  async unsubscribe(contentTopics: ContentTopic[]): Promise<void> {
156
- const stream = await this.newStream(this.peer);
157
- const unsubscribeRequest = FilterSubscribeRpc.createUnsubscribeRequest(
158
- this.pubsubTopic,
159
- contentTopics
160
- );
168
+ const promises = this.peers.map(async (peer) => {
169
+ const stream = await this.newStream(peer);
170
+ const unsubscribeRequest = FilterSubscribeRpc.createUnsubscribeRequest(
171
+ this.pubsubTopic,
172
+ contentTopics
173
+ );
161
174
 
162
- try {
163
- await pipe([unsubscribeRequest.encode()], lp.encode, stream.sink);
164
- } catch (error) {
165
- throw new Error("Error subscribing: " + error);
166
- }
175
+ try {
176
+ await pipe([unsubscribeRequest.encode()], lp.encode, stream.sink);
177
+ } catch (error) {
178
+ throw new Error("Error unsubscribing: " + error);
179
+ }
167
180
 
168
- contentTopics.forEach((contentTopic: string) => {
169
- this.subscriptionCallbacks.delete(contentTopic);
181
+ contentTopics.forEach((contentTopic: string) => {
182
+ this.subscriptionCallbacks.delete(contentTopic);
183
+ });
170
184
  });
185
+
186
+ const results = await Promise.allSettled(promises);
187
+
188
+ this.handleErrors(results, "unsubscribe");
171
189
  }
172
190
 
173
191
  async ping(): Promise<void> {
174
- const stream = await this.newStream(this.peer);
192
+ const promises = this.peers.map(async (peer) => {
193
+ const stream = await this.newStream(peer);
194
+
195
+ const request = FilterSubscribeRpc.createSubscriberPingRequest();
196
+
197
+ try {
198
+ const res = await pipe(
199
+ [request.encode()],
200
+ lp.encode,
201
+ stream,
202
+ lp.decode,
203
+ async (source) => await all(source)
204
+ );
175
205
 
176
- const request = FilterSubscribeRpc.createSubscriberPingRequest();
206
+ if (!res || !res.length) {
207
+ throw Error(
208
+ `No response received for request ${request.requestId}: ${res}`
209
+ );
210
+ }
177
211
 
178
- try {
179
- const res = await pipe(
180
- [request.encode()],
181
- lp.encode,
182
- stream,
183
- lp.decode,
184
- async (source) => await all(source)
185
- );
212
+ const { statusCode, requestId, statusDesc } =
213
+ FilterSubscribeResponse.decode(res[0].slice());
186
214
 
187
- if (!res || !res.length) {
188
- throw Error(
189
- `No response received for request ${request.requestId}: ${res}`
190
- );
215
+ if (statusCode < 200 || statusCode >= 300) {
216
+ throw new Error(
217
+ `Filter ping request ${requestId} failed with status code ${statusCode}: ${statusDesc}`
218
+ );
219
+ }
220
+ log.info(`Ping successful for peer ${peer.id.toString()}`);
221
+ } catch (error) {
222
+ log.error("Error pinging: ", error);
223
+ throw error; // Rethrow the actual error instead of wrapping it
191
224
  }
225
+ });
192
226
 
193
- const { statusCode, requestId, statusDesc } =
194
- FilterSubscribeResponse.decode(res[0].slice());
195
-
196
- if (statusCode < 200 || statusCode >= 300) {
197
- throw new Error(
198
- `Filter ping request ${requestId} failed with status code ${statusCode}: ${statusDesc}`
199
- );
200
- }
227
+ const results = await Promise.allSettled(promises);
201
228
 
202
- log.info("Ping successful");
203
- } catch (error) {
204
- log.error("Error pinging: ", error);
205
- throw new Error("Error pinging: " + error);
206
- }
229
+ this.handleErrors(results, "ping");
207
230
  }
208
231
 
209
232
  async unsubscribeAll(): Promise<void> {
210
- const stream = await this.newStream(this.peer);
211
-
212
- const request = FilterSubscribeRpc.createUnsubscribeAllRequest(
213
- this.pubsubTopic
214
- );
233
+ const promises = this.peers.map(async (peer) => {
234
+ const stream = await this.newStream(peer);
215
235
 
216
- try {
217
- const res = await pipe(
218
- [request.encode()],
219
- lp.encode,
220
- stream,
221
- lp.decode,
222
- async (source) => await all(source)
236
+ const request = FilterSubscribeRpc.createUnsubscribeAllRequest(
237
+ this.pubsubTopic
223
238
  );
224
239
 
225
- if (!res || !res.length) {
226
- throw Error(
227
- `No response received for request ${request.requestId}: ${res}`
240
+ try {
241
+ const res = await pipe(
242
+ [request.encode()],
243
+ lp.encode,
244
+ stream,
245
+ lp.decode,
246
+ async (source) => await all(source)
228
247
  );
229
- }
230
248
 
231
- const { statusCode, requestId, statusDesc } =
232
- FilterSubscribeResponse.decode(res[0].slice());
249
+ if (!res || !res.length) {
250
+ throw Error(
251
+ `No response received for request ${request.requestId}: ${res}`
252
+ );
253
+ }
254
+
255
+ const { statusCode, requestId, statusDesc } =
256
+ FilterSubscribeResponse.decode(res[0].slice());
233
257
 
234
- if (statusCode < 200 || statusCode >= 300) {
258
+ if (statusCode < 200 || statusCode >= 300) {
259
+ throw new Error(
260
+ `Filter unsubscribe all request ${requestId} failed with status code ${statusCode}: ${statusDesc}`
261
+ );
262
+ }
263
+
264
+ this.subscriptionCallbacks.clear();
265
+ log.info(
266
+ `Unsubscribed from all content topics for pubsub topic ${this.pubsubTopic}`
267
+ );
268
+ } catch (error) {
235
269
  throw new Error(
236
- `Filter unsubscribe all request ${requestId} failed with status code ${statusCode}: ${statusDesc}`
270
+ "Error unsubscribing from all content topics: " + error
237
271
  );
238
272
  }
273
+ });
239
274
 
240
- this.subscriptionCallbacks.clear();
241
- log.info("Unsubscribed from all content topics");
242
- } catch (error) {
243
- throw new Error("Error unsubscribing from all content topics: " + error);
244
- }
275
+ const results = await Promise.allSettled(promises);
276
+
277
+ this.handleErrors(results, "unsubscribeAll");
245
278
  }
246
279
 
247
280
  async processMessage(message: WakuMessage): Promise<void> {
248
- const contentTopic = message.contentTopic;
281
+ const hashedMessageStr = messageHashStr(
282
+ this.pubsubTopic,
283
+ message as IProtoMessage
284
+ );
285
+ if (this.receivedMessagesHashStr.includes(hashedMessageStr)) {
286
+ log.info("Message already received, skipping");
287
+ return;
288
+ }
289
+ this.receivedMessagesHashStr.push(hashedMessageStr);
290
+
291
+ const { contentTopic } = message;
249
292
  const subscriptionCallback = this.subscriptionCallbacks.get(contentTopic);
250
293
  if (!subscriptionCallback) {
251
294
  log.error("No subscription callback available for ", contentTopic);
252
295
  return;
253
296
  }
297
+ log.info(
298
+ "Processing message with content topic ",
299
+ contentTopic,
300
+ " on pubsub topic ",
301
+ this.pubsubTopic
302
+ );
254
303
  await pushMessage(subscriptionCallback, this.pubsubTopic, message);
255
304
  }
305
+
306
+ // Filter out only the rejected promises and extract & handle their reasons
307
+ private handleErrors(
308
+ results: PromiseSettledResult<void>[],
309
+ type: "ping" | "subscribe" | "unsubscribe" | "unsubscribeAll"
310
+ ): void {
311
+ const errors = results
312
+ .filter(
313
+ (result): result is PromiseRejectedResult =>
314
+ result.status === "rejected"
315
+ )
316
+ .map((rejectedResult) => rejectedResult.reason);
317
+
318
+ if (errors.length === this.peers.length) {
319
+ const errorCounts = new Map<string, number>();
320
+ // TODO: streamline error logging with https://github.com/orgs/waku-org/projects/2/views/1?pane=issue&itemId=42849952
321
+ errors.forEach((error) => {
322
+ const message = error instanceof Error ? error.message : String(error);
323
+ errorCounts.set(message, (errorCounts.get(message) || 0) + 1);
324
+ });
325
+
326
+ const uniqueErrorMessages = Array.from(
327
+ errorCounts,
328
+ ([message, count]) => `${message} (occurred ${count} times)`
329
+ ).join(", ");
330
+ throw new Error(`Error ${type} all peers: ${uniqueErrorMessages}`);
331
+ } else if (errors.length > 0) {
332
+ // TODO: handle renewing faulty peers with new peers (https://github.com/waku-org/js-waku/issues/1463)
333
+ log.warn(
334
+ `Some ${type} failed. These will be refreshed with new peers`,
335
+ errors
336
+ );
337
+ } else {
338
+ log.info(`${type} successful for all peers`);
339
+ }
340
+ }
256
341
  }
257
342
 
258
343
  class Filter extends BaseProtocol implements IReceiver {
259
- private readonly pubsubTopics: PubsubTopic[] = [];
260
344
  private activeSubscriptions = new Map<string, Subscription>();
261
- private readonly NUM_PEERS_PROTOCOL = 1;
262
345
 
263
346
  private getActiveSubscription(
264
- pubsubTopic: PubsubTopic,
265
- peerIdStr: PeerIdStr
347
+ pubsubTopic: PubsubTopic
266
348
  ): Subscription | undefined {
267
- return this.activeSubscriptions.get(`${pubsubTopic}_${peerIdStr}`);
349
+ return this.activeSubscriptions.get(pubsubTopic);
268
350
  }
269
351
 
270
352
  private setActiveSubscription(
271
353
  pubsubTopic: PubsubTopic,
272
- peerIdStr: PeerIdStr,
273
354
  subscription: Subscription
274
355
  ): Subscription {
275
- this.activeSubscriptions.set(`${pubsubTopic}_${peerIdStr}`, subscription);
356
+ this.activeSubscriptions.set(pubsubTopic, subscription);
276
357
  return subscription;
277
358
  }
278
359
 
279
360
  constructor(libp2p: Libp2p, options?: ProtocolCreateOptions) {
280
- super(FilterCodecs.SUBSCRIBE, libp2p.components);
281
-
282
- this.pubsubTopics = options?.pubsubTopics || [DefaultPubsubTopic];
361
+ super(
362
+ FilterCodecs.SUBSCRIBE,
363
+ libp2p.components,
364
+ log,
365
+ options!.pubsubTopics!,
366
+ options
367
+ );
283
368
 
284
369
  libp2p.handle(FilterCodecs.PUSH, this.onRequest.bind(this)).catch((e) => {
285
370
  log.error("Failed to register ", FilterCodecs.PUSH, e);
@@ -288,26 +373,40 @@ class Filter extends BaseProtocol implements IReceiver {
288
373
  this.activeSubscriptions = new Map();
289
374
  }
290
375
 
376
+ /**
377
+ * Creates a new subscription to the given pubsub topic.
378
+ * The subscription is made to multiple peers for decentralization.
379
+ * @param pubsubTopicShardInfo The pubsub topic to subscribe to.
380
+ * @returns The subscription object.
381
+ */
291
382
  async createSubscription(
292
- pubsubTopic: string = DefaultPubsubTopic
383
+ pubsubTopicShardInfo: SingleShardInfo | PubsubTopic = DefaultPubsubTopic
293
384
  ): Promise<Subscription> {
385
+ const pubsubTopic =
386
+ typeof pubsubTopicShardInfo == "string"
387
+ ? pubsubTopicShardInfo
388
+ : singleShardInfoToPubsubTopic(pubsubTopicShardInfo);
389
+
294
390
  ensurePubsubTopicIsConfigured(pubsubTopic, this.pubsubTopics);
295
391
 
296
- //TODO: get a relevant peer for the topic/shard
297
- // https://github.com/waku-org/js-waku/pull/1586#discussion_r1336428230
298
- const peer = (
299
- await this.getPeers({
300
- maxBootstrapPeers: 1,
301
- numPeers: this.NUM_PEERS_PROTOCOL
302
- })
303
- )[0];
392
+ const peers = await this.getPeers({
393
+ maxBootstrapPeers: 1,
394
+ numPeers: this.numPeersToUse
395
+ });
396
+ if (peers.length === 0) {
397
+ throw new Error("No peer found to initiate subscription.");
398
+ }
399
+
400
+ log.info(
401
+ `Creating filter subscription with ${peers.length} peers: `,
402
+ peers.map((peer) => peer.id.toString())
403
+ );
304
404
 
305
405
  const subscription =
306
- this.getActiveSubscription(pubsubTopic, peer.id.toString()) ??
406
+ this.getActiveSubscription(pubsubTopic) ??
307
407
  this.setActiveSubscription(
308
408
  pubsubTopic,
309
- peer.id.toString(),
310
- new Subscription(pubsubTopic, peer, this.getStream.bind(this, peer))
409
+ new Subscription(pubsubTopic, peers, this.getStream.bind(this))
311
410
  );
312
411
 
313
412
  return subscription;
@@ -354,8 +453,11 @@ class Filter extends BaseProtocol implements IReceiver {
354
453
  }
355
454
 
356
455
  private onRequest(streamData: IncomingStreamData): void {
456
+ const { connection, stream } = streamData;
457
+ const { remotePeer } = connection;
458
+ log.info(`Received message from ${remotePeer.toString()}`);
357
459
  try {
358
- pipe(streamData.stream, lp.decode, async (source) => {
460
+ pipe(stream, lp.decode, async (source) => {
359
461
  for await (const bytes of source) {
360
462
  const response = FilterPushRpc.decode(bytes.slice());
361
463
 
@@ -371,11 +473,7 @@ class Filter extends BaseProtocol implements IReceiver {
371
473
  return;
372
474
  }
373
475
 
374
- const peerIdStr = streamData.connection.remotePeer.toString();
375
- const subscription = this.getActiveSubscription(
376
- pubsubTopic,
377
- peerIdStr
378
- );
476
+ const subscription = this.getActiveSubscription(pubsubTopic);
379
477
 
380
478
  if (!subscription) {
381
479
  log.error(
@@ -401,7 +499,7 @@ class Filter extends BaseProtocol implements IReceiver {
401
499
  }
402
500
 
403
501
  export function wakuFilter(
404
- init: Partial<ProtocolCreateOptions> = {}
502
+ init: ProtocolCreateOptions = { pubsubTopics: [] }
405
503
  ): (libp2p: Libp2p) => IFilter {
406
504
  return (libp2p: Libp2p) => new Filter(libp2p, init);
407
505
  }
@@ -1,24 +1,32 @@
1
- import { Peer } from "@libp2p/interface/peer-store";
1
+ import { Peer } from "@libp2p/interface";
2
2
  import { Tags } from "@waku/interfaces";
3
3
 
4
4
  /**
5
- * Retrieves a list of peers based on the specified criteria.
5
+ * Retrieves a list of peers based on the specified criteria:
6
+ * 1. If numPeers is 0, return all peers
7
+ * 2. Bootstrap peers are prioritized
8
+ * 3. Non-bootstrap peers are randomly selected to fill up to numPeers
6
9
  *
7
10
  * @param peers - The list of peers to filter from.
8
- * @param numPeers - The total number of peers to retrieve. If 0, all peers are returned.
11
+ * @param numPeers - The total number of peers to retrieve. If 0, all peers are returned, irrespective of `maxBootstrapPeers`.
9
12
  * @param maxBootstrapPeers - The maximum number of bootstrap peers to retrieve.
10
- * @returns A Promise that resolves to an array of peers based on the specified criteria.
13
+ * @returns An array of peers based on the specified criteria.
11
14
  */
12
- export async function filterPeers(
15
+ export function filterPeersByDiscovery(
13
16
  peers: Peer[],
14
17
  numPeers: number,
15
18
  maxBootstrapPeers: number
16
- ): Promise<Peer[]> {
19
+ ): Peer[] {
17
20
  // Collect the bootstrap peers up to the specified maximum
18
- const bootstrapPeers = peers
21
+ let bootstrapPeers = peers
19
22
  .filter((peer) => peer.tags.has(Tags.BOOTSTRAP))
20
23
  .slice(0, maxBootstrapPeers);
21
24
 
25
+ // If numPeers is less than the number of bootstrap peers, adjust the bootstrapPeers array
26
+ if (numPeers > 0 && numPeers < bootstrapPeers.length) {
27
+ bootstrapPeers = bootstrapPeers.slice(0, numPeers);
28
+ }
29
+
22
30
  // Collect non-bootstrap peers
23
31
  const nonBootstrapPeers = peers.filter(
24
32
  (peer) => !peer.tags.has(Tags.BOOTSTRAP)
@@ -1,10 +1,9 @@
1
- import type { PeerId } from "@libp2p/interface/peer-id";
2
- import type { PeerStore } from "@libp2p/interface/peer-store";
1
+ import type { PeerId, PeerStore } from "@libp2p/interface";
2
+ import type { PingService } from "@libp2p/ping";
3
3
  import type { IRelay, PeerIdStr } from "@waku/interfaces";
4
4
  import type { KeepAliveOptions } from "@waku/interfaces";
5
- import { Logger } from "@waku/utils";
5
+ import { Logger, pubsubTopicToSingleShardInfo } from "@waku/utils";
6
6
  import { utf8ToBytes } from "@waku/utils/bytes";
7
- import type { PingService } from "libp2p/ping";
8
7
 
9
8
  import { createEncoder } from "./message/version_0.js";
10
9
 
@@ -129,7 +128,7 @@ export class KeepAliveManager {
129
128
  if (!meshPeers.includes(peerIdStr)) continue;
130
129
 
131
130
  const encoder = createEncoder({
132
- pubsubTopic: topic,
131
+ pubsubTopicShardInfo: pubsubTopicToSingleShardInfo(topic),
133
132
  contentTopic: RelayPingContentTopic,
134
133
  ephemeral: true
135
134
  });
@@ -1,12 +1,10 @@
1
- import type { Stream } from "@libp2p/interface/connection";
2
- import type { PeerId } from "@libp2p/interface/peer-id";
1
+ import type { PeerId, Stream } from "@libp2p/interface";
3
2
  import {
4
3
  IEncoder,
5
4
  ILightPush,
6
5
  IMessage,
7
6
  Libp2p,
8
7
  ProtocolCreateOptions,
9
- PubsubTopic,
10
8
  SendError,
11
9
  SendResult
12
10
  } from "@waku/interfaces";
@@ -22,7 +20,6 @@ import { pipe } from "it-pipe";
22
20
  import { Uint8ArrayList } from "uint8arraylist";
23
21
 
24
22
  import { BaseProtocol } from "../base_protocol.js";
25
- import { DefaultPubsubTopic } from "../constants.js";
26
23
 
27
24
  import { PushRpc } from "./push_rpc.js";
28
25
 
@@ -45,12 +42,14 @@ type PreparePushMessageResult =
45
42
  * Implements the [Waku v2 Light Push protocol](https://rfc.vac.dev/spec/19/).
46
43
  */
47
44
  class LightPush extends BaseProtocol implements ILightPush {
48
- private readonly pubsubTopics: PubsubTopic[];
49
- private readonly NUM_PEERS_PROTOCOL = 1;
50
-
51
45
  constructor(libp2p: Libp2p, options?: ProtocolCreateOptions) {
52
- super(LightPushCodec, libp2p.components);
53
- this.pubsubTopics = options?.pubsubTopics ?? [DefaultPubsubTopic];
46
+ super(
47
+ LightPushCodec,
48
+ libp2p.components,
49
+ log,
50
+ options!.pubsubTopics!,
51
+ options
52
+ );
54
53
  }
55
54
 
56
55
  private async preparePushMessage(
@@ -109,10 +108,9 @@ class LightPush extends BaseProtocol implements ILightPush {
109
108
  };
110
109
  }
111
110
 
112
- //TODO: get a relevant peer for the topic/shard
113
111
  const peers = await this.getPeers({
114
112
  maxBootstrapPeers: 1,
115
- numPeers: this.NUM_PEERS_PROTOCOL
113
+ numPeers: this.numPeersToUse
116
114
  });
117
115
 
118
116
  if (!peers.length) {
@@ -178,6 +176,8 @@ class LightPush extends BaseProtocol implements ILightPush {
178
176
  });
179
177
 
180
178
  const results = await Promise.allSettled(promises);
179
+
180
+ // TODO: handle renewing faulty peers with new peers (https://github.com/waku-org/js-waku/issues/1463)
181
181
  const errors = results
182
182
  .filter(
183
183
  (