Skip to content

Commit a3c45b6

Browse files
feat(static-sharding)!: allow multiple pubSubTopics (#1586)
* `ProtocolCreateOptions` now has `pubSubTopic` as `pubSubTopic[]` * chore: update encoder & decoder to support `PubSubTopic` * feat(protocols): allow multiple `PubSubTopic[]` * feat(relay): allow multiple `PubSubTopic[]` * chore(tests): update for new API * chore: minor fixes * chore: make store more robust * fix(relay): correctly set types * chore(address comments): update terminology around configured pubsub topics * chore(address comments): minor refactoring * chore(relay): split `subscribe` into smaller functions for readability & modularity * chore(address comments): refactor `waitForGossipSubPeerInMesh` * chore(store): only allow to query one `pubSubTopic` * fix: `store` bug * feat(tests): add some basic tests * sharding utils * address comments * feat(relay): re-add API for `getMeshPeers` * update error message Co-authored-by: fryorcraken <110212804+fryorcraken@users.noreply.github.com> * refactor for new API * feat: simplify handling of observers (#1614) * refactor: simplify handling of observers * refactor: Remove redundant PubSubTopic from Observer * use `??` instead of `||` * update `pubsubTopic` to `pubSubTopic` * update `interval` typo * change occurence of `pubsubTopic` to `pubSubTopic` * relay: rm `getAllMeshPeers` and make `pubSubTopics` public * relay: use `push_or_init_map` and move to `utils` * fix: update API for tests * fix: relay waitForRemotePeer --------- Co-authored-by: fryorcraken <110212804+fryorcraken@users.noreply.github.com>
1 parent 077d9a7 commit a3c45b6

26 files changed

+395
-194
lines changed

.cspell.json

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"exponentiate",
4040
"extip",
4141
"fanout",
42+
"sharded",
4243
"floodsub",
4344
"fontsource",
4445
"globby",

package-lock.json

+8-18
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/src/lib/filter/index.ts

+17-10
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ import type {
1717
Unsubscribe
1818
} from "@waku/interfaces";
1919
import { WakuMessage } from "@waku/proto";
20-
import { groupByContentTopic, toAsyncIterator } from "@waku/utils";
20+
import {
21+
ensurePubsubTopicIsConfigured,
22+
groupByContentTopic,
23+
toAsyncIterator
24+
} from "@waku/utils";
2125
import debug from "debug";
2226
import all from "it-all";
2327
import * as lp from "it-length-prefixed";
@@ -230,7 +234,7 @@ class Subscription {
230234
}
231235

232236
class Filter extends BaseProtocol implements IReceiver {
233-
private readonly options: ProtocolCreateOptions;
237+
private readonly pubSubTopics: PubSubTopic[] = [];
234238
private activeSubscriptions = new Map<string, Subscription>();
235239
private readonly NUM_PEERS_PROTOCOL = 1;
236240

@@ -253,19 +257,22 @@ class Filter extends BaseProtocol implements IReceiver {
253257
constructor(libp2p: Libp2p, options?: ProtocolCreateOptions) {
254258
super(FilterCodecs.SUBSCRIBE, libp2p.components);
255259

260+
this.pubSubTopics = options?.pubSubTopics || [DefaultPubSubTopic];
261+
256262
libp2p.handle(FilterCodecs.PUSH, this.onRequest.bind(this)).catch((e) => {
257263
log("Failed to register ", FilterCodecs.PUSH, e);
258264
});
259265

260266
this.activeSubscriptions = new Map();
261-
262-
this.options = options ?? {};
263267
}
264268

265-
async createSubscription(pubSubTopic?: string): Promise<Subscription> {
266-
const _pubSubTopic =
267-
pubSubTopic ?? this.options.pubSubTopic ?? DefaultPubSubTopic;
269+
async createSubscription(
270+
pubSubTopic: string = DefaultPubSubTopic
271+
): Promise<Subscription> {
272+
ensurePubsubTopicIsConfigured(pubSubTopic, this.pubSubTopics);
268273

274+
//TODO: get a relevant peer for the topic/shard
275+
// https://github.com/waku-org/js-waku/pull/1586#discussion_r1336428230
269276
const peer = (
270277
await this.getPeers({
271278
maxBootstrapPeers: 1,
@@ -274,11 +281,11 @@ class Filter extends BaseProtocol implements IReceiver {
274281
)[0];
275282

276283
const subscription =
277-
this.getActiveSubscription(_pubSubTopic, peer.id.toString()) ??
284+
this.getActiveSubscription(pubSubTopic, peer.id.toString()) ??
278285
this.setActiveSubscription(
279-
_pubSubTopic,
286+
pubSubTopic,
280287
peer.id.toString(),
281-
new Subscription(_pubSubTopic, peer, this.getStream.bind(this, peer))
288+
new Subscription(pubSubTopic, peer, this.getStream.bind(this, peer))
282289
);
283290

284291
return subscription;

packages/core/src/lib/keep_alive_manager.ts

+37-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { PeerId } from "@libp2p/interface/peer-id";
22
import type { PeerStore } from "@libp2p/interface/peer-store";
3-
import type { IRelay } from "@waku/interfaces";
3+
import type { IRelay, PeerIdStr } from "@waku/interfaces";
44
import type { KeepAliveOptions } from "@waku/interfaces";
55
import { utf8ToBytes } from "@waku/utils/bytes";
66
import debug from "debug";
@@ -13,7 +13,7 @@ const log = debug("waku:keep-alive");
1313

1414
export class KeepAliveManager {
1515
private pingKeepAliveTimers: Map<string, ReturnType<typeof setInterval>>;
16-
private relayKeepAliveTimers: Map<PeerId, ReturnType<typeof setInterval>>;
16+
private relayKeepAliveTimers: Map<PeerId, ReturnType<typeof setInterval>[]>;
1717
private options: KeepAliveOptions;
1818
private relay?: IRelay;
1919

@@ -66,17 +66,12 @@ export class KeepAliveManager {
6666

6767
const relay = this.relay;
6868
if (relay && relayPeriodSecs !== 0) {
69-
const encoder = createEncoder({
70-
contentTopic: RelayPingContentTopic,
71-
ephemeral: true
72-
});
73-
const interval = setInterval(() => {
74-
log("Sending Waku Relay ping message");
75-
relay
76-
.send(encoder, { payload: new Uint8Array([1]) })
77-
.catch((e) => log("Failed to send relay ping", e));
78-
}, relayPeriodSecs * 1000);
79-
this.relayKeepAliveTimers.set(peerId, interval);
69+
const intervals = this.scheduleRelayPings(
70+
relay,
71+
relayPeriodSecs,
72+
peerId.toString()
73+
);
74+
this.relayKeepAliveTimers.set(peerId, intervals);
8075
}
8176
}
8277

@@ -89,7 +84,7 @@ export class KeepAliveManager {
8984
}
9085

9186
if (this.relayKeepAliveTimers.has(peerId)) {
92-
clearInterval(this.relayKeepAliveTimers.get(peerId));
87+
this.relayKeepAliveTimers.get(peerId)?.map(clearInterval);
9388
this.relayKeepAliveTimers.delete(peerId);
9489
}
9590
}
@@ -105,4 +100,32 @@ export class KeepAliveManager {
105100
this.pingKeepAliveTimers.clear();
106101
this.relayKeepAliveTimers.clear();
107102
}
103+
104+
private scheduleRelayPings(
105+
relay: IRelay,
106+
relayPeriodSecs: number,
107+
peerIdStr: PeerIdStr
108+
): NodeJS.Timeout[] {
109+
// send a ping message to each PubSubTopic the peer is part of
110+
const intervals: NodeJS.Timeout[] = [];
111+
for (const topic of relay.pubSubTopics) {
112+
const meshPeers = relay.getMeshPeers(topic);
113+
if (!meshPeers.includes(peerIdStr)) continue;
114+
115+
const encoder = createEncoder({
116+
pubSubTopic: topic,
117+
contentTopic: RelayPingContentTopic,
118+
ephemeral: true
119+
});
120+
const interval = setInterval(() => {
121+
log("Sending Waku Relay ping message");
122+
relay
123+
.send(encoder, { payload: new Uint8Array([1]) })
124+
.catch((e) => log("Failed to send relay ping", e));
125+
}, relayPeriodSecs * 1000);
126+
intervals.push(interval);
127+
}
128+
129+
return intervals;
130+
}
108131
}

packages/core/src/lib/light_push/index.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import {
66
IMessage,
77
Libp2p,
88
ProtocolCreateOptions,
9+
PubSubTopic,
910
SendError,
1011
SendResult
1112
} from "@waku/interfaces";
1213
import { PushResponse } from "@waku/proto";
13-
import { isSizeValid } from "@waku/utils";
14+
import { ensurePubsubTopicIsConfigured, isSizeValid } from "@waku/utils";
1415
import debug from "debug";
1516
import all from "it-all";
1617
import * as lp from "it-length-prefixed";
@@ -41,12 +42,12 @@ type PreparePushMessageResult =
4142
* Implements the [Waku v2 Light Push protocol](https://rfc.vac.dev/spec/19/).
4243
*/
4344
class LightPush extends BaseProtocol implements ILightPush {
44-
options: ProtocolCreateOptions;
45+
private readonly pubSubTopics: PubSubTopic[];
4546
private readonly NUM_PEERS_PROTOCOL = 1;
4647

4748
constructor(libp2p: Libp2p, options?: ProtocolCreateOptions) {
4849
super(LightPushCodec, libp2p.components);
49-
this.options = options || {};
50+
this.pubSubTopics = options?.pubSubTopics ?? [DefaultPubSubTopic];
5051
}
5152

5253
private async preparePushMessage(
@@ -82,7 +83,9 @@ class LightPush extends BaseProtocol implements ILightPush {
8283
}
8384

8485
async send(encoder: IEncoder, message: IMessage): Promise<SendResult> {
85-
const { pubSubTopic = DefaultPubSubTopic } = this.options;
86+
const { pubSubTopic } = encoder;
87+
ensurePubsubTopicIsConfigured(pubSubTopic, this.pubSubTopics);
88+
8689
const recipients: PeerId[] = [];
8790

8891
const { query, error: preparationError } = await this.preparePushMessage(
@@ -98,6 +101,7 @@ class LightPush extends BaseProtocol implements ILightPush {
98101
};
99102
}
100103

104+
//TODO: get a relevant peer for the topic/shard
101105
const peers = await this.getPeers({
102106
maxBootstrapPeers: 1,
103107
numPeers: this.NUM_PEERS_PROTOCOL

packages/core/src/lib/message/version_0.ts

+16-5
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ import type {
66
IMessage,
77
IMetaSetter,
88
IProtoMessage,
9-
IRateLimitProof
9+
IRateLimitProof,
10+
PubSubTopic
1011
} from "@waku/interfaces";
1112
import { proto_message as proto } from "@waku/proto";
1213
import debug from "debug";
1314

15+
import { DefaultPubSubTopic } from "../constants.js";
16+
1417
const log = debug("waku:message:version-0");
1518
const OneMillion = BigInt(1_000_000);
1619

@@ -73,6 +76,7 @@ export class Encoder implements IEncoder {
7376
constructor(
7477
public contentTopic: string,
7578
public ephemeral: boolean = false,
79+
public pubSubTopic: PubSubTopic,
7680
public metaSetter?: IMetaSetter
7781
) {
7882
if (!contentTopic || contentTopic === "") {
@@ -115,15 +119,19 @@ export class Encoder implements IEncoder {
115119
* messages.
116120
*/
117121
export function createEncoder({
122+
pubSubTopic = DefaultPubSubTopic,
118123
contentTopic,
119124
ephemeral,
120125
metaSetter
121126
}: EncoderOptions): Encoder {
122-
return new Encoder(contentTopic, ephemeral, metaSetter);
127+
return new Encoder(contentTopic, ephemeral, pubSubTopic, metaSetter);
123128
}
124129

125130
export class Decoder implements IDecoder<DecodedMessage> {
126-
constructor(public contentTopic: string) {
131+
constructor(
132+
public pubSubTopic: PubSubTopic,
133+
public contentTopic: string
134+
) {
127135
if (!contentTopic || contentTopic === "") {
128136
throw new Error("Content topic must be specified");
129137
}
@@ -173,6 +181,9 @@ export class Decoder implements IDecoder<DecodedMessage> {
173181
*
174182
* @param contentTopic The resulting decoder will only decode messages with this content topic.
175183
*/
176-
export function createDecoder(contentTopic: string): Decoder {
177-
return new Decoder(contentTopic);
184+
export function createDecoder(
185+
contentTopic: string,
186+
pubsubTopic: PubSubTopic = DefaultPubSubTopic
187+
): Decoder {
188+
return new Decoder(pubsubTopic, contentTopic);
178189
}

0 commit comments

Comments
 (0)