@waku/core 0.0.23 → 0.0.25-a42b7be.0
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.
- package/CHANGELOG.md +34 -0
- package/bundle/{base_protocol-84d9b670.js → base_protocol-4bcf7514.js} +194 -134
- package/bundle/{browser-bde977a3.js → browser-90197c87.js} +26 -1
- package/bundle/index-27b91e3b.js +31 -0
- package/bundle/index.js +19384 -2421
- package/bundle/lib/base_protocol.js +3 -2
- package/bundle/lib/message/version_0.js +3 -2
- package/bundle/lib/predefined_bootstrap_nodes.js +2 -0
- package/bundle/{version_0-74b4b9db.js → version_0-2f1176e3.js} +36 -24
- package/dist/.tsbuildinfo +1 -1
- package/dist/lib/connection_manager.d.ts +17 -4
- package/dist/lib/connection_manager.js +111 -45
- package/dist/lib/connection_manager.js.map +1 -1
- package/dist/lib/filter/index.js +53 -37
- package/dist/lib/filter/index.js.map +1 -1
- package/dist/lib/keep_alive_manager.d.ts +1 -0
- package/dist/lib/keep_alive_manager.js +42 -18
- package/dist/lib/keep_alive_manager.js.map +1 -1
- package/dist/lib/light_push/index.js +60 -38
- package/dist/lib/light_push/index.js.map +1 -1
- package/dist/lib/light_push/push_rpc.d.ts +1 -1
- package/dist/lib/light_push/push_rpc.js +2 -2
- package/dist/lib/message/version_0.d.ts +13 -13
- package/dist/lib/message/version_0.js +22 -20
- package/dist/lib/message/version_0.js.map +1 -1
- package/dist/lib/store/history_rpc.d.ts +1 -1
- package/dist/lib/store/history_rpc.js +1 -1
- package/dist/lib/store/index.d.ts +1 -1
- package/dist/lib/store/index.js +43 -17
- package/dist/lib/store/index.js.map +1 -1
- package/dist/lib/stream_manager.d.ts +1 -1
- package/dist/lib/stream_manager.js +8 -5
- package/dist/lib/stream_manager.js.map +1 -1
- package/dist/lib/wait_for_remote_peer.d.ts +2 -2
- package/dist/lib/wait_for_remote_peer.js +13 -11
- package/dist/lib/wait_for_remote_peer.js.map +1 -1
- package/dist/lib/waku.d.ts +4 -3
- package/dist/lib/waku.js +13 -11
- package/dist/lib/waku.js.map +1 -1
- package/package.json +1 -137
- package/src/lib/connection_manager.ts +156 -51
- package/src/lib/filter/index.ts +76 -40
- package/src/lib/keep_alive_manager.ts +53 -20
- package/src/lib/light_push/index.ts +74 -38
- package/src/lib/light_push/push_rpc.ts +2 -2
- package/src/lib/message/version_0.ts +25 -17
- package/src/lib/store/history_rpc.ts +2 -2
- package/src/lib/store/index.ts +60 -23
- package/src/lib/stream_manager.ts +12 -7
- package/src/lib/wait_for_remote_peer.ts +13 -11
- package/src/lib/waku.ts +12 -9
- package/dist/lib/push_or_init_map.d.ts +0 -1
- package/dist/lib/push_or_init_map.js +0 -9
- package/dist/lib/push_or_init_map.js.map +0 -1
- package/src/lib/push_or_init_map.ts +0 -13
@@ -1,7 +1,9 @@
|
|
1
1
|
import type { PeerId } from "@libp2p/interface/peer-id";
|
2
2
|
import type { PeerInfo } from "@libp2p/interface/peer-info";
|
3
3
|
import type { Peer } from "@libp2p/interface/peer-store";
|
4
|
+
import type { PeerStore } from "@libp2p/interface/peer-store";
|
4
5
|
import { CustomEvent, EventEmitter } from "@libp2p/interfaces/events";
|
6
|
+
import { decodeRelayShard } from "@waku/enr";
|
5
7
|
import {
|
6
8
|
ConnectionManagerOptions,
|
7
9
|
EPeersByDiscoveryEvents,
|
@@ -9,14 +11,17 @@ import {
|
|
9
11
|
IPeersByDiscoveryEvents,
|
10
12
|
IRelay,
|
11
13
|
KeepAliveOptions,
|
12
|
-
PeersByDiscoveryResult
|
14
|
+
PeersByDiscoveryResult,
|
15
|
+
PubSubTopic,
|
16
|
+
ShardInfo
|
13
17
|
} from "@waku/interfaces";
|
14
18
|
import { Libp2p, Tags } from "@waku/interfaces";
|
15
|
-
import
|
19
|
+
import { shardInfoToPubSubTopics } from "@waku/utils";
|
20
|
+
import { Logger } from "@waku/utils";
|
16
21
|
|
17
22
|
import { KeepAliveManager } from "./keep_alive_manager.js";
|
18
23
|
|
19
|
-
const log =
|
24
|
+
const log = new Logger("connection-manager");
|
20
25
|
|
21
26
|
export const DEFAULT_MAX_BOOTSTRAP_PEERS_ALLOWED = 1;
|
22
27
|
export const DEFAULT_MAX_DIAL_ATTEMPTS_FOR_PEER = 3;
|
@@ -33,13 +38,14 @@ export class ConnectionManager
|
|
33
38
|
private dialAttemptsForPeer: Map<string, number> = new Map();
|
34
39
|
private dialErrorsForPeer: Map<string, any> = new Map();
|
35
40
|
|
36
|
-
private
|
41
|
+
private currentActiveParallelDialCount = 0;
|
37
42
|
private pendingPeerDialQueue: Array<PeerId> = [];
|
38
43
|
|
39
44
|
public static create(
|
40
45
|
peerId: string,
|
41
46
|
libp2p: Libp2p,
|
42
47
|
keepAliveOptions: KeepAliveOptions,
|
48
|
+
pubsubTopics: PubSubTopic[],
|
43
49
|
relay?: IRelay,
|
44
50
|
options?: ConnectionManagerOptions
|
45
51
|
): ConnectionManager {
|
@@ -48,6 +54,7 @@ export class ConnectionManager
|
|
48
54
|
instance = new ConnectionManager(
|
49
55
|
libp2p,
|
50
56
|
keepAliveOptions,
|
57
|
+
pubsubTopics,
|
51
58
|
relay,
|
52
59
|
options
|
53
60
|
);
|
@@ -104,11 +111,13 @@ export class ConnectionManager
|
|
104
111
|
private constructor(
|
105
112
|
libp2p: Libp2p,
|
106
113
|
keepAliveOptions: KeepAliveOptions,
|
114
|
+
private configuredPubSubTopics: PubSubTopic[],
|
107
115
|
relay?: IRelay,
|
108
116
|
options?: Partial<ConnectionManagerOptions>
|
109
117
|
) {
|
110
118
|
super();
|
111
119
|
this.libp2p = libp2p;
|
120
|
+
this.configuredPubSubTopics = configuredPubSubTopics;
|
112
121
|
this.options = {
|
113
122
|
maxDialAttemptsForPeer: DEFAULT_MAX_DIAL_ATTEMPTS_FOR_PEER,
|
114
123
|
maxBootstrapPeersAllowed: DEFAULT_MAX_BOOTSTRAP_PEERS_ALLOWED,
|
@@ -119,14 +128,16 @@ export class ConnectionManager
|
|
119
128
|
this.keepAliveManager = new KeepAliveManager(keepAliveOptions, relay);
|
120
129
|
|
121
130
|
this.run()
|
122
|
-
.then(() => log(`Connection Manager is now running`))
|
123
|
-
.catch((error) =>
|
131
|
+
.then(() => log.info(`Connection Manager is now running`))
|
132
|
+
.catch((error) =>
|
133
|
+
log.error(`Unexpected error while running service`, error)
|
134
|
+
);
|
124
135
|
|
125
136
|
// libp2p emits `peer:discovery` events during its initialization
|
126
137
|
// which means that before the ConnectionManager is initialized, some peers may have been discovered
|
127
138
|
// we will dial the peers in peerStore ONCE before we start to listen to the `peer:discovery` events within the ConnectionManager
|
128
139
|
this.dialPeerStorePeers().catch((error) =>
|
129
|
-
log(`Unexpected error while dialing peer store peers`, error)
|
140
|
+
log.error(`Unexpected error while dialing peer store peers`, error)
|
130
141
|
);
|
131
142
|
}
|
132
143
|
|
@@ -144,7 +155,7 @@ export class ConnectionManager
|
|
144
155
|
try {
|
145
156
|
await Promise.all(dialPromises);
|
146
157
|
} catch (error) {
|
147
|
-
log(`Unexpected error while dialing peer store peers`, error);
|
158
|
+
log.error(`Unexpected error while dialing peer store peers`, error);
|
148
159
|
}
|
149
160
|
}
|
150
161
|
|
@@ -172,11 +183,13 @@ export class ConnectionManager
|
|
172
183
|
}
|
173
184
|
|
174
185
|
private async dialPeer(peerId: PeerId): Promise<void> {
|
175
|
-
this.
|
186
|
+
this.currentActiveParallelDialCount += 1;
|
176
187
|
let dialAttempt = 0;
|
177
188
|
while (dialAttempt < this.options.maxDialAttemptsForPeer) {
|
178
189
|
try {
|
179
|
-
log
|
190
|
+
log.info(
|
191
|
+
`Dialing peer ${peerId.toString()} on attempt ${dialAttempt + 1}`
|
192
|
+
);
|
180
193
|
await this.libp2p.dial(peerId);
|
181
194
|
|
182
195
|
const tags = await this.getTagNamesForPeer(peerId);
|
@@ -186,16 +199,21 @@ export class ConnectionManager
|
|
186
199
|
conn.tags = Array.from(new Set([...conn.tags, ...tags]));
|
187
200
|
});
|
188
201
|
|
189
|
-
|
202
|
+
// instead of deleting the peer from the peer store, we set the dial attempt to -1
|
203
|
+
// this helps us keep track of peers that have been dialed before
|
204
|
+
this.dialAttemptsForPeer.set(peerId.toString(), -1);
|
205
|
+
|
190
206
|
// Dialing succeeded, break the loop
|
191
207
|
break;
|
192
208
|
} catch (error) {
|
193
209
|
if (error instanceof AggregateError) {
|
194
210
|
// Handle AggregateError
|
195
|
-
log
|
211
|
+
log.error(
|
212
|
+
`Error dialing peer ${peerId.toString()} - ${error.errors}`
|
213
|
+
);
|
196
214
|
} else {
|
197
215
|
// Handle generic error
|
198
|
-
log(
|
216
|
+
log.error(
|
199
217
|
`Error dialing peer ${peerId.toString()} - ${
|
200
218
|
(error as any).message
|
201
219
|
}`
|
@@ -209,7 +227,7 @@ export class ConnectionManager
|
|
209
227
|
}
|
210
228
|
|
211
229
|
// Always decrease the active dial count and process the dial queue
|
212
|
-
this.
|
230
|
+
this.currentActiveParallelDialCount--;
|
213
231
|
this.processDialQueue();
|
214
232
|
|
215
233
|
// If max dial attempts reached and dialing failed, delete the peer
|
@@ -217,16 +235,24 @@ export class ConnectionManager
|
|
217
235
|
try {
|
218
236
|
const error = this.dialErrorsForPeer.get(peerId.toString());
|
219
237
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
238
|
+
if (error) {
|
239
|
+
let errorMessage;
|
240
|
+
if (error instanceof AggregateError) {
|
241
|
+
if (!error.errors) {
|
242
|
+
log.warn(`No errors array found for AggregateError`);
|
243
|
+
} else if (error.errors.length === 0) {
|
244
|
+
log.warn(`Errors array is empty for AggregateError`);
|
245
|
+
} else {
|
246
|
+
errorMessage = JSON.stringify(error.errors[0]);
|
247
|
+
}
|
248
|
+
} else {
|
249
|
+
errorMessage = error.message;
|
250
|
+
}
|
226
251
|
|
227
|
-
|
228
|
-
|
229
|
-
|
252
|
+
log.info(
|
253
|
+
`Deleting undialable peer ${peerId.toString()} from peer store. Reason: ${errorMessage}`
|
254
|
+
);
|
255
|
+
}
|
230
256
|
|
231
257
|
this.dialErrorsForPeer.delete(peerId.toString());
|
232
258
|
await this.libp2p.peerStore.delete(peerId);
|
@@ -242,9 +268,9 @@ export class ConnectionManager
|
|
242
268
|
try {
|
243
269
|
this.keepAliveManager.stop(peerId);
|
244
270
|
await this.libp2p.hangUp(peerId);
|
245
|
-
log(`Dropped connection with peer ${peerId.toString()}`);
|
271
|
+
log.info(`Dropped connection with peer ${peerId.toString()}`);
|
246
272
|
} catch (error) {
|
247
|
-
log(
|
273
|
+
log.error(
|
248
274
|
`Error dropping connection with peer ${peerId.toString()} - ${error}`
|
249
275
|
);
|
250
276
|
}
|
@@ -253,12 +279,12 @@ export class ConnectionManager
|
|
253
279
|
private processDialQueue(): void {
|
254
280
|
if (
|
255
281
|
this.pendingPeerDialQueue.length > 0 &&
|
256
|
-
this.
|
282
|
+
this.currentActiveParallelDialCount < this.options.maxParallelDials
|
257
283
|
) {
|
258
284
|
const peerId = this.pendingPeerDialQueue.shift();
|
259
285
|
if (!peerId) return;
|
260
286
|
this.attemptDial(peerId).catch((error) => {
|
261
|
-
log(error);
|
287
|
+
log.error(error);
|
262
288
|
});
|
263
289
|
}
|
264
290
|
}
|
@@ -297,15 +323,15 @@ export class ConnectionManager
|
|
297
323
|
}
|
298
324
|
|
299
325
|
private async attemptDial(peerId: PeerId): Promise<void> {
|
300
|
-
if (
|
326
|
+
if (!(await this.shouldDialPeer(peerId))) return;
|
327
|
+
|
328
|
+
if (this.currentActiveParallelDialCount >= this.options.maxParallelDials) {
|
301
329
|
this.pendingPeerDialQueue.push(peerId);
|
302
330
|
return;
|
303
331
|
}
|
304
332
|
|
305
|
-
if (!(await this.shouldDialPeer(peerId))) return;
|
306
|
-
|
307
333
|
this.dialPeer(peerId).catch((err) => {
|
308
|
-
|
334
|
+
log.error(`Error dialing peer ${peerId.toString()} : ${err}`);
|
309
335
|
});
|
310
336
|
}
|
311
337
|
|
@@ -314,25 +340,12 @@ export class ConnectionManager
|
|
314
340
|
void (async () => {
|
315
341
|
const { id: peerId } = evt.detail;
|
316
342
|
|
317
|
-
|
318
|
-
Tags.BOOTSTRAP
|
319
|
-
);
|
320
|
-
|
321
|
-
this.dispatchEvent(
|
322
|
-
new CustomEvent<PeerId>(
|
323
|
-
isBootstrap
|
324
|
-
? EPeersByDiscoveryEvents.PEER_DISCOVERY_BOOTSTRAP
|
325
|
-
: EPeersByDiscoveryEvents.PEER_DISCOVERY_PEER_EXCHANGE,
|
326
|
-
{
|
327
|
-
detail: peerId
|
328
|
-
}
|
329
|
-
)
|
330
|
-
);
|
343
|
+
await this.dispatchDiscoveryEvent(peerId);
|
331
344
|
|
332
345
|
try {
|
333
346
|
await this.attemptDial(peerId);
|
334
347
|
} catch (error) {
|
335
|
-
log(`Error dialing peer ${peerId.toString()} : ${error}`);
|
348
|
+
log.error(`Error dialing peer ${peerId.toString()} : ${error}`);
|
336
349
|
}
|
337
350
|
})();
|
338
351
|
},
|
@@ -390,15 +403,63 @@ export class ConnectionManager
|
|
390
403
|
};
|
391
404
|
|
392
405
|
/**
|
393
|
-
* Checks if the peer
|
394
|
-
* 1. If the peer is
|
395
|
-
* 2. If the peer is not
|
406
|
+
* Checks if the peer should be dialed based on the following conditions:
|
407
|
+
* 1. If the peer is already connected, don't dial
|
408
|
+
* 2. If the peer is not part of any of the configured pubsub topics, don't dial
|
409
|
+
* 3. If the peer is not dialable based on bootstrap status, don't dial
|
410
|
+
* 4. If the peer is already has an active dial attempt, or has been dialed before, don't dial it
|
411
|
+
* @returns true if the peer should be dialed, false otherwise
|
396
412
|
*/
|
397
413
|
private async shouldDialPeer(peerId: PeerId): Promise<boolean> {
|
414
|
+
// if we're already connected to the peer, don't dial
|
398
415
|
const isConnected = this.libp2p.getConnections(peerId).length > 0;
|
416
|
+
if (isConnected) {
|
417
|
+
log.warn(`Already connected to peer ${peerId.toString()}. Not dialing.`);
|
418
|
+
return false;
|
419
|
+
}
|
399
420
|
|
400
|
-
if
|
421
|
+
// if the peer is not part of any of the configured pubsub topics, don't dial
|
422
|
+
if (!(await this.isPeerTopicConfigured(peerId))) {
|
423
|
+
const shardInfo = await this.getPeerShardInfo(
|
424
|
+
peerId,
|
425
|
+
this.libp2p.peerStore
|
426
|
+
);
|
427
|
+
log.warn(
|
428
|
+
`Discovered peer ${peerId.toString()} with ShardInfo ${shardInfo} is not part of any of the configured pubsub topics (${
|
429
|
+
this.configuredPubSubTopics
|
430
|
+
}).
|
431
|
+
Not dialing.`
|
432
|
+
);
|
433
|
+
return false;
|
434
|
+
}
|
401
435
|
|
436
|
+
// if the peer is not dialable based on bootstrap status, don't dial
|
437
|
+
if (!(await this.isPeerDialableBasedOnBootstrapStatus(peerId))) {
|
438
|
+
log.warn(
|
439
|
+
`Peer ${peerId.toString()} is not dialable based on bootstrap status. Not dialing.`
|
440
|
+
);
|
441
|
+
return false;
|
442
|
+
}
|
443
|
+
|
444
|
+
// If the peer is already already has an active dial attempt, or has been dialed before, don't dial it
|
445
|
+
if (this.dialAttemptsForPeer.has(peerId.toString())) {
|
446
|
+
log.warn(
|
447
|
+
`Peer ${peerId.toString()} has already been attempted dial before, or already has a dial attempt in progress, skipping dial`
|
448
|
+
);
|
449
|
+
return false;
|
450
|
+
}
|
451
|
+
|
452
|
+
return true;
|
453
|
+
}
|
454
|
+
|
455
|
+
/**
|
456
|
+
* Checks if the peer is dialable based on the following conditions:
|
457
|
+
* 1. If the peer is a bootstrap peer, it is only dialable if the number of current bootstrap connections is less than the max allowed.
|
458
|
+
* 2. If the peer is not a bootstrap peer
|
459
|
+
*/
|
460
|
+
private async isPeerDialableBasedOnBootstrapStatus(
|
461
|
+
peerId: PeerId
|
462
|
+
): Promise<boolean> {
|
402
463
|
const tagNames = await this.getTagNamesForPeer(peerId);
|
403
464
|
|
404
465
|
const isBootstrap = tagNames.some((tagName) => tagName === Tags.BOOTSTRAP);
|
@@ -418,6 +479,23 @@ export class ConnectionManager
|
|
418
479
|
return false;
|
419
480
|
}
|
420
481
|
|
482
|
+
private async dispatchDiscoveryEvent(peerId: PeerId): Promise<void> {
|
483
|
+
const isBootstrap = (await this.getTagNamesForPeer(peerId)).includes(
|
484
|
+
Tags.BOOTSTRAP
|
485
|
+
);
|
486
|
+
|
487
|
+
this.dispatchEvent(
|
488
|
+
new CustomEvent<PeerId>(
|
489
|
+
isBootstrap
|
490
|
+
? EPeersByDiscoveryEvents.PEER_DISCOVERY_BOOTSTRAP
|
491
|
+
: EPeersByDiscoveryEvents.PEER_DISCOVERY_PEER_EXCHANGE,
|
492
|
+
{
|
493
|
+
detail: peerId
|
494
|
+
}
|
495
|
+
)
|
496
|
+
);
|
497
|
+
}
|
498
|
+
|
421
499
|
/**
|
422
500
|
* Fetches the tag names for a given peer
|
423
501
|
*/
|
@@ -426,8 +504,35 @@ export class ConnectionManager
|
|
426
504
|
const peer = await this.libp2p.peerStore.get(peerId);
|
427
505
|
return Array.from(peer.tags.keys());
|
428
506
|
} catch (error) {
|
429
|
-
log(`Failed to get peer ${peerId}, error: ${error}`);
|
507
|
+
log.error(`Failed to get peer ${peerId}, error: ${error}`);
|
430
508
|
return [];
|
431
509
|
}
|
432
510
|
}
|
511
|
+
|
512
|
+
private async isPeerTopicConfigured(peerId: PeerId): Promise<boolean> {
|
513
|
+
const shardInfo = await this.getPeerShardInfo(
|
514
|
+
peerId,
|
515
|
+
this.libp2p.peerStore
|
516
|
+
);
|
517
|
+
|
518
|
+
// If there's no shard information, simply return true
|
519
|
+
if (!shardInfo) return true;
|
520
|
+
|
521
|
+
const pubsubTopics = shardInfoToPubSubTopics(shardInfo);
|
522
|
+
|
523
|
+
const isTopicConfigured = pubsubTopics.some((topic) =>
|
524
|
+
this.configuredPubSubTopics.includes(topic)
|
525
|
+
);
|
526
|
+
return isTopicConfigured;
|
527
|
+
}
|
528
|
+
|
529
|
+
private async getPeerShardInfo(
|
530
|
+
peerId: PeerId,
|
531
|
+
peerStore: PeerStore
|
532
|
+
): Promise<ShardInfo | undefined> {
|
533
|
+
const peer = await peerStore.get(peerId);
|
534
|
+
const shardInfoBytes = peer.metadata.get("shardInfo");
|
535
|
+
if (!shardInfoBytes) return undefined;
|
536
|
+
return decodeRelayShard(shardInfoBytes);
|
537
|
+
}
|
433
538
|
}
|
package/src/lib/filter/index.ts
CHANGED
@@ -17,8 +17,12 @@ import type {
|
|
17
17
|
Unsubscribe
|
18
18
|
} from "@waku/interfaces";
|
19
19
|
import { WakuMessage } from "@waku/proto";
|
20
|
-
import {
|
21
|
-
|
20
|
+
import {
|
21
|
+
ensurePubsubTopicIsConfigured,
|
22
|
+
groupByContentTopic,
|
23
|
+
toAsyncIterator
|
24
|
+
} from "@waku/utils";
|
25
|
+
import { Logger } from "@waku/utils";
|
22
26
|
import all from "it-all";
|
23
27
|
import * as lp from "it-length-prefixed";
|
24
28
|
import { pipe } from "it-pipe";
|
@@ -32,7 +36,7 @@ import {
|
|
32
36
|
FilterSubscribeRpc
|
33
37
|
} from "./filter_rpc.js";
|
34
38
|
|
35
|
-
const log =
|
39
|
+
const log = new Logger("filter:v2");
|
36
40
|
|
37
41
|
type SubscriptionCallback<T extends IDecodedMessage> = {
|
38
42
|
decoders: IDecoder<T>[];
|
@@ -46,7 +50,7 @@ export const FilterCodecs = {
|
|
46
50
|
|
47
51
|
class Subscription {
|
48
52
|
private readonly peer: Peer;
|
49
|
-
private readonly
|
53
|
+
private readonly pubsubTopic: PubSubTopic;
|
50
54
|
private newStream: (peer: Peer) => Promise<Stream>;
|
51
55
|
|
52
56
|
private subscriptionCallbacks: Map<
|
@@ -55,12 +59,12 @@ class Subscription {
|
|
55
59
|
>;
|
56
60
|
|
57
61
|
constructor(
|
58
|
-
|
62
|
+
pubsubTopic: PubSubTopic,
|
59
63
|
remotePeer: Peer,
|
60
64
|
newStream: (peer: Peer) => Promise<Stream>
|
61
65
|
) {
|
62
66
|
this.peer = remotePeer;
|
63
|
-
this.
|
67
|
+
this.pubsubTopic = pubsubTopic;
|
64
68
|
this.newStream = newStream;
|
65
69
|
this.subscriptionCallbacks = new Map();
|
66
70
|
}
|
@@ -70,13 +74,23 @@ class Subscription {
|
|
70
74
|
callback: Callback<T>
|
71
75
|
): Promise<void> {
|
72
76
|
const decodersArray = Array.isArray(decoders) ? decoders : [decoders];
|
77
|
+
|
78
|
+
// check that all decoders are configured for the same pubsub topic as this subscription
|
79
|
+
decodersArray.forEach((decoder) => {
|
80
|
+
if (decoder.pubsubTopic !== this.pubsubTopic) {
|
81
|
+
throw new Error(
|
82
|
+
`Pubsub topic not configured: decoder is configured for pubsub topic ${decoder.pubsubTopic} but this subscription is for pubsub topic ${this.pubsubTopic}. Please create a new Subscription for the different pubsub topic.`
|
83
|
+
);
|
84
|
+
}
|
85
|
+
});
|
86
|
+
|
73
87
|
const decodersGroupedByCT = groupByContentTopic(decodersArray);
|
74
88
|
const contentTopics = Array.from(decodersGroupedByCT.keys());
|
75
89
|
|
76
90
|
const stream = await this.newStream(this.peer);
|
77
91
|
|
78
92
|
const request = FilterSubscribeRpc.createSubscribeRequest(
|
79
|
-
this.
|
93
|
+
this.pubsubTopic,
|
80
94
|
contentTopics
|
81
95
|
);
|
82
96
|
|
@@ -89,6 +103,12 @@ class Subscription {
|
|
89
103
|
async (source) => await all(source)
|
90
104
|
);
|
91
105
|
|
106
|
+
if (!res || !res.length) {
|
107
|
+
throw Error(
|
108
|
+
`No response received for request ${request.requestId}: ${res}`
|
109
|
+
);
|
110
|
+
}
|
111
|
+
|
92
112
|
const { statusCode, requestId, statusDesc } =
|
93
113
|
FilterSubscribeResponse.decode(res[0].slice());
|
94
114
|
|
@@ -98,7 +118,7 @@ class Subscription {
|
|
98
118
|
);
|
99
119
|
}
|
100
120
|
|
101
|
-
log(
|
121
|
+
log.info(
|
102
122
|
"Subscribed to peer ",
|
103
123
|
this.peer.id.toString(),
|
104
124
|
"for content topics",
|
@@ -135,7 +155,7 @@ class Subscription {
|
|
135
155
|
async unsubscribe(contentTopics: ContentTopic[]): Promise<void> {
|
136
156
|
const stream = await this.newStream(this.peer);
|
137
157
|
const unsubscribeRequest = FilterSubscribeRpc.createUnsubscribeRequest(
|
138
|
-
this.
|
158
|
+
this.pubsubTopic,
|
139
159
|
contentTopics
|
140
160
|
);
|
141
161
|
|
@@ -164,6 +184,12 @@ class Subscription {
|
|
164
184
|
async (source) => await all(source)
|
165
185
|
);
|
166
186
|
|
187
|
+
if (!res || !res.length) {
|
188
|
+
throw Error(
|
189
|
+
`No response received for request ${request.requestId}: ${res}`
|
190
|
+
);
|
191
|
+
}
|
192
|
+
|
167
193
|
const { statusCode, requestId, statusDesc } =
|
168
194
|
FilterSubscribeResponse.decode(res[0].slice());
|
169
195
|
|
@@ -173,9 +199,9 @@ class Subscription {
|
|
173
199
|
);
|
174
200
|
}
|
175
201
|
|
176
|
-
log("Ping successful");
|
202
|
+
log.info("Ping successful");
|
177
203
|
} catch (error) {
|
178
|
-
log("Error pinging: ", error);
|
204
|
+
log.error("Error pinging: ", error);
|
179
205
|
throw new Error("Error pinging: " + error);
|
180
206
|
}
|
181
207
|
}
|
@@ -184,7 +210,7 @@ class Subscription {
|
|
184
210
|
const stream = await this.newStream(this.peer);
|
185
211
|
|
186
212
|
const request = FilterSubscribeRpc.createUnsubscribeAllRequest(
|
187
|
-
this.
|
213
|
+
this.pubsubTopic
|
188
214
|
);
|
189
215
|
|
190
216
|
try {
|
@@ -196,6 +222,12 @@ class Subscription {
|
|
196
222
|
async (source) => await all(source)
|
197
223
|
);
|
198
224
|
|
225
|
+
if (!res || !res.length) {
|
226
|
+
throw Error(
|
227
|
+
`No response received for request ${request.requestId}: ${res}`
|
228
|
+
);
|
229
|
+
}
|
230
|
+
|
199
231
|
const { statusCode, requestId, statusDesc } =
|
200
232
|
FilterSubscribeResponse.decode(res[0].slice());
|
201
233
|
|
@@ -206,7 +238,7 @@ class Subscription {
|
|
206
238
|
}
|
207
239
|
|
208
240
|
this.subscriptionCallbacks.clear();
|
209
|
-
log("Unsubscribed from all content topics");
|
241
|
+
log.info("Unsubscribed from all content topics");
|
210
242
|
} catch (error) {
|
211
243
|
throw new Error("Error unsubscribing from all content topics: " + error);
|
212
244
|
}
|
@@ -216,50 +248,53 @@ class Subscription {
|
|
216
248
|
const contentTopic = message.contentTopic;
|
217
249
|
const subscriptionCallback = this.subscriptionCallbacks.get(contentTopic);
|
218
250
|
if (!subscriptionCallback) {
|
219
|
-
log("No subscription callback available for ", contentTopic);
|
251
|
+
log.error("No subscription callback available for ", contentTopic);
|
220
252
|
return;
|
221
253
|
}
|
222
|
-
await pushMessage(subscriptionCallback, this.
|
254
|
+
await pushMessage(subscriptionCallback, this.pubsubTopic, message);
|
223
255
|
}
|
224
256
|
}
|
225
257
|
|
226
258
|
class Filter extends BaseProtocol implements IReceiver {
|
227
|
-
private readonly
|
259
|
+
private readonly pubsubTopics: PubSubTopic[] = [];
|
228
260
|
private activeSubscriptions = new Map<string, Subscription>();
|
229
261
|
private readonly NUM_PEERS_PROTOCOL = 1;
|
230
262
|
|
231
263
|
private getActiveSubscription(
|
232
|
-
|
264
|
+
pubsubTopic: PubSubTopic,
|
233
265
|
peerIdStr: PeerIdStr
|
234
266
|
): Subscription | undefined {
|
235
|
-
return this.activeSubscriptions.get(`${
|
267
|
+
return this.activeSubscriptions.get(`${pubsubTopic}_${peerIdStr}`);
|
236
268
|
}
|
237
269
|
|
238
270
|
private setActiveSubscription(
|
239
|
-
|
271
|
+
pubsubTopic: PubSubTopic,
|
240
272
|
peerIdStr: PeerIdStr,
|
241
273
|
subscription: Subscription
|
242
274
|
): Subscription {
|
243
|
-
this.activeSubscriptions.set(`${
|
275
|
+
this.activeSubscriptions.set(`${pubsubTopic}_${peerIdStr}`, subscription);
|
244
276
|
return subscription;
|
245
277
|
}
|
246
278
|
|
247
279
|
constructor(libp2p: Libp2p, options?: ProtocolCreateOptions) {
|
248
280
|
super(FilterCodecs.SUBSCRIBE, libp2p.components);
|
249
281
|
|
282
|
+
this.pubsubTopics = options?.pubsubTopics || [DefaultPubSubTopic];
|
283
|
+
|
250
284
|
libp2p.handle(FilterCodecs.PUSH, this.onRequest.bind(this)).catch((e) => {
|
251
|
-
log("Failed to register ", FilterCodecs.PUSH, e);
|
285
|
+
log.error("Failed to register ", FilterCodecs.PUSH, e);
|
252
286
|
});
|
253
287
|
|
254
288
|
this.activeSubscriptions = new Map();
|
255
|
-
|
256
|
-
this.options = options ?? {};
|
257
289
|
}
|
258
290
|
|
259
|
-
async createSubscription(
|
260
|
-
|
261
|
-
|
291
|
+
async createSubscription(
|
292
|
+
pubsubTopic: string = DefaultPubSubTopic
|
293
|
+
): Promise<Subscription> {
|
294
|
+
ensurePubsubTopicIsConfigured(pubsubTopic, this.pubsubTopics);
|
262
295
|
|
296
|
+
//TODO: get a relevant peer for the topic/shard
|
297
|
+
// https://github.com/waku-org/js-waku/pull/1586#discussion_r1336428230
|
263
298
|
const peer = (
|
264
299
|
await this.getPeers({
|
265
300
|
maxBootstrapPeers: 1,
|
@@ -268,11 +303,11 @@ class Filter extends BaseProtocol implements IReceiver {
|
|
268
303
|
)[0];
|
269
304
|
|
270
305
|
const subscription =
|
271
|
-
this.getActiveSubscription(
|
306
|
+
this.getActiveSubscription(pubsubTopic, peer.id.toString()) ??
|
272
307
|
this.setActiveSubscription(
|
273
|
-
|
308
|
+
pubsubTopic,
|
274
309
|
peer.id.toString(),
|
275
|
-
new Subscription(
|
310
|
+
new Subscription(pubsubTopic, peer, this.getStream.bind(this, peer))
|
276
311
|
);
|
277
312
|
|
278
313
|
return subscription;
|
@@ -319,7 +354,6 @@ class Filter extends BaseProtocol implements IReceiver {
|
|
319
354
|
}
|
320
355
|
|
321
356
|
private onRequest(streamData: IncomingStreamData): void {
|
322
|
-
log("Receiving message push");
|
323
357
|
try {
|
324
358
|
pipe(streamData.stream, lp.decode, async (source) => {
|
325
359
|
for await (const bytes of source) {
|
@@ -328,12 +362,12 @@ class Filter extends BaseProtocol implements IReceiver {
|
|
328
362
|
const { pubsubTopic, wakuMessage } = response;
|
329
363
|
|
330
364
|
if (!wakuMessage) {
|
331
|
-
log("Received empty message");
|
365
|
+
log.error("Received empty message");
|
332
366
|
return;
|
333
367
|
}
|
334
368
|
|
335
369
|
if (!pubsubTopic) {
|
336
|
-
log("PubSub topic missing from push message");
|
370
|
+
log.error("PubSub topic missing from push message");
|
337
371
|
return;
|
338
372
|
}
|
339
373
|
|
@@ -344,7 +378,9 @@ class Filter extends BaseProtocol implements IReceiver {
|
|
344
378
|
);
|
345
379
|
|
346
380
|
if (!subscription) {
|
347
|
-
log(
|
381
|
+
log.error(
|
382
|
+
`No subscription locally registered for topic ${pubsubTopic}`
|
383
|
+
);
|
348
384
|
return;
|
349
385
|
}
|
350
386
|
|
@@ -352,14 +388,14 @@ class Filter extends BaseProtocol implements IReceiver {
|
|
352
388
|
}
|
353
389
|
}).then(
|
354
390
|
() => {
|
355
|
-
log("Receiving pipe closed.");
|
391
|
+
log.info("Receiving pipe closed.");
|
356
392
|
},
|
357
393
|
(e) => {
|
358
|
-
log("Error with receiving pipe", e);
|
394
|
+
log.error("Error with receiving pipe", e);
|
359
395
|
}
|
360
396
|
);
|
361
397
|
} catch (e) {
|
362
|
-
log("Error decoding message", e);
|
398
|
+
log.error("Error decoding message", e);
|
363
399
|
}
|
364
400
|
}
|
365
401
|
}
|
@@ -372,21 +408,21 @@ export function wakuFilter(
|
|
372
408
|
|
373
409
|
async function pushMessage<T extends IDecodedMessage>(
|
374
410
|
subscriptionCallback: SubscriptionCallback<T>,
|
375
|
-
|
411
|
+
pubsubTopic: PubSubTopic,
|
376
412
|
message: WakuMessage
|
377
413
|
): Promise<void> {
|
378
414
|
const { decoders, callback } = subscriptionCallback;
|
379
415
|
|
380
416
|
const { contentTopic } = message;
|
381
417
|
if (!contentTopic) {
|
382
|
-
log("Message has no content topic, skipping");
|
418
|
+
log.warn("Message has no content topic, skipping");
|
383
419
|
return;
|
384
420
|
}
|
385
421
|
|
386
422
|
try {
|
387
423
|
const decodePromises = decoders.map((dec) =>
|
388
424
|
dec
|
389
|
-
.fromProtoObj(
|
425
|
+
.fromProtoObj(pubsubTopic, message as IProtoMessage)
|
390
426
|
.then((decoded) => decoded || Promise.reject("Decoding failed"))
|
391
427
|
);
|
392
428
|
|
@@ -394,6 +430,6 @@ async function pushMessage<T extends IDecodedMessage>(
|
|
394
430
|
|
395
431
|
await callback(decodedMessage);
|
396
432
|
} catch (e) {
|
397
|
-
log("Error decoding message", e);
|
433
|
+
log.error("Error decoding message", e);
|
398
434
|
}
|
399
435
|
}
|