From 54bc0cabdcb55e921a83f557975d93223e8a65b0 Mon Sep 17 00:00:00 2001 From: fryorcraken Date: Fri, 11 Jul 2025 13:33:45 +1000 Subject: [PATCH] feat!: Introduce routing info concept Concepts are being mixed up between the global network config (static vs auto sharding), that needs to be the same of all nodes in the network, individual node configuration (eg relay node subscribing to a given shard), and the routing characteristic of a specific message (eg pubsub topic, shard). This stops proper configuration of nwaku post 0.36.0 because we know need to be deliberate on whether nwaku nodes are running with auto or static sharding. It also included various back and forth conversions between shards, pubsub topics, etc. With this change, we tidy up the network configuration, and make it explicit whether it is static or auto sharded. We also introduce the concept of routing info, which is specific to a message, and tied to the overall network configuration. Routing info abstract pubsub topic, shard, and autosharding needs. Which should lead to easier tidy up of the pubsub concept at a later stage. --- .../connection_manager.spec.ts | 2 +- .../connection_manager/connection_manager.ts | 13 +- .../src/lib/connection_manager/dialer.spec.ts | 20 +- .../core/src/lib/connection_manager/dialer.ts | 6 +- .../keep_alive_manager.spec.ts | 22 + .../connection_manager/keep_alive_manager.ts | 15 +- .../connection_manager/shard_reader.spec.ts | 119 +++-- .../lib/connection_manager/shard_reader.ts | 79 ++-- .../core/src/lib/light_push/light_push.ts | 8 +- .../core/src/lib/message/version_0.spec.ts | 91 ++-- packages/core/src/lib/message/version_0.ts | 59 ++- packages/core/src/lib/metadata/metadata.ts | 14 +- packages/core/src/lib/store/rpc.spec.ts | 31 +- packages/core/src/lib/store/rpc.ts | 4 +- packages/core/src/lib/store/store.ts | 2 +- .../waku_peer_exchange_discovery.ts | 4 +- packages/enr/src/enr.ts | 6 +- packages/enr/src/raw_enr.ts | 6 +- packages/interfaces/src/constants.ts | 10 +- packages/interfaces/src/enr.ts | 4 +- packages/interfaces/src/message.ts | 35 +- packages/interfaces/src/metadata.ts | 4 +- packages/interfaces/src/protocols.ts | 4 +- packages/interfaces/src/sharding.ts | 46 +- packages/interfaces/src/store.ts | 7 +- packages/interfaces/src/waku.ts | 13 +- packages/message-encryption/src/ecies.spec.ts | 77 ++- packages/message-encryption/src/ecies.ts | 47 +- .../message-encryption/src/symmetric.spec.ts | 77 ++- packages/message-encryption/src/symmetric.ts | 47 +- packages/relay/src/create.ts | 12 +- packages/relay/src/message_validator.spec.ts | 21 +- packages/relay/src/relay.ts | 30 +- packages/relay/src/topic_only_message.ts | 26 +- .../tests/high-throughput.spec.ts | 44 +- .../reliability-tests/tests/longevity.spec.ts | 40 +- .../tests/throughput-sizes.spec.ts | 40 +- packages/rln/src/codec.spec.ts | 120 +++-- packages/rln/src/codec.test-utils.ts | 18 +- packages/rln/src/codec.ts | 14 +- packages/rln/src/rln.ts | 31 +- packages/sdk/src/filter/filter.spec.ts | 26 +- packages/sdk/src/filter/filter.ts | 16 +- packages/sdk/src/filter/subscription.spec.ts | 24 +- packages/sdk/src/filter/subscription.ts | 25 +- packages/sdk/src/filter/types.ts | 8 +- .../sdk/src/light_push/light_push.spec.ts | 15 +- packages/sdk/src/light_push/light_push.ts | 6 +- .../sdk/src/light_push/retry_manager.spec.ts | 26 +- packages/sdk/src/light_push/retry_manager.ts | 12 +- .../sdk/src/peer_manager/peer_manager.spec.ts | 19 +- packages/sdk/src/peer_manager/peer_manager.ts | 24 +- packages/sdk/src/store/store.ts | 18 +- packages/sdk/src/waku/utils.spec.ts | 119 ----- packages/sdk/src/waku/utils.ts | 47 -- packages/sdk/src/waku/waku.ts | 46 +- packages/tests/src/constants.ts | 47 +- packages/tests/src/lib/index.ts | 55 ++- packages/tests/src/lib/message_collector.ts | 26 +- packages/tests/src/lib/runNodes.ts | 95 +++- packages/tests/src/lib/service_node.ts | 102 +++- packages/tests/src/types.ts | 7 +- .../tests/src/utils/generate_test_data.ts | 22 +- packages/tests/src/utils/nodes.ts | 28 +- .../connection_limiter.spec.ts | 8 +- .../tests/connection-mananger/dialing.spec.ts | 6 +- .../discovery_dialer.spec.ts | 6 +- .../network_monitor.spec.ts | 17 +- .../tests/tests/connection-mananger/utils.ts | 13 +- packages/tests/tests/enr.node.spec.ts | 30 +- packages/tests/tests/ephemeral.node.spec.ts | 64 ++- packages/tests/tests/filter/push.node.spec.ts | 45 +- .../tests/tests/filter/subscribe.node.spec.ts | 232 ++++++---- .../tests/filter/unsubscribe.node.spec.ts | 48 +- packages/tests/tests/filter/utils.ts | 137 +----- .../tests/tests/light-push/index.node.spec.ts | 104 ++--- .../light-push/multiple_pubsub.node.spec.ts | 283 +++++------ packages/tests/tests/light-push/utils.ts | 41 +- packages/tests/tests/metadata.spec.ts | 372 +++++++++------ packages/tests/tests/nwaku.node.spec.ts | 2 - .../tests/peer-exchange/compliance.spec.ts | 6 +- .../continuous_discovery.spec.ts | 20 +- .../tests/tests/peer-exchange/index.spec.ts | 18 +- .../tests/peer-exchange/pe.optional.spec.ts | 6 +- packages/tests/tests/relay/index.node.spec.ts | 18 +- .../tests/tests/relay/interop.node.spec.ts | 22 +- .../tests/relay/multiple_pubsub.node.spec.ts | 192 ++++---- .../tests/tests/relay/publish.node.spec.ts | 67 +-- .../tests/tests/relay/subscribe.node.spec.ts | 92 ++-- packages/tests/tests/relay/utils.ts | 42 +- .../tests/sharding/auto_sharding.spec.ts | 86 ++-- .../tests/sharding/peer_management.spec.ts | 128 ++--- .../tests/sharding/static_sharding.spec.ts | 128 +---- .../tests/tests/store/cursor.node.spec.ts | 33 +- .../store/different_static_shards.spec.ts | 190 ++++++++ .../tests/store/error_handling.node.spec.ts | 8 +- packages/tests/tests/store/index.node.spec.ts | 97 ++-- .../tests/tests/store/message_hash.spec.ts | 13 +- .../tests/tests/store/multiple_pubsub.spec.ts | 438 ------------------ packages/tests/tests/store/order.node.spec.ts | 11 +- .../tests/tests/store/page_size.node.spec.ts | 14 +- .../tests/tests/store/sorting.node.spec.ts | 9 +- .../tests/store/time_filter.node.spec.ts | 11 +- packages/tests/tests/store/utils.ts | 88 ++-- .../tests/wait_for_remote_peer.node.spec.ts | 49 +- packages/tests/tests/waku.node.spec.ts | 42 +- .../utils/src/common/relay_shard_codec.ts | 8 +- .../utils/src/common/sharding/index.spec.ts | 299 ++++-------- packages/utils/src/common/sharding/index.ts | 247 ++-------- .../utils/src/common/sharding/routing_info.ts | 183 ++++++++ .../utils/src/common/sharding/type_guards.ts | 10 +- 111 files changed, 2853 insertions(+), 3211 deletions(-) delete mode 100644 packages/sdk/src/waku/utils.spec.ts delete mode 100644 packages/sdk/src/waku/utils.ts create mode 100644 packages/tests/tests/store/different_static_shards.spec.ts delete mode 100644 packages/tests/tests/store/multiple_pubsub.spec.ts create mode 100644 packages/utils/src/common/sharding/routing_info.ts diff --git a/packages/core/src/lib/connection_manager/connection_manager.spec.ts b/packages/core/src/lib/connection_manager/connection_manager.spec.ts index 25235cba12..ec1d53921f 100644 --- a/packages/core/src/lib/connection_manager/connection_manager.spec.ts +++ b/packages/core/src/lib/connection_manager/connection_manager.spec.ts @@ -63,7 +63,7 @@ describe("ConnectionManager", () => { } as unknown as IWakuEventEmitter; networkConfig = { - clusterId: 1, + clusterId: 2, shards: [0, 1] } as NetworkConfig; diff --git a/packages/core/src/lib/connection_manager/connection_manager.ts b/packages/core/src/lib/connection_manager/connection_manager.ts index 952ab32d53..0f3f83e159 100644 --- a/packages/core/src/lib/connection_manager/connection_manager.ts +++ b/packages/core/src/lib/connection_manager/connection_manager.ts @@ -1,11 +1,13 @@ import { type Peer, type PeerId, type Stream } from "@libp2p/interface"; import { MultiaddrInput } from "@multiformats/multiaddr"; import { + ClusterId, ConnectionManagerOptions, IConnectionManager, IRelay, IWakuEventEmitter, - NetworkConfig + NetworkConfig, + ShardId } from "@waku/interfaces"; import { Libp2p } from "@waku/interfaces"; import { Logger } from "@waku/utils"; @@ -66,6 +68,7 @@ export class ConnectionManager implements IConnectionManager { this.keepAliveManager = new KeepAliveManager({ relay: options.relay, libp2p: options.libp2p, + networkConfig: options.networkConfig, options: { pingKeepAlive: this.options.pingKeepAlive, relayKeepAlive: this.options.relayKeepAlive @@ -194,4 +197,12 @@ export class ConnectionManager implements IConnectionManager { ): Promise { return this.shardReader.isPeerOnTopic(peerId, pubsubTopic); } + + public async isPeerOnShard( + peerId: PeerId, + clusterId: ClusterId, + shardId: ShardId + ): Promise { + return this.shardReader.isPeerOnShard(peerId, clusterId, shardId); + } } diff --git a/packages/core/src/lib/connection_manager/dialer.spec.ts b/packages/core/src/lib/connection_manager/dialer.spec.ts index 74690a79c9..d7112a5b02 100644 --- a/packages/core/src/lib/connection_manager/dialer.spec.ts +++ b/packages/core/src/lib/connection_manager/dialer.spec.ts @@ -29,7 +29,7 @@ describe("Dialer", () => { mockShardReader = { hasShardInfo: sinon.stub().resolves(false), - isPeerOnNetwork: sinon.stub().resolves(true) + isPeerOnCluster: sinon.stub().resolves(true) } as unknown as sinon.SinonStubbedInstance; mockOptions = { @@ -280,9 +280,9 @@ describe("Dialer", () => { expect(dialStub.calledTwice).to.be.true; }); - it("should skip peer when not on same shard", async () => { + it("should skip peer when not on same cluster", async () => { mockShardReader.hasShardInfo.resolves(true); - mockShardReader.isPeerOnNetwork.resolves(false); + mockShardReader.isPeerOnCluster.resolves(false); const dialStub = libp2p.dial as sinon.SinonStub; @@ -290,12 +290,12 @@ describe("Dialer", () => { expect(dialStub.called).to.be.false; expect(mockShardReader.hasShardInfo.calledWith(mockPeerId)).to.be.true; - expect(mockShardReader.isPeerOnNetwork.calledWith(mockPeerId)).to.be.true; + expect(mockShardReader.isPeerOnCluster.calledWith(mockPeerId)).to.be.true; }); it("should dial peer when on same shard", async () => { mockShardReader.hasShardInfo.resolves(true); - mockShardReader.isPeerOnNetwork.resolves(true); + mockShardReader.isPeerOnCluster.resolves(true); const dialStub = libp2p.dial as sinon.SinonStub; dialStub.resolves(); @@ -305,7 +305,7 @@ describe("Dialer", () => { expect(dialStub.calledOnce).to.be.true; expect(dialStub.calledWith(mockPeerId)).to.be.true; expect(mockShardReader.hasShardInfo.calledWith(mockPeerId)).to.be.true; - expect(mockShardReader.isPeerOnNetwork.calledWith(mockPeerId)).to.be.true; + expect(mockShardReader.isPeerOnCluster.calledWith(mockPeerId)).to.be.true; }); it("should dial peer when no shard info available", async () => { @@ -319,7 +319,7 @@ describe("Dialer", () => { expect(dialStub.calledOnce).to.be.true; expect(dialStub.calledWith(mockPeerId)).to.be.true; expect(mockShardReader.hasShardInfo.calledWith(mockPeerId)).to.be.true; - expect(mockShardReader.isPeerOnNetwork.called).to.be.false; + expect(mockShardReader.isPeerOnCluster.called).to.be.false; }); it("should handle dial errors gracefully", async () => { @@ -468,7 +468,7 @@ describe("Dialer", () => { it("should handle network check errors gracefully", async () => { mockShardReader.hasShardInfo.resolves(true); - mockShardReader.isPeerOnNetwork.rejects(new Error("Network check error")); + mockShardReader.isPeerOnCluster.rejects(new Error("Network check error")); const dialStub = libp2p.dial as sinon.SinonStub; @@ -476,7 +476,7 @@ describe("Dialer", () => { expect(dialStub.called).to.be.false; expect(mockShardReader.hasShardInfo.calledWith(mockPeerId)).to.be.true; - expect(mockShardReader.isPeerOnNetwork.calledWith(mockPeerId)).to.be.true; + expect(mockShardReader.isPeerOnCluster.calledWith(mockPeerId)).to.be.true; }); }); @@ -512,7 +512,7 @@ describe("Dialer", () => { dialStub.resolves(); mockShardReader.hasShardInfo.withArgs(mockPeerId).resolves(true); - mockShardReader.isPeerOnNetwork.withArgs(mockPeerId).resolves(true); + mockShardReader.isPeerOnCluster.withArgs(mockPeerId).resolves(true); mockShardReader.hasShardInfo.withArgs(mockPeerId2).resolves(false); diff --git a/packages/core/src/lib/connection_manager/dialer.ts b/packages/core/src/lib/connection_manager/dialer.ts index 21989c12aa..fbe317d3d2 100644 --- a/packages/core/src/lib/connection_manager/dialer.ts +++ b/packages/core/src/lib/connection_manager/dialer.ts @@ -153,9 +153,9 @@ export class Dialer implements IDialer { return false; } - const isOnSameShard = await this.shardReader.isPeerOnNetwork(peerId); - if (!isOnSameShard) { - log.info(`Skipping peer ${peerId} - not on same shard`); + const isOnSameCluster = await this.shardReader.isPeerOnCluster(peerId); + if (!isOnSameCluster) { + log.info(`Skipping peer ${peerId} - not on same cluster`); return true; } diff --git a/packages/core/src/lib/connection_manager/keep_alive_manager.spec.ts b/packages/core/src/lib/connection_manager/keep_alive_manager.spec.ts index 3699be7967..9df2050c1e 100644 --- a/packages/core/src/lib/connection_manager/keep_alive_manager.spec.ts +++ b/packages/core/src/lib/connection_manager/keep_alive_manager.spec.ts @@ -1,4 +1,5 @@ import type { PeerId } from "@libp2p/interface"; +import { AutoSharding } from "@waku/interfaces"; import { expect } from "chai"; import sinon from "sinon"; @@ -23,6 +24,11 @@ describe("KeepAliveManager", () => { relayKeepAlive: 60 }; + const defaultNetworkConfig: AutoSharding = { + clusterId: 0, + numShardsInCluster: 1 + }; + beforeEach(() => { clock = sinon.useFakeTimers(); @@ -61,6 +67,7 @@ describe("KeepAliveManager", () => { it("should create KeepAliveManager with required options", () => { keepAliveManager = new KeepAliveManager({ options: defaultOptions, + networkConfig: defaultNetworkConfig, libp2p }); @@ -70,6 +77,7 @@ describe("KeepAliveManager", () => { it("should create KeepAliveManager with relay", () => { keepAliveManager = new KeepAliveManager({ options: defaultOptions, + networkConfig: defaultNetworkConfig, libp2p, relay }); @@ -82,6 +90,7 @@ describe("KeepAliveManager", () => { beforeEach(() => { keepAliveManager = new KeepAliveManager({ options: defaultOptions, + networkConfig: defaultNetworkConfig, libp2p }); }); @@ -110,6 +119,7 @@ describe("KeepAliveManager", () => { beforeEach(() => { keepAliveManager = new KeepAliveManager({ options: defaultOptions, + networkConfig: defaultNetworkConfig, libp2p, relay }); @@ -158,6 +168,7 @@ describe("KeepAliveManager", () => { beforeEach(() => { keepAliveManager = new KeepAliveManager({ options: defaultOptions, + networkConfig: defaultNetworkConfig, libp2p, relay }); @@ -194,6 +205,7 @@ describe("KeepAliveManager", () => { beforeEach(() => { keepAliveManager = new KeepAliveManager({ options: defaultOptions, + networkConfig: defaultNetworkConfig, libp2p, relay }); @@ -225,6 +237,7 @@ describe("KeepAliveManager", () => { beforeEach(() => { keepAliveManager = new KeepAliveManager({ options: defaultOptions, + networkConfig: defaultNetworkConfig, libp2p }); keepAliveManager.start(); @@ -244,6 +257,7 @@ describe("KeepAliveManager", () => { keepAliveManager.stop(); keepAliveManager = new KeepAliveManager({ options: { pingKeepAlive: 0, relayKeepAlive: 0 }, + networkConfig: defaultNetworkConfig, libp2p }); keepAliveManager.start(); @@ -317,6 +331,7 @@ describe("KeepAliveManager", () => { beforeEach(() => { keepAliveManager = new KeepAliveManager({ options: defaultOptions, + networkConfig: defaultNetworkConfig, libp2p, relay }); @@ -337,6 +352,7 @@ describe("KeepAliveManager", () => { keepAliveManager.stop(); keepAliveManager = new KeepAliveManager({ options: { pingKeepAlive: 30, relayKeepAlive: 0 }, + networkConfig: defaultNetworkConfig, libp2p, relay }); @@ -355,6 +371,7 @@ describe("KeepAliveManager", () => { keepAliveManager.stop(); keepAliveManager = new KeepAliveManager({ options: defaultOptions, + networkConfig: defaultNetworkConfig, libp2p }); keepAliveManager.start(); @@ -423,6 +440,7 @@ describe("KeepAliveManager", () => { beforeEach(() => { keepAliveManager = new KeepAliveManager({ options: defaultOptions, + networkConfig: defaultNetworkConfig, libp2p, relay }); @@ -489,6 +507,7 @@ describe("KeepAliveManager", () => { keepAliveManager = new KeepAliveManager({ options: defaultOptions, + networkConfig: defaultNetworkConfig, libp2p, relay: emptyRelay }); @@ -506,6 +525,7 @@ describe("KeepAliveManager", () => { it("should handle all zero keep alive options", () => { keepAliveManager = new KeepAliveManager({ options: { pingKeepAlive: 0, relayKeepAlive: 0 }, + networkConfig: defaultNetworkConfig, libp2p, relay }); @@ -525,6 +545,7 @@ describe("KeepAliveManager", () => { keepAliveManager = new KeepAliveManager({ options: defaultOptions, + networkConfig: defaultNetworkConfig, libp2p, relay }); @@ -544,6 +565,7 @@ describe("KeepAliveManager", () => { it("should handle complete peer lifecycle", async () => { keepAliveManager = new KeepAliveManager({ options: defaultOptions, + networkConfig: defaultNetworkConfig, libp2p, relay }); diff --git a/packages/core/src/lib/connection_manager/keep_alive_manager.ts b/packages/core/src/lib/connection_manager/keep_alive_manager.ts index 35c0800e5e..19ad6070d2 100644 --- a/packages/core/src/lib/connection_manager/keep_alive_manager.ts +++ b/packages/core/src/lib/connection_manager/keep_alive_manager.ts @@ -1,6 +1,6 @@ import type { PeerId } from "@libp2p/interface"; -import type { IEncoder, IRelay, Libp2p } from "@waku/interfaces"; -import { Logger, pubsubTopicToSingleShardInfo } from "@waku/utils"; +import type { IEncoder, IRelay, Libp2p, NetworkConfig } from "@waku/interfaces"; +import { createRoutingInfo, Logger } from "@waku/utils"; import { utf8ToBytes } from "@waku/utils/bytes"; import { createEncoder } from "../message/version_0.js"; @@ -15,6 +15,7 @@ type KeepAliveOptions = { type CreateKeepAliveManagerOptions = { options: KeepAliveOptions; + networkConfig: NetworkConfig; libp2p: Libp2p; relay?: IRelay; }; @@ -26,6 +27,7 @@ interface IKeepAliveManager { export class KeepAliveManager implements IKeepAliveManager { private readonly relay?: IRelay; + private readonly networkConfig: NetworkConfig; private readonly libp2p: Libp2p; private readonly options: KeepAliveOptions; @@ -38,10 +40,12 @@ export class KeepAliveManager implements IKeepAliveManager { public constructor({ options, relay, + networkConfig, libp2p }: CreateKeepAliveManagerOptions) { this.options = options; this.relay = relay; + this.networkConfig = networkConfig; this.libp2p = libp2p; this.onPeerConnect = this.onPeerConnect.bind(this); @@ -163,8 +167,13 @@ export class KeepAliveManager implements IKeepAliveManager { continue; } + const routingInfo = createRoutingInfo(this.networkConfig, { + contentTopic: RelayPingContentTopic, + pubsubTopic: topic + }); + const encoder = createEncoder({ - pubsubTopicShardInfo: pubsubTopicToSingleShardInfo(topic), + routingInfo: routingInfo, contentTopic: RelayPingContentTopic, ephemeral: true }); diff --git a/packages/core/src/lib/connection_manager/shard_reader.spec.ts b/packages/core/src/lib/connection_manager/shard_reader.spec.ts index 843966f705..7f38c83190 100644 --- a/packages/core/src/lib/connection_manager/shard_reader.spec.ts +++ b/packages/core/src/lib/connection_manager/shard_reader.spec.ts @@ -1,9 +1,10 @@ import { PeerId } from "@libp2p/interface"; import { + AutoSharding, + DEFAULT_NUM_SHARDS, NetworkConfig, PubsubTopic, - ShardInfo, - SingleShardInfo + RelayShards } from "@waku/interfaces"; import { contentTopicToShardIndex, encodeRelayShard } from "@waku/utils"; import { expect } from "chai"; @@ -30,12 +31,12 @@ describe("ShardReader", function () { const testClusterId = 3; const testShardIndex = contentTopicToShardIndex(testContentTopic); - const testNetworkConfig: NetworkConfig = { - contentTopics: [testContentTopic], - clusterId: testClusterId + const testNetworkConfig: AutoSharding = { + clusterId: testClusterId, + numShardsInCluster: DEFAULT_NUM_SHARDS }; - const testShardInfo: ShardInfo = { + const testRelayShards: RelayShards = { clusterId: testClusterId, shards: [testShardIndex] }; @@ -64,10 +65,10 @@ describe("ShardReader", function () { }); describe("constructor", function () { - it("should create ShardReader with contentTopics network config", function () { - const config: NetworkConfig = { - contentTopics: ["/test/1/waku-light-push/utf8"], - clusterId: 3 + it("should create ShardReader with auto sharding network config", function () { + const config: AutoSharding = { + clusterId: 3, + numShardsInCluster: 10 }; const reader = new ShardReader({ @@ -78,10 +79,9 @@ describe("ShardReader", function () { expect(reader).to.be.instanceOf(ShardReader); }); - it("should create ShardReader with shards network config", function () { + it("should create ShardReader with static shards network config", function () { const config: NetworkConfig = { - clusterId: 3, - shards: [1, 2, 3] + clusterId: 3 }; const reader = new ShardReader({ @@ -94,22 +94,22 @@ describe("ShardReader", function () { }); describe("isPeerOnNetwork", function () { - it("should return true when peer is on the same network", async function () { - const shardInfoBytes = encodeRelayShard(testShardInfo); + it("should return true when peer is on the same cluster", async function () { + const shardInfoBytes = encodeRelayShard(testRelayShards); const mockPeer = { metadata: new Map([["shardInfo", shardInfoBytes]]) }; mockPeerStore.get.resolves(mockPeer); - const result = await shardReader.isPeerOnNetwork(testPeerId); + const result = await shardReader.isPeerOnCluster(testPeerId); expect(result).to.be.true; sinon.assert.calledWith(mockPeerStore.get, testPeerId); }); it("should return false when peer is on different cluster", async function () { - const differentClusterShardInfo: ShardInfo = { + const differentClusterShardInfo: RelayShards = { clusterId: 5, shards: [1, 2] }; @@ -120,13 +120,13 @@ describe("ShardReader", function () { mockPeerStore.get.resolves(mockPeer); - const result = await shardReader.isPeerOnNetwork(testPeerId); + const result = await shardReader.isPeerOnCluster(testPeerId); expect(result).to.be.false; }); - it("should return false when peer has no overlapping shards", async function () { - const noOverlapShardInfo: ShardInfo = { + it("should return true even if peer has no overlapping shards", async function () { + const noOverlapShardInfo: RelayShards = { clusterId: testClusterId, shards: [testShardIndex + 100, testShardIndex + 200] // Use different shards }; @@ -137,9 +137,9 @@ describe("ShardReader", function () { mockPeerStore.get.resolves(mockPeer); - const result = await shardReader.isPeerOnNetwork(testPeerId); + const result = await shardReader.isPeerOnCluster(testPeerId); - expect(result).to.be.false; + expect(result).to.be.true; }); it("should return false when peer has no shard info", async function () { @@ -149,7 +149,7 @@ describe("ShardReader", function () { mockPeerStore.get.resolves(mockPeer); - const result = await shardReader.isPeerOnNetwork(testPeerId); + const result = await shardReader.isPeerOnCluster(testPeerId); expect(result).to.be.false; }); @@ -157,7 +157,7 @@ describe("ShardReader", function () { it("should return false when peer is not found", async function () { mockPeerStore.get.rejects(new Error("Peer not found")); - const result = await shardReader.isPeerOnNetwork(testPeerId); + const result = await shardReader.isPeerOnCluster(testPeerId); expect(result).to.be.false; }); @@ -165,66 +165,52 @@ describe("ShardReader", function () { describe("isPeerOnShard", function () { it("should return true when peer is on the specified shard", async function () { - const shardInfoBytes = encodeRelayShard(testShardInfo); + const shardInfoBytes = encodeRelayShard(testRelayShards); const mockPeer = { metadata: new Map([["shardInfo", shardInfoBytes]]) }; mockPeerStore.get.resolves(mockPeer); - const shard: SingleShardInfo = { - clusterId: testClusterId, - shard: testShardIndex - }; - - const result = await shardReader.isPeerOnShard(testPeerId, shard); + const result = await shardReader.isPeerOnShard( + testPeerId, + testClusterId, + testShardIndex + ); expect(result).to.be.true; }); it("should return false when peer is on different cluster", async function () { - const shardInfoBytes = encodeRelayShard(testShardInfo); + const shardInfoBytes = encodeRelayShard(testRelayShards); const mockPeer = { metadata: new Map([["shardInfo", shardInfoBytes]]) }; mockPeerStore.get.resolves(mockPeer); - const shard: SingleShardInfo = { - clusterId: 5, - shard: testShardIndex - }; - - const result = await shardReader.isPeerOnShard(testPeerId, shard); + const result = await shardReader.isPeerOnShard( + testPeerId, + 5, + testShardIndex + ); expect(result).to.be.false; }); it("should return false when peer is not on the specified shard", async function () { - const shardInfoBytes = encodeRelayShard(testShardInfo); + const shardInfoBytes = encodeRelayShard(testRelayShards); const mockPeer = { metadata: new Map([["shardInfo", shardInfoBytes]]) }; mockPeerStore.get.resolves(mockPeer); - const shard: SingleShardInfo = { - clusterId: testClusterId, - shard: testShardIndex + 100 - }; - - const result = await shardReader.isPeerOnShard(testPeerId, shard); - - expect(result).to.be.false; - }); - - it("should return false when shard info is undefined", async function () { - const shard: SingleShardInfo = { - clusterId: testClusterId, - shard: undefined - }; - - const result = await shardReader.isPeerOnShard(testPeerId, shard); + const result = await shardReader.isPeerOnShard( + testPeerId, + testClusterId, + testShardIndex + 100 + ); expect(result).to.be.false; }); @@ -232,12 +218,11 @@ describe("ShardReader", function () { it("should return false when peer shard info is not found", async function () { mockPeerStore.get.rejects(new Error("Peer not found")); - const shard: SingleShardInfo = { - clusterId: testClusterId, - shard: testShardIndex - }; - - const result = await shardReader.isPeerOnShard(testPeerId, shard); + const result = await shardReader.isPeerOnShard( + testPeerId, + testClusterId, + testShardIndex + ); expect(result).to.be.false; }); @@ -245,7 +230,7 @@ describe("ShardReader", function () { describe("isPeerOnTopic", function () { it("should return true when peer is on the pubsub topic shard", async function () { - const shardInfoBytes = encodeRelayShard(testShardInfo); + const shardInfoBytes = encodeRelayShard(testRelayShards); const mockPeer = { metadata: new Map([["shardInfo", shardInfoBytes]]) }; @@ -260,7 +245,7 @@ describe("ShardReader", function () { }); it("should return false when peer is not on the pubsub topic shard", async function () { - const shardInfoBytes = encodeRelayShard(testShardInfo); + const shardInfoBytes = encodeRelayShard(testRelayShards); const mockPeer = { metadata: new Map([["shardInfo", shardInfoBytes]]) }; @@ -275,7 +260,7 @@ describe("ShardReader", function () { }); it("should return false when pubsub topic parsing fails", async function () { - const shardInfoBytes = encodeRelayShard(testShardInfo); + const shardInfoBytes = encodeRelayShard(testRelayShards); const mockPeer = { metadata: new Map([["shardInfo", shardInfoBytes]]) }; @@ -307,7 +292,7 @@ describe("ShardReader", function () { it("should handle errors gracefully when getting peer info", async function () { mockPeerStore.get.rejects(new Error("Network error")); - const result = await shardReader.isPeerOnNetwork(testPeerId); + const result = await shardReader.isPeerOnCluster(testPeerId); expect(result).to.be.false; }); @@ -319,7 +304,7 @@ describe("ShardReader", function () { mockPeerStore.get.resolves(mockPeer); - const result = await shardReader.isPeerOnNetwork(testPeerId); + const result = await shardReader.isPeerOnCluster(testPeerId); expect(result).to.be.false; }); diff --git a/packages/core/src/lib/connection_manager/shard_reader.ts b/packages/core/src/lib/connection_manager/shard_reader.ts index b7b5a735b0..b5ae35a778 100644 --- a/packages/core/src/lib/connection_manager/shard_reader.ts +++ b/packages/core/src/lib/connection_manager/shard_reader.ts @@ -1,13 +1,12 @@ import type { PeerId } from "@libp2p/interface"; import type { + ClusterId, NetworkConfig, PubsubTopic, - ShardInfo, - SingleShardInfo, - StaticSharding + RelayShards, + ShardId } from "@waku/interfaces"; import { - contentTopicToShardIndex, decodeRelayShard, Logger, pubsubTopicToSingleShardInfo @@ -23,8 +22,12 @@ type ShardReaderConstructorOptions = { interface IShardReader { hasShardInfo(id: PeerId): Promise; - isPeerOnNetwork(id: PeerId): Promise; - isPeerOnShard(id: PeerId, shard: SingleShardInfo): Promise; + isPeerOnCluster(id: PeerId): Promise; + isPeerOnShard( + id: PeerId, + clusterId: ClusterId, + shard: ShardId + ): Promise; isPeerOnTopic(id: PeerId, pubsubTopic: PubsubTopic): Promise; } @@ -34,33 +37,26 @@ interface IShardReader { export class ShardReader implements IShardReader { private readonly libp2p: Libp2p; - private readonly staticShard: StaticSharding; + private readonly clusterId: ClusterId; public constructor(options: ShardReaderConstructorOptions) { this.libp2p = options.libp2p; - this.staticShard = this.getStaticShardFromNetworkConfig( - options.networkConfig - ); + this.clusterId = options.networkConfig.clusterId; } - public async isPeerOnNetwork(id: PeerId): Promise { - const shardInfo = await this.getShardInfo(id); + public async isPeerOnCluster(id: PeerId): Promise { + const peerRelayShards = await this.getRelayShards(id); - if (!shardInfo) { + if (!peerRelayShards) { return false; } - const clusterMatch = shardInfo.clusterId === this.staticShard.clusterId; - const shardOverlap = this.staticShard.shards.some((s) => - shardInfo.shards.includes(s) - ); - - return clusterMatch && shardOverlap; + return peerRelayShards.clusterId === this.clusterId; } public async hasShardInfo(id: PeerId): Promise { - const shardInfo = await this.getShardInfo(id); + const shardInfo = await this.getRelayShards(id); return !!shardInfo; } @@ -69,8 +65,8 @@ export class ShardReader implements IShardReader { pubsubTopic: PubsubTopic ): Promise { try { - const shardInfo = pubsubTopicToSingleShardInfo(pubsubTopic); - return await this.isPeerOnShard(id, shardInfo); + const { clusterId, shard } = pubsubTopicToSingleShardInfo(pubsubTopic); + return await this.isPeerOnShard(id, clusterId, shard); } catch (error) { log.error( `Error comparing pubsub topic ${pubsubTopic} with shard info for ${id}`, @@ -82,21 +78,25 @@ export class ShardReader implements IShardReader { public async isPeerOnShard( id: PeerId, - shard: SingleShardInfo + clusterId: ClusterId, + shard: ShardId ): Promise { - const peerShardInfo = await this.getShardInfo(id); - - if (!peerShardInfo || shard.shard === undefined) { + const peerShardInfo = await this.getRelayShards(id); + log.info( + `Checking if peer on same shard: this { clusterId: ${clusterId}, shardId: ${shard} },` + + `${id} { clusterId: ${peerShardInfo?.clusterId}, shards: ${peerShardInfo?.shards} }` + ); + if (!peerShardInfo) { return false; } return ( - peerShardInfo.clusterId === shard.clusterId && - peerShardInfo.shards.includes(shard.shard) + peerShardInfo.clusterId === clusterId && + peerShardInfo.shards.includes(shard) ); } - private async getShardInfo(id: PeerId): Promise { + private async getRelayShards(id: PeerId): Promise { try { const peer = await this.libp2p.peerStore.get(id); @@ -106,29 +106,10 @@ export class ShardReader implements IShardReader { return undefined; } - const decodedShardInfo = decodeRelayShard(shardInfoBytes); - - return decodedShardInfo; + return decodeRelayShard(shardInfoBytes); } catch (error) { log.error(`Error getting shard info for ${id}`, error); return undefined; } } - - private getStaticShardFromNetworkConfig( - networkConfig: NetworkConfig - ): StaticSharding { - if ("shards" in networkConfig) { - return networkConfig; - } - - const shards = networkConfig.contentTopics.map((topic) => - contentTopicToShardIndex(topic) - ); - - return { - clusterId: networkConfig.clusterId!, - shards - }; - } } diff --git a/packages/core/src/lib/light_push/light_push.ts b/packages/core/src/lib/light_push/light_push.ts index 6c2430e5a5..68f17d4a71 100644 --- a/packages/core/src/lib/light_push/light_push.ts +++ b/packages/core/src/lib/light_push/light_push.ts @@ -8,8 +8,7 @@ import { type ThisOrThat } from "@waku/interfaces"; import { PushResponse } from "@waku/proto"; -import { isMessageSizeUnderCap } from "@waku/utils"; -import { Logger } from "@waku/utils"; +import { isMessageSizeUnderCap, Logger } from "@waku/utils"; import all from "it-all"; import * as lp from "it-length-prefixed"; import { pipe } from "it-pipe"; @@ -63,7 +62,10 @@ export class LightPushCore { }; } - const query = PushRpc.createRequest(protoMessage, encoder.pubsubTopic); + const query = PushRpc.createRequest( + protoMessage, + encoder.routingInfo.pubsubTopic + ); return { query, error: null }; } catch (error) { log.error("Failed to prepare push message", error); diff --git a/packages/core/src/lib/message/version_0.spec.ts b/packages/core/src/lib/message/version_0.spec.ts index 4cf6856154..4c9f02ef67 100644 --- a/packages/core/src/lib/message/version_0.spec.ts +++ b/packages/core/src/lib/message/version_0.spec.ts @@ -1,30 +1,38 @@ -import type { IProtoMessage } from "@waku/interfaces"; -import { contentTopicToPubsubTopic } from "@waku/utils"; +import type { AutoSharding, IProtoMessage } from "@waku/interfaces"; +import { createRoutingInfo } from "@waku/utils"; import { expect } from "chai"; import fc from "fast-check"; import { createDecoder, createEncoder, DecodedMessage } from "./version_0.js"; -const contentTopic = "/js-waku/1/tests/bytes"; -const pubsubTopic = contentTopicToPubsubTopic(contentTopic); +const testContentTopic = "/js-waku/1/tests/bytes"; + +const testNetworkConfig: AutoSharding = { + clusterId: 0, + numShardsInCluster: 8 +}; +const testRoutingInfo = createRoutingInfo(testNetworkConfig, { + contentTopic: testContentTopic +}); describe("Waku Message version 0", function () { it("Round trip binary serialization", async function () { await fc.assert( fc.asyncProperty(fc.uint8Array({ minLength: 1 }), async (payload) => { const encoder = createEncoder({ - contentTopic + contentTopic: testContentTopic, + routingInfo: testRoutingInfo }); const bytes = await encoder.toWire({ payload }); - const decoder = createDecoder(contentTopic); + const decoder = createDecoder(testContentTopic, testRoutingInfo); const protoResult = await decoder.fromWireToProtoObj(bytes); const result = (await decoder.fromProtoObj( - pubsubTopic, + testRoutingInfo.pubsubTopic, protoResult! )) as DecodedMessage; - expect(result.contentTopic).to.eq(contentTopic); - expect(result.pubsubTopic).to.eq(pubsubTopic); + expect(result.contentTopic).to.eq(testContentTopic); + expect(result.pubsubTopic).to.eq(testRoutingInfo.pubsubTopic); expect(result.version).to.eq(0); expect(result.ephemeral).to.be.false; expect(result.payload).to.deep.eq(payload); @@ -37,14 +45,15 @@ describe("Waku Message version 0", function () { await fc.assert( fc.asyncProperty(fc.uint8Array({ minLength: 1 }), async (payload) => { const encoder = createEncoder({ - contentTopic, + contentTopic: testContentTopic, + routingInfo: testRoutingInfo, ephemeral: true }); const bytes = await encoder.toWire({ payload }); - const decoder = createDecoder(contentTopic); + const decoder = createDecoder(testContentTopic, testRoutingInfo); const protoResult = await decoder.fromWireToProtoObj(bytes); const result = (await decoder.fromProtoObj( - pubsubTopic, + testRoutingInfo.pubsubTopic, protoResult! )) as DecodedMessage; @@ -68,15 +77,16 @@ describe("Waku Message version 0", function () { }; const encoder = createEncoder({ - contentTopic, + contentTopic: testContentTopic, + routingInfo: testRoutingInfo, ephemeral: true, metaSetter }); const bytes = await encoder.toWire({ payload }); - const decoder = createDecoder(contentTopic); + const decoder = createDecoder(testContentTopic, testRoutingInfo); const protoResult = await decoder.fromWireToProtoObj(bytes); const result = (await decoder.fromProtoObj( - pubsubTopic, + testRoutingInfo.pubsubTopic, protoResult! )) as DecodedMessage; @@ -99,54 +109,73 @@ describe("Waku Message version 0", function () { describe("Ensures content topic is defined", () => { it("Encoder throws on undefined content topic", () => { const wrapper = function (): void { - createEncoder({ contentTopic: undefined as unknown as string }); + createEncoder({ + contentTopic: undefined as unknown as string, + routingInfo: testRoutingInfo + }); }; - expect(wrapper).to.throw("Content topic must be specified"); + expect(wrapper).to.throw( + "Routing Info must have the same content topic as the encoder" + ); }); it("Encoder throws on empty string content topic", () => { const wrapper = function (): void { - createEncoder({ contentTopic: "" }); + createEncoder({ + contentTopic: "", + routingInfo: createRoutingInfo(testNetworkConfig, { contentTopic: "" }) + }); }; - expect(wrapper).to.throw("Content topic must be specified"); + expect(wrapper).to.throw("AutoSharding requires contentTopic"); }); it("Decoder throws on undefined content topic", () => { const wrapper = function (): void { - createDecoder(undefined as unknown as string); + createDecoder( + undefined as unknown as string, + createRoutingInfo(testNetworkConfig, { + contentTopic: undefined as unknown as string + }) + ); }; - expect(wrapper).to.throw("Content topic must be specified"); + expect(wrapper).to.throw("AutoSharding requires contentTopic"); }); it("Decoder throws on empty string content topic", () => { const wrapper = function (): void { - createDecoder(""); + createDecoder( + "", + createRoutingInfo(testNetworkConfig, { contentTopic: "" }) + ); }; - expect(wrapper).to.throw("Content topic must be specified"); + expect(wrapper).to.throw("AutoSharding requires contentTopic"); }); }); describe("Sets sharding configuration correctly", () => { it("uses static shard pubsub topic instead of autosharding when set", async () => { // Create an encoder setup to use autosharding - const ContentTopic = "/waku/2/content/test.js"; + const contentTopic = "/myapp/1/test/proto"; const autoshardingEncoder = createEncoder({ - pubsubTopicShardInfo: { clusterId: 0 }, - contentTopic: ContentTopic + contentTopic: contentTopic, + routingInfo: createRoutingInfo(testNetworkConfig, { contentTopic }) }); // When autosharding is enabled, we expect the shard index to be 1 - expect(autoshardingEncoder.pubsubTopic).to.be.eq("/waku/2/rs/0/1"); + expect(autoshardingEncoder.routingInfo.pubsubTopic).to.be.eq( + "/waku/2/rs/0/0" + ); // Create an encoder setup to use static sharding with the same content topic - const singleShardInfo = { clusterId: 0, shard: 0 }; const staticshardingEncoder = createEncoder({ - contentTopic: ContentTopic, - pubsubTopicShardInfo: singleShardInfo + contentTopic: contentTopic, + routingInfo: createRoutingInfo({ clusterId: 0 }, { shardId: 3 }) }); // When static sharding is enabled, we expect the shard index to be 0 - expect(staticshardingEncoder.pubsubTopic).to.be.eq("/waku/2/rs/0/0"); + expect(staticshardingEncoder.routingInfo.pubsubTopic).to.be.eq( + "/waku/2/rs/0/3" + ); }); }); diff --git a/packages/core/src/lib/message/version_0.ts b/packages/core/src/lib/message/version_0.ts index d1777b3c2d..53f337ccde 100644 --- a/packages/core/src/lib/message/version_0.ts +++ b/packages/core/src/lib/message/version_0.ts @@ -1,17 +1,14 @@ import type { - EncoderOptions, IDecodedMessage, IDecoder, IEncoder, IMessage, IMetaSetter, IProtoMessage, - IRateLimitProof, - PubsubTopic, - SingleShardInfo + IRateLimitProof } from "@waku/interfaces"; import { proto_message as proto } from "@waku/proto"; -import { determinePubsubTopic, Logger } from "@waku/utils"; +import { isAutoShardingRoutingInfo, Logger, RoutingInfo } from "@waku/utils"; const log = new Logger("message:version-0"); const OneMillion = BigInt(1_000_000); @@ -67,11 +64,31 @@ export class DecodedMessage implements IDecodedMessage { } } +export type EncoderOptions = { + /** + * The routing information for messages to encode. + */ + routingInfo: RoutingInfo; + /** The content topic to set on outgoing messages. */ + contentTopic: string; + /** + * An optional flag to mark message as ephemeral, i.e., not to be stored by Waku Store nodes. + * @defaultValue `false` + */ + ephemeral?: boolean; + /** + * A function called when encoding messages to set the meta field. + * @param IProtoMessage The message encoded for wire, without the meta field. + * If encryption is used, `metaSetter` only accesses _encrypted_ payload. + */ + metaSetter?: IMetaSetter; +}; + export class Encoder implements IEncoder { public constructor( public contentTopic: string, public ephemeral: boolean = false, - public pubsubTopic: PubsubTopic, + public routingInfo: RoutingInfo, public metaSetter?: IMetaSetter ) { if (!contentTopic || contentTopic === "") { @@ -114,24 +131,22 @@ export class Encoder implements IEncoder { * messages. */ export function createEncoder({ - pubsubTopic, - pubsubTopicShardInfo, contentTopic, + routingInfo, ephemeral, metaSetter }: EncoderOptions): Encoder { - return new Encoder( - contentTopic, - ephemeral, - determinePubsubTopic(contentTopic, pubsubTopic ?? pubsubTopicShardInfo), - metaSetter - ); + if (isAutoShardingRoutingInfo(routingInfo)) { + if (routingInfo.contentTopic !== contentTopic) + throw "Routing Info must have the same content topic as the encoder"; + } + return new Encoder(contentTopic, ephemeral, routingInfo, metaSetter); } export class Decoder implements IDecoder { public constructor( - public pubsubTopic: PubsubTopic, - public contentTopic: string + public contentTopic: string, + public routingInfo: RoutingInfo ) { if (!contentTopic || contentTopic === "") { throw new Error("Content topic must be specified"); @@ -182,13 +197,15 @@ export class Decoder implements IDecoder { * messages. * * @param contentTopic The resulting decoder will only decode messages with this content topic. + * @param routingInfo */ export function createDecoder( contentTopic: string, - pubsubTopicShardInfo?: SingleShardInfo | PubsubTopic + routingInfo: RoutingInfo ): Decoder { - return new Decoder( - determinePubsubTopic(contentTopic, pubsubTopicShardInfo), - contentTopic - ); + if (isAutoShardingRoutingInfo(routingInfo)) { + if (routingInfo.contentTopic !== contentTopic) + throw "Routing Info must have the same content topic as the encoder"; + } + return new Decoder(contentTopic, routingInfo); } diff --git a/packages/core/src/lib/metadata/metadata.ts b/packages/core/src/lib/metadata/metadata.ts index ac4707e575..18d59b5790 100644 --- a/packages/core/src/lib/metadata/metadata.ts +++ b/packages/core/src/lib/metadata/metadata.ts @@ -7,7 +7,7 @@ import { type MetadataQueryResult, type PeerIdStr, ProtocolError, - type ShardInfo + type RelayShards } from "@waku/interfaces"; import { proto_metadata } from "@waku/proto"; import { encodeRelayShard, Logger } from "@waku/utils"; @@ -25,7 +25,7 @@ export const MetadataCodec = "/vac/waku/metadata/1.0.0"; class Metadata implements IMetadata { private readonly streamManager: StreamManager; private readonly libp2pComponents: Libp2pComponents; - protected handshakesConfirmed: Map = new Map(); + protected handshakesConfirmed: Map = new Map(); public readonly multicodec = MetadataCodec; @@ -148,7 +148,7 @@ class Metadata implements IMetadata { }); const response = proto_metadata.WakuMetadataResponse.decode( bytes - ) as ShardInfo; + ) as RelayShards; if (!response) { log.error("Error decoding metadata response"); @@ -166,16 +166,16 @@ class Metadata implements IMetadata { private async savePeerShardInfo( peerId: PeerId, - shardInfo: ShardInfo + relayShards: RelayShards ): Promise { - // add or update the shardInfo to peer store + // add or update the relayShards to peer store await this.libp2pComponents.peerStore.merge(peerId, { metadata: { - shardInfo: encodeRelayShard(shardInfo) + shardInfo: encodeRelayShard(relayShards) } }); - this.handshakesConfirmed.set(peerId.toString(), shardInfo); + this.handshakesConfirmed.set(peerId.toString(), relayShards); } } diff --git a/packages/core/src/lib/store/rpc.spec.ts b/packages/core/src/lib/store/rpc.spec.ts index 6e38449c2f..ecea28e3c0 100644 --- a/packages/core/src/lib/store/rpc.spec.ts +++ b/packages/core/src/lib/store/rpc.spec.ts @@ -1,11 +1,17 @@ +import { createRoutingInfo } from "@waku/utils"; import { expect } from "chai"; import { StoreQueryRequest } from "./rpc.js"; +const routingInfo = createRoutingInfo( + { clusterId: 0 }, + { pubsubTopic: "/waku/2/rs/0/0" } +); + describe("StoreQueryRequest validation", () => { it("accepts valid content-filtered query", () => { const request = StoreQueryRequest.create({ - pubsubTopic: "/waku/2/default-waku/proto", + routingInfo, contentTopics: ["/test/1/content/proto"], includeData: true, paginationForward: true @@ -16,7 +22,7 @@ describe("StoreQueryRequest validation", () => { it("rejects content-filtered query with only pubsubTopic", () => { expect(() => StoreQueryRequest.create({ - pubsubTopic: "/waku/2/default-waku/proto", + routingInfo, contentTopics: [], includeData: true, paginationForward: true @@ -26,22 +32,9 @@ describe("StoreQueryRequest validation", () => { ); }); - it("rejects content-filtered query with only contentTopics", () => { - expect(() => - StoreQueryRequest.create({ - pubsubTopic: "", - contentTopics: ["/test/1/content/proto"], - includeData: true, - paginationForward: true - }) - ).to.throw( - "Both pubsubTopic and contentTopics must be set together for content-filtered queries" - ); - }); - it("accepts valid message hash query", () => { const request = StoreQueryRequest.create({ - pubsubTopic: "", + routingInfo, contentTopics: [], messageHashes: [new Uint8Array([1, 2, 3, 4])], includeData: true, @@ -54,7 +47,7 @@ describe("StoreQueryRequest validation", () => { expect(() => StoreQueryRequest.create({ messageHashes: [new Uint8Array([1, 2, 3, 4])], - pubsubTopic: "/waku/2/default-waku/proto", + routingInfo, contentTopics: ["/test/1/content/proto"], includeData: true, paginationForward: true @@ -67,7 +60,7 @@ describe("StoreQueryRequest validation", () => { it("rejects hash query with time filter", () => { expect(() => StoreQueryRequest.create({ - pubsubTopic: "", + routingInfo, contentTopics: [], messageHashes: [new Uint8Array([1, 2, 3, 4])], timeStart: new Date(), @@ -81,7 +74,7 @@ describe("StoreQueryRequest validation", () => { it("accepts time-filtered query with content filter", () => { const request = StoreQueryRequest.create({ - pubsubTopic: "/waku/2/default-waku/proto", + routingInfo, contentTopics: ["/test/1/content/proto"], timeStart: new Date(Date.now() - 3600000), timeEnd: new Date(), diff --git a/packages/core/src/lib/store/rpc.ts b/packages/core/src/lib/store/rpc.ts index 0055ed96a3..3fcc00f8ab 100644 --- a/packages/core/src/lib/store/rpc.ts +++ b/packages/core/src/lib/store/rpc.ts @@ -42,9 +42,9 @@ export class StoreQueryRequest { } } else { if ( - (params.pubsubTopic && + (params.routingInfo && (!params.contentTopics || params.contentTopics.length === 0)) || - (!params.pubsubTopic && + (!params.routingInfo && params.contentTopics && params.contentTopics.length > 0) ) { diff --git a/packages/core/src/lib/store/store.ts b/packages/core/src/lib/store/store.ts index ce61b7a553..61f6f07737 100644 --- a/packages/core/src/lib/store/store.ts +++ b/packages/core/src/lib/store/store.ts @@ -76,7 +76,7 @@ export class StoreCore { log.info("Sending store query request:", { hasMessageHashes: !!queryOpts.messageHashes?.length, messageHashCount: queryOpts.messageHashes?.length, - pubsubTopic: queryOpts.pubsubTopic, + routingInfo: queryOpts.routingInfo, contentTopics: queryOpts.contentTopics }); diff --git a/packages/discovery/src/peer-exchange/waku_peer_exchange_discovery.ts b/packages/discovery/src/peer-exchange/waku_peer_exchange_discovery.ts index 9087f12c15..a9d46740f2 100644 --- a/packages/discovery/src/peer-exchange/waku_peer_exchange_discovery.ts +++ b/packages/discovery/src/peer-exchange/waku_peer_exchange_discovery.ts @@ -10,7 +10,7 @@ import type { import { type Libp2pComponents, type PeerExchangeQueryResult, - ShardInfo, + type RelayShards, Tags } from "@waku/interfaces"; import { decodeRelayShard, encodeRelayShard, Logger } from "@waku/utils"; @@ -279,7 +279,7 @@ export class PeerExchangeDiscovery private async checkPeerInfoDiff( peerInfo: PeerInfo, - shardInfo?: ShardInfo + shardInfo?: RelayShards ): Promise<{ hasMultiaddrDiff: boolean; hasShardDiff: boolean }> { const { id: peerId } = peerInfo; const peer = await this.components.peerStore.get(peerId); diff --git a/packages/enr/src/enr.ts b/packages/enr/src/enr.ts index 77c80fc1c2..71b2bcc0fb 100644 --- a/packages/enr/src/enr.ts +++ b/packages/enr/src/enr.ts @@ -5,8 +5,8 @@ import type { ENRValue, IEnr, NodeId, - SequenceNumber, - ShardInfo + RelayShards, + SequenceNumber } from "@waku/interfaces"; import { Logger } from "@waku/utils"; @@ -64,7 +64,7 @@ export class ENR extends RawEnr implements IEnr { protocol: TransportProtocol | TransportProtocolPerIpVersion ) => Multiaddr | undefined = locationMultiaddrFromEnrFields.bind({}, this); - public get shardInfo(): ShardInfo | undefined { + public get shardInfo(): RelayShards | undefined { if (this.rs && this.rsv) { log.warn("ENR contains both `rs` and `rsv` fields."); } diff --git a/packages/enr/src/raw_enr.ts b/packages/enr/src/raw_enr.ts index 0629932f78..1b3ced089a 100644 --- a/packages/enr/src/raw_enr.ts +++ b/packages/enr/src/raw_enr.ts @@ -6,8 +6,8 @@ import { import type { ENRKey, ENRValue, + RelayShards, SequenceNumber, - ShardInfo, Waku2 } from "@waku/interfaces"; import { decodeRelayShard } from "@waku/utils"; @@ -52,13 +52,13 @@ export class RawEnr extends Map { } } - public get rs(): ShardInfo | undefined { + public get rs(): RelayShards | undefined { const rs = this.get("rs"); if (!rs) return undefined; return decodeRelayShard(rs); } - public get rsv(): ShardInfo | undefined { + public get rsv(): RelayShards | undefined { const rsv = this.get("rsv"); if (!rsv) return undefined; return decodeRelayShard(rsv); diff --git a/packages/interfaces/src/constants.ts b/packages/interfaces/src/constants.ts index b65f48d72c..566299501c 100644 --- a/packages/interfaces/src/constants.ts +++ b/packages/interfaces/src/constants.ts @@ -1,4 +1,4 @@ -import type { ShardInfo } from "./sharding"; +import type { AutoSharding } from "./sharding"; /** * The default cluster ID for The Waku Network @@ -11,11 +11,9 @@ export const DEFAULT_CLUSTER_ID = 1; export const DEFAULT_NUM_SHARDS = 8; /** - * DefaultShardInfo is default configuration for The Waku Network. + * DefaultNetworkConfig is default configuration for The Waku Network. */ -export const DefaultShardInfo: ShardInfo = { +export const DefaultNetworkConfig: AutoSharding = { clusterId: DEFAULT_CLUSTER_ID, - shards: [0, 1, 2, 3, 4, 5, 6, 7, 8] + numShardsInCluster: DEFAULT_NUM_SHARDS }; - -export const DefaultNetworkConfig = DefaultShardInfo; diff --git a/packages/interfaces/src/enr.ts b/packages/interfaces/src/enr.ts index ec4b4ab54c..01d4bcb751 100644 --- a/packages/interfaces/src/enr.ts +++ b/packages/interfaces/src/enr.ts @@ -2,7 +2,7 @@ import type { PeerId } from "@libp2p/interface"; import type { PeerInfo } from "@libp2p/interface"; import type { Multiaddr } from "@multiformats/multiaddr"; -import { ShardInfo } from "./sharding.js"; +import { RelayShards } from "./sharding.js"; export type ENRKey = string; export type ENRValue = Uint8Array; @@ -36,7 +36,7 @@ export interface IEnr extends Map { multiaddrs?: Multiaddr[]; waku2?: Waku2; peerInfo: PeerInfo | undefined; - shardInfo?: ShardInfo; + shardInfo?: RelayShards; /** * @deprecated: use { @link IEnr.peerInfo } instead. diff --git a/packages/interfaces/src/message.ts b/packages/interfaces/src/message.ts index 8c1ae1dd20..fda16fb160 100644 --- a/packages/interfaces/src/message.ts +++ b/packages/interfaces/src/message.ts @@ -1,13 +1,5 @@ import type { ContentTopic, PubsubTopic } from "./misc.js"; - -export interface SingleShardInfo { - clusterId: number; - /** - * TODO: make shard required - * Specifying this field indicates to the encoder/decoder that static sharding must be used. - */ - shard?: number; -} +import type { IRoutingInfo } from "./sharding.js"; export interface IRateLimitProof { proof: Uint8Array; @@ -79,38 +71,17 @@ export interface IMetaSetter { (message: IProtoMessage & { meta: undefined }): Uint8Array; } -export interface EncoderOptions { - /** - * @deprecated - */ - pubsubTopic?: PubsubTopic; - pubsubTopicShardInfo?: SingleShardInfo; - /** The content topic to set on outgoing messages. */ - contentTopic: string; - /** - * An optional flag to mark message as ephemeral, i.e., not to be stored by Waku Store nodes. - * @defaultValue `false` - */ - ephemeral?: boolean; - /** - * A function called when encoding messages to set the meta field. - * @param IProtoMessage The message encoded for wire, without the meta field. - * If encryption is used, `metaSetter` only accesses _encrypted_ payload. - */ - metaSetter?: IMetaSetter; -} - export interface IEncoder { - pubsubTopic: PubsubTopic; contentTopic: string; ephemeral: boolean; + routingInfo: IRoutingInfo; toWire: (message: IMessage) => Promise; toProtoObj: (message: IMessage) => Promise; } export interface IDecoder { - pubsubTopic: PubsubTopic; contentTopic: string; + routingInfo: IRoutingInfo; fromWireToProtoObj: (bytes: Uint8Array) => Promise; fromProtoObj: ( pubsubTopic: string, diff --git a/packages/interfaces/src/metadata.ts b/packages/interfaces/src/metadata.ts index 32ce59c2e6..b9714d92f8 100644 --- a/packages/interfaces/src/metadata.ts +++ b/packages/interfaces/src/metadata.ts @@ -1,9 +1,9 @@ import type { PeerId } from "@libp2p/interface"; import { ThisOrThat } from "./misc.js"; -import type { ClusterId, ShardInfo } from "./sharding.js"; +import type { ClusterId, RelayShards } from "./sharding.js"; -export type MetadataQueryResult = ThisOrThat<"shardInfo", ShardInfo>; +export type MetadataQueryResult = ThisOrThat<"shardInfo", RelayShards>; export interface IMetadata { readonly multicodec: string; diff --git a/packages/interfaces/src/protocols.ts b/packages/interfaces/src/protocols.ts index 1086bc28d9..9b0b8fc44c 100644 --- a/packages/interfaces/src/protocols.ts +++ b/packages/interfaces/src/protocols.ts @@ -6,7 +6,7 @@ import type { CreateLibp2pOptions } from "./libp2p.js"; import type { LightPushProtocolOptions } from "./light_push.js"; import type { IDecodedMessage } from "./message.js"; import type { ThisAndThat, ThisOrThat } from "./misc.js"; -import type { AutoSharding, StaticSharding } from "./sharding.js"; +import { NetworkConfig } from "./sharding.js"; import type { StoreProtocolOptions } from "./store.js"; export enum Protocols { @@ -16,8 +16,6 @@ export enum Protocols { Filter = "filter" } -export type NetworkConfig = StaticSharding | AutoSharding; - export type CreateNodeOptions = { /** * Set the user agent string to be used in identification of the node. diff --git a/packages/interfaces/src/sharding.ts b/packages/interfaces/src/sharding.ts index c2364b6396..bc963ecc11 100644 --- a/packages/interfaces/src/sharding.ts +++ b/packages/interfaces/src/sharding.ts @@ -1,6 +1,12 @@ -export type ShardInfo = { - clusterId: number; - shards: number[]; +/** + * Configuration for a Waku network. All nodes in a given network/cluster + * should have the same configuration. + */ +export type NetworkConfig = StaticSharding | AutoSharding; + +export type RelayShards = { + clusterId: ClusterId; + shards: ShardId[]; }; export type ContentTopicInfo = { @@ -8,6 +14,36 @@ export type ContentTopicInfo = { contentTopics: string[]; }; -export type StaticSharding = ShardInfo; -export type AutoSharding = ContentTopicInfo; +export type StaticSharding = { + clusterId: ClusterId; +}; +export type AutoSharding = { + clusterId: ClusterId; + numShardsInCluster: number; +}; export type ClusterId = number; +export type ShardId = number; + +/** + * Routing Information for a given message. + */ +export interface IRoutingInfoAutoSharding { + pubsubTopic: string; + shardId: ShardId; + networkConfig: AutoSharding; + contentTopic: string; + isAutoSharding(): boolean; + isStaticSharding(): boolean; +} + +export interface IRoutingInfoStaticSharding { + pubsubTopic: string; + shardId: ShardId; + networkConfig: StaticSharding; + isAutoSharding(): boolean; + isStaticSharding(): boolean; +} + +export type IRoutingInfo = + | IRoutingInfoAutoSharding + | IRoutingInfoStaticSharding; diff --git a/packages/interfaces/src/store.ts b/packages/interfaces/src/store.ts index 014842aaa6..a8feebb236 100644 --- a/packages/interfaces/src/store.ts +++ b/packages/interfaces/src/store.ts @@ -1,4 +1,5 @@ import type { IDecodedMessage, IDecoder } from "./message.js"; +import { IRoutingInfo } from "./sharding.js"; export type StoreCursor = Uint8Array; @@ -15,10 +16,10 @@ export type QueryRequestParams = { includeData: boolean; /** - * The pubsub topic to query. This field is mandatory. - * The query will only return messages that were published on this specific pubsub topic. + * The routing information to query. This field is mandatory. + * The query will only return messages that were published on this specific route (cluster and shard). */ - pubsubTopic: string; + routingInfo: IRoutingInfo; /** * The content topics to filter the messages. diff --git a/packages/interfaces/src/waku.ts b/packages/interfaces/src/waku.ts index 049588c819..5c99f716e6 100644 --- a/packages/interfaces/src/waku.ts +++ b/packages/interfaces/src/waku.ts @@ -13,21 +13,12 @@ import type { ILightPush } from "./light_push.js"; import { IDecodedMessage, IDecoder, IEncoder } from "./message.js"; import type { Protocols } from "./protocols.js"; import type { IRelay } from "./relay.js"; +import type { ShardId } from "./sharding.js"; import type { IStore } from "./store.js"; -type AutoShardSingle = { - clusterId: number; - shardsUnderCluster: number; -}; - -type StaticShardSingle = { - clusterId: number; - shard: number; -}; - export type CreateDecoderParams = { contentTopic: string; - shardInfo?: AutoShardSingle | StaticShardSingle; + shardId?: ShardId; }; export type CreateEncoderParams = CreateDecoderParams & { diff --git a/packages/message-encryption/src/ecies.spec.ts b/packages/message-encryption/src/ecies.spec.ts index 55743820a0..9020aeffb6 100644 --- a/packages/message-encryption/src/ecies.spec.ts +++ b/packages/message-encryption/src/ecies.spec.ts @@ -1,13 +1,19 @@ import { IProtoMessage } from "@waku/interfaces"; -import { contentTopicToPubsubTopic } from "@waku/utils"; +import { createRoutingInfo } from "@waku/utils"; import { expect } from "chai"; import fc from "fast-check"; import { getPublicKey } from "./crypto/index.js"; import { createDecoder, createEncoder } from "./ecies.js"; -const contentTopic = "/js-waku/1/tests/bytes"; -const pubsubTopic = contentTopicToPubsubTopic(contentTopic); +const testContentTopic = "/js-waku/1/tests/bytes"; +const testRoutingInfo = createRoutingInfo( + { + clusterId: 0, + numShardsInCluster: 14 + }, + { contentTopic: testContentTopic } +); describe("Ecies Encryption", function () { this.timeout(20000); @@ -20,19 +26,27 @@ describe("Ecies Encryption", function () { const publicKey = getPublicKey(privateKey); const encoder = createEncoder({ - contentTopic, + contentTopic: testContentTopic, + routingInfo: testRoutingInfo, publicKey }); const bytes = await encoder.toWire({ payload }); - const decoder = createDecoder(contentTopic, privateKey); + const decoder = createDecoder( + testContentTopic, + testRoutingInfo, + privateKey + ); const protoResult = await decoder.fromWireToProtoObj(bytes!); if (!protoResult) throw "Failed to proto decode"; - const result = await decoder.fromProtoObj(pubsubTopic, protoResult); + const result = await decoder.fromProtoObj( + testRoutingInfo.pubsubTopic, + protoResult + ); if (!result) throw "Failed to decode"; - expect(result.contentTopic).to.equal(contentTopic); - expect(result.pubsubTopic).to.equal(pubsubTopic); + expect(result.contentTopic).to.equal(testContentTopic); + expect(result.pubsubTopic).to.equal(testRoutingInfo.pubsubTopic); expect(result.version).to.equal(1); expect(result?.payload).to.deep.equal(payload); expect(result.signature).to.be.undefined; @@ -56,20 +70,28 @@ describe("Ecies Encryption", function () { const bobPublicKey = getPublicKey(bobPrivateKey); const encoder = createEncoder({ - contentTopic, + contentTopic: testContentTopic, + routingInfo: testRoutingInfo, publicKey: bobPublicKey, sigPrivKey: alicePrivateKey }); const bytes = await encoder.toWire({ payload }); - const decoder = createDecoder(contentTopic, bobPrivateKey); + const decoder = createDecoder( + testContentTopic, + testRoutingInfo, + bobPrivateKey + ); const protoResult = await decoder.fromWireToProtoObj(bytes!); if (!protoResult) throw "Failed to proto decode"; - const result = await decoder.fromProtoObj(pubsubTopic, protoResult); + const result = await decoder.fromProtoObj( + testRoutingInfo.pubsubTopic, + protoResult + ); if (!result) throw "Failed to decode"; - expect(result.contentTopic).to.equal(contentTopic); - expect(result.pubsubTopic).to.equal(pubsubTopic); + expect(result.contentTopic).to.equal(testContentTopic); + expect(result.pubsubTopic).to.equal(testRoutingInfo.pubsubTopic); expect(result.version).to.equal(1); expect(result?.payload).to.deep.equal(payload); expect(result.signature).to.not.be.undefined; @@ -97,16 +119,24 @@ describe("Ecies Encryption", function () { }; const encoder = createEncoder({ - contentTopic, + contentTopic: testContentTopic, + routingInfo: testRoutingInfo, publicKey, metaSetter }); const bytes = await encoder.toWire({ payload }); - const decoder = createDecoder(contentTopic, privateKey); + const decoder = createDecoder( + testContentTopic, + testRoutingInfo, + privateKey + ); const protoResult = await decoder.fromWireToProtoObj(bytes!); if (!protoResult) throw "Failed to proto decode"; - const result = await decoder.fromProtoObj(pubsubTopic, protoResult); + const result = await decoder.fromProtoObj( + testRoutingInfo.pubsubTopic, + protoResult + ); if (!result) throw "Failed to decode"; const expectedMeta = metaSetter({ @@ -131,6 +161,7 @@ describe("Ensures content topic is defined", () => { const wrapper = function (): void { createEncoder({ contentTopic: undefined as unknown as string, + routingInfo: testRoutingInfo, publicKey: new Uint8Array() }); }; @@ -139,21 +170,29 @@ describe("Ensures content topic is defined", () => { }); it("Encoder throws on empty string content topic", () => { const wrapper = function (): void { - createEncoder({ contentTopic: "", publicKey: new Uint8Array() }); + createEncoder({ + contentTopic: "", + routingInfo: testRoutingInfo, + publicKey: new Uint8Array() + }); }; expect(wrapper).to.throw("Content topic must be specified"); }); it("Decoder throws on undefined content topic", () => { const wrapper = function (): void { - createDecoder(undefined as unknown as string, new Uint8Array()); + createDecoder( + undefined as unknown as string, + testRoutingInfo, + new Uint8Array() + ); }; expect(wrapper).to.throw("Content topic must be specified"); }); it("Decoder throws on empty string content topic", () => { const wrapper = function (): void { - createDecoder("", new Uint8Array()); + createDecoder("", testRoutingInfo, new Uint8Array()); }; expect(wrapper).to.throw("Content topic must be specified"); diff --git a/packages/message-encryption/src/ecies.ts b/packages/message-encryption/src/ecies.ts index 83bb05ceb2..4fec13531b 100644 --- a/packages/message-encryption/src/ecies.ts +++ b/packages/message-encryption/src/ecies.ts @@ -1,17 +1,14 @@ import { Decoder as DecoderV0 } from "@waku/core/lib/message/version_0"; import { - type EncoderOptions as BaseEncoderOptions, type IDecoder, type IEncoder, type IEncryptedMessage, type IMessage, type IMetaSetter, - type IProtoMessage, - type PubsubTopic, - type SingleShardInfo + type IProtoMessage } from "@waku/interfaces"; import { WakuMessage } from "@waku/proto"; -import { determinePubsubTopic, Logger } from "@waku/utils"; +import { Logger, RoutingInfo } from "@waku/utils"; import { generatePrivateKey } from "./crypto/utils.js"; import { DecodedMessage } from "./decoded_message.js"; @@ -35,8 +32,8 @@ const log = new Logger("message-encryption:ecies"); class Encoder implements IEncoder { public constructor( - public pubsubTopic: PubsubTopic, public contentTopic: string, + public routingInfo: RoutingInfo, private publicKey: Uint8Array, private sigPrivKey?: Uint8Array, public ephemeral: boolean = false, @@ -81,11 +78,24 @@ class Encoder implements IEncoder { } } -export interface EncoderOptions extends BaseEncoderOptions { +export interface EncoderOptions { /** - * @deprecated + * The routing information for messages to encode. */ - pubsubTopic?: PubsubTopic; + routingInfo: RoutingInfo; + /** The content topic to set on outgoing messages. */ + contentTopic: string; + /** + * An optional flag to mark message as ephemeral, i.e., not to be stored by Waku Store nodes. + * @defaultValue `false` + */ + ephemeral?: boolean; + /** + * A function called when encoding messages to set the meta field. + * @param IProtoMessage The message encoded for wire, without the meta field. + * If encryption is used, `metaSetter` only accesses _encrypted_ payload. + */ + metaSetter?: IMetaSetter; /** The public key to encrypt the payload for. */ publicKey: Uint8Array; /** An optional private key to be used to sign the payload before encryption. */ @@ -105,17 +115,16 @@ export interface EncoderOptions extends BaseEncoderOptions { * in [26/WAKU2-PAYLOAD](https://rfc.vac.dev/spec/26/). */ export function createEncoder({ - pubsubTopic, - pubsubTopicShardInfo, contentTopic, + routingInfo, publicKey, sigPrivKey, ephemeral = false, metaSetter }: EncoderOptions): Encoder { return new Encoder( - determinePubsubTopic(contentTopic, pubsubTopic ?? pubsubTopicShardInfo), contentTopic, + routingInfo, publicKey, sigPrivKey, ephemeral, @@ -125,11 +134,11 @@ export function createEncoder({ class Decoder extends DecoderV0 implements IDecoder { public constructor( - pubsubTopic: PubsubTopic, contentTopic: string, + routingInfo: RoutingInfo, private privateKey: Uint8Array ) { - super(pubsubTopic, contentTopic); + super(contentTopic, routingInfo); } public async fromProtoObj( @@ -201,12 +210,8 @@ class Decoder extends DecoderV0 implements IDecoder { */ export function createDecoder( contentTopic: string, - privateKey: Uint8Array, - pubsubTopicShardInfo?: SingleShardInfo | PubsubTopic + routingInfo: RoutingInfo, + privateKey: Uint8Array ): Decoder { - return new Decoder( - determinePubsubTopic(contentTopic, pubsubTopicShardInfo), - contentTopic, - privateKey - ); + return new Decoder(contentTopic, routingInfo, privateKey); } diff --git a/packages/message-encryption/src/symmetric.spec.ts b/packages/message-encryption/src/symmetric.spec.ts index 9016f66604..de6026456e 100644 --- a/packages/message-encryption/src/symmetric.spec.ts +++ b/packages/message-encryption/src/symmetric.spec.ts @@ -1,13 +1,19 @@ import { IProtoMessage } from "@waku/interfaces"; -import { contentTopicToPubsubTopic } from "@waku/utils"; +import { createRoutingInfo } from "@waku/utils"; import { expect } from "chai"; import fc from "fast-check"; import { getPublicKey } from "./crypto/index.js"; import { createDecoder, createEncoder } from "./symmetric.js"; -const contentTopic = "/js-waku/1/tests/bytes"; -const pubsubTopic = contentTopicToPubsubTopic(contentTopic); +const testContentTopic = "/js-waku/1/tests/bytes"; +const testRoutingInfo = createRoutingInfo( + { + clusterId: 0, + numShardsInCluster: 14 + }, + { contentTopic: testContentTopic } +); describe("Symmetric Encryption", function () { it("Round trip binary encryption [symmetric, no signature]", async function () { @@ -17,19 +23,27 @@ describe("Symmetric Encryption", function () { fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), async (payload, symKey) => { const encoder = createEncoder({ - contentTopic, + contentTopic: testContentTopic, + routingInfo: testRoutingInfo, symKey }); const bytes = await encoder.toWire({ payload }); - const decoder = createDecoder(contentTopic, symKey); + const decoder = createDecoder( + testContentTopic, + testRoutingInfo, + symKey + ); const protoResult = await decoder.fromWireToProtoObj(bytes!); if (!protoResult) throw "Failed to proto decode"; - const result = await decoder.fromProtoObj(pubsubTopic, protoResult); + const result = await decoder.fromProtoObj( + testRoutingInfo.pubsubTopic, + protoResult + ); if (!result) throw "Failed to decode"; - expect(result.contentTopic).to.equal(contentTopic); - expect(result.pubsubTopic).to.equal(pubsubTopic); + expect(result.contentTopic).to.equal(testContentTopic); + expect(result.pubsubTopic).to.equal(testRoutingInfo.pubsubTopic); expect(result.version).to.equal(1); expect(result?.payload).to.deep.equal(payload); expect(result.signature).to.be.undefined; @@ -50,20 +64,28 @@ describe("Symmetric Encryption", function () { const sigPubKey = getPublicKey(sigPrivKey); const encoder = createEncoder({ - contentTopic, + contentTopic: testContentTopic, + routingInfo: testRoutingInfo, symKey, sigPrivKey }); const bytes = await encoder.toWire({ payload }); - const decoder = createDecoder(contentTopic, symKey); + const decoder = createDecoder( + testContentTopic, + testRoutingInfo, + symKey + ); const protoResult = await decoder.fromWireToProtoObj(bytes!); if (!protoResult) throw "Failed to proto decode"; - const result = await decoder.fromProtoObj(pubsubTopic, protoResult); + const result = await decoder.fromProtoObj( + testRoutingInfo.pubsubTopic, + protoResult + ); if (!result) throw "Failed to decode"; - expect(result.contentTopic).to.equal(contentTopic); - expect(result.pubsubTopic).to.equal(pubsubTopic); + expect(result.contentTopic).to.equal(testContentTopic); + expect(result.pubsubTopic).to.equal(testRoutingInfo.pubsubTopic); expect(result.version).to.equal(1); expect(result?.payload).to.deep.equal(payload); expect(result.signature).to.not.be.undefined; @@ -90,16 +112,24 @@ describe("Symmetric Encryption", function () { }; const encoder = createEncoder({ - contentTopic, + contentTopic: testContentTopic, + routingInfo: testRoutingInfo, symKey, metaSetter }); const bytes = await encoder.toWire({ payload }); - const decoder = createDecoder(contentTopic, symKey); + const decoder = createDecoder( + testContentTopic, + testRoutingInfo, + symKey + ); const protoResult = await decoder.fromWireToProtoObj(bytes!); if (!protoResult) throw "Failed to proto decode"; - const result = await decoder.fromProtoObj(pubsubTopic, protoResult); + const result = await decoder.fromProtoObj( + testRoutingInfo.pubsubTopic, + protoResult + ); if (!result) throw "Failed to decode"; const expectedMeta = metaSetter({ @@ -124,6 +154,7 @@ describe("Ensures content topic is defined", () => { const wrapper = function (): void { createEncoder({ contentTopic: undefined as unknown as string, + routingInfo: testRoutingInfo, symKey: new Uint8Array() }); }; @@ -132,21 +163,29 @@ describe("Ensures content topic is defined", () => { }); it("Encoder throws on empty string content topic", () => { const wrapper = function (): void { - createEncoder({ contentTopic: "", symKey: new Uint8Array() }); + createEncoder({ + contentTopic: "", + routingInfo: testRoutingInfo, + symKey: new Uint8Array() + }); }; expect(wrapper).to.throw("Content topic must be specified"); }); it("Decoder throws on undefined content topic", () => { const wrapper = function (): void { - createDecoder(undefined as unknown as string, new Uint8Array()); + createDecoder( + undefined as unknown as string, + testRoutingInfo, + new Uint8Array() + ); }; expect(wrapper).to.throw("Content topic must be specified"); }); it("Decoder throws on empty string content topic", () => { const wrapper = function (): void { - createDecoder("", new Uint8Array()); + createDecoder("", testRoutingInfo, new Uint8Array()); }; expect(wrapper).to.throw("Content topic must be specified"); diff --git a/packages/message-encryption/src/symmetric.ts b/packages/message-encryption/src/symmetric.ts index 732f0755d9..80692dd834 100644 --- a/packages/message-encryption/src/symmetric.ts +++ b/packages/message-encryption/src/symmetric.ts @@ -1,17 +1,15 @@ import { Decoder as DecoderV0 } from "@waku/core/lib/message/version_0"; import type { - EncoderOptions as BaseEncoderOptions, IDecoder, IEncoder, IEncryptedMessage, IMessage, IMetaSetter, IProtoMessage, - PubsubTopic, - SingleShardInfo + IRoutingInfo } from "@waku/interfaces"; import { WakuMessage } from "@waku/proto"; -import { determinePubsubTopic, Logger } from "@waku/utils"; +import { Logger, RoutingInfo } from "@waku/utils"; import { generateSymmetricKey } from "./crypto/utils.js"; import { DecodedMessage } from "./decoded_message.js"; @@ -35,8 +33,8 @@ const log = new Logger("message-encryption:symmetric"); class Encoder implements IEncoder { public constructor( - public pubsubTopic: PubsubTopic, public contentTopic: string, + public routingInfo: IRoutingInfo, private symKey: Uint8Array, private sigPrivKey?: Uint8Array, public ephemeral: boolean = false, @@ -81,7 +79,24 @@ class Encoder implements IEncoder { } } -export interface EncoderOptions extends BaseEncoderOptions { +export interface EncoderOptions { + /** + * The routing information for messages to encode. + */ + routingInfo: RoutingInfo; + /** The content topic to set on outgoing messages. */ + contentTopic: string; + /** + * An optional flag to mark message as ephemeral, i.e., not to be stored by Waku Store nodes. + * @defaultValue `false` + */ + ephemeral?: boolean; + /** + * A function called when encoding messages to set the meta field. + * @param IProtoMessage The message encoded for wire, without the meta field. + * If encryption is used, `metaSetter` only accesses _encrypted_ payload. + */ + metaSetter?: IMetaSetter; /** The symmetric key to encrypt the payload with. */ symKey: Uint8Array; /** An optional private key to be used to sign the payload before encryption. */ @@ -101,17 +116,16 @@ export interface EncoderOptions extends BaseEncoderOptions { * in [26/WAKU2-PAYLOAD](https://rfc.vac.dev/spec/26/). */ export function createEncoder({ - pubsubTopic, - pubsubTopicShardInfo, contentTopic, + routingInfo, symKey, sigPrivKey, ephemeral = false, metaSetter }: EncoderOptions): Encoder { return new Encoder( - determinePubsubTopic(contentTopic, pubsubTopic ?? pubsubTopicShardInfo), contentTopic, + routingInfo, symKey, sigPrivKey, ephemeral, @@ -121,11 +135,11 @@ export function createEncoder({ class Decoder extends DecoderV0 implements IDecoder { public constructor( - pubsubTopic: PubsubTopic, contentTopic: string, + routingInfo: RoutingInfo, private symKey: Uint8Array ) { - super(pubsubTopic, contentTopic); + super(contentTopic, routingInfo); } public async fromProtoObj( @@ -193,16 +207,13 @@ class Decoder extends DecoderV0 implements IDecoder { * decode incoming messages. * * @param contentTopic The resulting decoder will only decode messages with this content topic. + * @param routingInfo Routing information, depends on the network config (static vs auto sharding) * @param symKey The symmetric key used to decrypt the message. */ export function createDecoder( contentTopic: string, - symKey: Uint8Array, - pubsubTopicShardInfo?: SingleShardInfo | PubsubTopic + routingInfo: RoutingInfo, + symKey: Uint8Array ): Decoder { - return new Decoder( - determinePubsubTopic(contentTopic, pubsubTopicShardInfo), - contentTopic, - symKey - ); + return new Decoder(contentTopic, routingInfo, symKey); } diff --git a/packages/relay/src/create.ts b/packages/relay/src/create.ts index 0d330dffd6..8ce2cd1de8 100644 --- a/packages/relay/src/create.ts +++ b/packages/relay/src/create.ts @@ -1,7 +1,5 @@ -import type { CreateNodeOptions, RelayNode } from "@waku/interfaces"; -import { DefaultNetworkConfig } from "@waku/interfaces"; +import { CreateNodeOptions, RelayNode } from "@waku/interfaces"; import { createLibp2pAndUpdateOptions, WakuNode } from "@waku/sdk"; -import { derivePubsubTopicsFromNetworkConfig } from "@waku/utils"; import { Relay, RelayCreateOptions, wakuGossipSub } from "./relay.js"; @@ -16,7 +14,7 @@ import { Relay, RelayCreateOptions, wakuGossipSub } from "./relay.js"; * or use this function with caution. */ export async function createRelayNode( - options: CreateNodeOptions & Partial + options: CreateNodeOptions & RelayCreateOptions ): Promise { options = { ...options, @@ -29,9 +27,9 @@ export async function createRelayNode( }; const libp2p = await createLibp2pAndUpdateOptions(options); - const pubsubTopics = derivePubsubTopicsFromNetworkConfig( - options.networkConfig ?? DefaultNetworkConfig - ); + + const pubsubTopics = options.routingInfos.map((ri) => ri.pubsubTopic); + const relay = new Relay({ pubsubTopics, libp2p diff --git a/packages/relay/src/message_validator.spec.ts b/packages/relay/src/message_validator.spec.ts index 313898708f..5f2a4a57f6 100644 --- a/packages/relay/src/message_validator.spec.ts +++ b/packages/relay/src/message_validator.spec.ts @@ -3,14 +3,21 @@ import { TopicValidatorResult } from "@libp2p/interface"; import type { UnsignedMessage } from "@libp2p/interface"; import { peerIdFromPrivateKey } from "@libp2p/peer-id"; import { createEncoder } from "@waku/core"; -import { determinePubsubTopic } from "@waku/utils"; +import { createRoutingInfo } from "@waku/utils"; import { expect } from "chai"; import fc from "fast-check"; import { messageValidator } from "./message_validator.js"; -const TestContentTopic = "/app/1/topic/utf8"; -const TestPubsubTopic = determinePubsubTopic(TestContentTopic); +const testContentTopic = "/app/1/topic/utf8"; +const testRoutingInfo = createRoutingInfo( + { + clusterId: 0, + numShardsInCluster: 8 + }, + { contentTopic: testContentTopic } +); +const testPubsubTopic = testRoutingInfo.pubsubTopic; describe("Message Validator", () => { it("Accepts a valid Waku Message", async () => { @@ -20,14 +27,14 @@ describe("Message Validator", () => { const peerId = peerIdFromPrivateKey(privateKey); const encoder = createEncoder({ - contentTopic: TestContentTopic, - pubsubTopic: TestPubsubTopic + contentTopic: testContentTopic, + routingInfo: testRoutingInfo }); const bytes = await encoder.toWire({ payload }); const message: UnsignedMessage = { type: "unsigned", - topic: TestPubsubTopic, + topic: testPubsubTopic, data: bytes }; @@ -46,7 +53,7 @@ describe("Message Validator", () => { const message: UnsignedMessage = { type: "unsigned", - topic: TestPubsubTopic, + topic: testPubsubTopic, data }; diff --git a/packages/relay/src/relay.ts b/packages/relay/src/relay.ts index 98aabde45d..571a6a78cd 100644 --- a/packages/relay/src/relay.ts +++ b/packages/relay/src/relay.ts @@ -22,14 +22,14 @@ import { PubsubTopic, SDKProtocolResult } from "@waku/interfaces"; -import { isWireSizeUnderCap, toAsyncIterator } from "@waku/utils"; +import { isWireSizeUnderCap, RoutingInfo, toAsyncIterator } from "@waku/utils"; import { pushOrInitMapSet } from "@waku/utils"; import { Logger } from "@waku/utils"; import { pEvent } from "p-event"; import { RelayCodecs } from "./constants.js"; import { messageValidator } from "./message_validator.js"; -import { TopicOnlyDecoder } from "./topic_only_message.js"; +import { ContentTopicOnlyDecoder } from "./topic_only_message.js"; const log = new Logger("relay"); @@ -38,7 +38,9 @@ export type Observer = { callback: Callback; }; -export type RelayCreateOptions = CreateNodeOptions & GossipsubOpts; +export type RelayCreateOptions = CreateNodeOptions & { + routingInfos: RoutingInfo[]; +} & Partial; export type ContentTopic = string; type ActiveSubscriptions = Map; @@ -53,7 +55,7 @@ type RelayConstructorParams = { * Throws if libp2p.pubsub does not support Waku Relay */ export class Relay implements IRelay { - public readonly pubsubTopics: Set; + public pubsubTopics: Set; private defaultDecoder: IDecoder; public static multicodec: string = RelayCodecs[0]; @@ -73,6 +75,7 @@ export class Relay implements IRelay { } this.gossipSub = params.libp2p.services.pubsub as GossipSub; + this.pubsubTopics = new Set(params.pubsubTopics); if (this.gossipSub.isStarted()) { @@ -82,7 +85,7 @@ export class Relay implements IRelay { this.observers = new Map(); // TODO: User might want to decide what decoder should be used (e.g. for RLN) - this.defaultDecoder = new TopicOnlyDecoder(params.pubsubTopics[0]); + this.defaultDecoder = new ContentTopicOnlyDecoder(); } /** @@ -124,7 +127,7 @@ export class Relay implements IRelay { encoder: IEncoder, message: IMessage ): Promise { - const { pubsubTopic } = encoder; + const { pubsubTopic } = encoder.routingInfo; if (!this.pubsubTopics.has(pubsubTopic)) { log.error("Failed to send waku relay: topic not configured"); return { @@ -176,7 +179,7 @@ export class Relay implements IRelay { const observers: Array<[PubsubTopic, Observer]> = []; for (const decoder of Array.isArray(decoders) ? decoders : [decoders]) { - const { pubsubTopic } = decoder; + const { pubsubTopic } = decoder.routingInfo; const ctObs: Map>> = this.observers.get( pubsubTopic ) ?? new Map(); @@ -240,8 +243,9 @@ export class Relay implements IRelay { pubsubTopic: string, bytes: Uint8Array ): Promise { - const topicOnlyMsg = await this.defaultDecoder.fromWireToProtoObj(bytes); - if (!topicOnlyMsg || !topicOnlyMsg.contentTopic) { + const contentTopicOnlyMsg = + await this.defaultDecoder.fromWireToProtoObj(bytes); + if (!contentTopicOnlyMsg || !contentTopicOnlyMsg.contentTopic) { log.warn("Message does not have a content topic, skipping"); return; } @@ -253,9 +257,9 @@ export class Relay implements IRelay { } // Retrieve the set of observers for the given contentTopic - const observers = contentTopicMap.get(topicOnlyMsg.contentTopic) as Set< - Observer - >; + const observers = contentTopicMap.get( + contentTopicOnlyMsg.contentTopic + ) as Set>; if (!observers) { return; } @@ -277,7 +281,7 @@ export class Relay implements IRelay { } else { log.error( "Failed to decode messages on", - topicOnlyMsg.contentTopic + contentTopicOnlyMsg.contentTopic ); } } catch (error) { diff --git a/packages/relay/src/topic_only_message.ts b/packages/relay/src/topic_only_message.ts index 9a9410cc07..d5332b9d9f 100644 --- a/packages/relay/src/topic_only_message.ts +++ b/packages/relay/src/topic_only_message.ts @@ -1,15 +1,18 @@ -import { message } from "@waku/core"; import type { IDecoder, IProtoMessage, - ITopicOnlyMessage, - PubsubTopic + IRoutingInfo, + ITopicOnlyMessage } from "@waku/interfaces"; import { TopicOnlyMessage as ProtoTopicOnlyMessage } from "@waku/proto"; export class TopicOnlyMessage implements ITopicOnlyMessage { - public version = message.version_0.Version; - public payload: Uint8Array = new Uint8Array(); + public get version(): number { + throw "Only content topic can be accessed on this message"; + } + public get payload(): Uint8Array { + throw "Only content topic can be accessed on this message"; + } public rateLimitProof: undefined; public timestamp: undefined; public meta: undefined; @@ -26,11 +29,16 @@ export class TopicOnlyMessage implements ITopicOnlyMessage { } // This decoder is used only for reading `contentTopic` from the WakuMessage -export class TopicOnlyDecoder implements IDecoder { - public contentTopic = ""; +export class ContentTopicOnlyDecoder implements IDecoder { + public constructor() {} - // pubsubTopic is ignored - public constructor(public pubsubTopic: PubsubTopic) {} + public get contentTopic(): string { + throw "ContentTopic is not available on this decoder, it is only meant to decode the content topic for any message"; + } + + public get routingInfo(): IRoutingInfo { + throw "RoutingInfo is not available on this decoder, it is only meant to decode the content topic for any message"; + } public fromWireToProtoObj( bytes: Uint8Array diff --git a/packages/reliability-tests/tests/high-throughput.spec.ts b/packages/reliability-tests/tests/high-throughput.spec.ts index 8414aab53d..357efed5f7 100644 --- a/packages/reliability-tests/tests/high-throughput.spec.ts +++ b/packages/reliability-tests/tests/high-throughput.spec.ts @@ -1,16 +1,6 @@ import { LightNode, Protocols } from "@waku/interfaces"; -import { - createDecoder, - createEncoder, - createLightNode, - utf8ToBytes -} from "@waku/sdk"; -import { - delay, - shardInfoToPubsubTopics, - singleShardInfosToShardInfo, - singleShardInfoToPubsubTopic -} from "@waku/utils"; +import { createDecoder, createLightNode, utf8ToBytes } from "@waku/sdk"; +import { createRoutingInfo, delay } from "@waku/utils"; import { expect } from "chai"; import { @@ -41,8 +31,7 @@ describe("High Throughput Messaging", function () { }); it("Send/Receive thousands of messages quickly", async function () { - const singleShardInfo = { clusterId: 0, shard: 0 }; - const shardInfo = singleShardInfosToShardInfo([singleShardInfo]); + const networkConfig = { clusterId: 0, numShardsInCluster: 8 }; const testStart = new Date(); const testEnd = Date.now() + testDurationMs; @@ -60,8 +49,8 @@ describe("High Throughput Messaging", function () { store: true, filter: true, relay: true, - clusterId: 0, - shard: [0], + clusterId: networkConfig.clusterId, + numShardsInNetwork: networkConfig.numShardsInCluster, contentTopic: [ContentTopic] }, { retries: 3 } @@ -69,29 +58,23 @@ describe("High Throughput Messaging", function () { await delay(1000); - await nwaku.ensureSubscriptions(shardInfoToPubsubTopics(shardInfo)); + // TODO await nwaku.ensureSubscriptions(shardInfoToPubsubTopics(shardInfo)); - waku = await createLightNode({ networkConfig: shardInfo }); + waku = await createLightNode({ networkConfig }); await waku.start(); await waku.dial(await nwaku.getMultiaddrWithId()); await waku.waitForPeers([Protocols.Filter]); - const decoder = createDecoder(ContentTopic, singleShardInfo); + const routingInfo = createRoutingInfo(networkConfig, { + contentTopic: ContentTopic + }); + const decoder = createDecoder(ContentTopic, routingInfo); const hasSubscribed = await waku.filter.subscribe( [decoder], messageCollector.callback ); if (!hasSubscribed) throw new Error("Failed to subscribe from the start."); - const encoder = createEncoder({ - contentTopic: ContentTopic, - pubsubTopicShardInfo: singleShardInfo - }); - - expect(encoder.pubsubTopic).to.eq( - singleShardInfoToPubsubTopic(singleShardInfo) - ); - let messageId = 0; // Send messages as fast as possible until testEnd @@ -107,7 +90,8 @@ describe("High Throughput Messaging", function () { ServiceNode.toMessageRpcQuery({ contentTopic: ContentTopic, payload: utf8ToBytes(message) - }) + }), + routingInfo ); sent = true; @@ -119,7 +103,7 @@ describe("High Throughput Messaging", function () { messageCollector.verifyReceivedMessage(0, { expectedMessageText: message, expectedContentTopic: ContentTopic, - expectedPubsubTopic: shardInfoToPubsubTopics(shardInfo)[0] + expectedPubsubTopic: routingInfo.pubsubTopic }); } } catch (e: any) { diff --git a/packages/reliability-tests/tests/longevity.spec.ts b/packages/reliability-tests/tests/longevity.spec.ts index 3abaddcde5..3e7848842f 100644 --- a/packages/reliability-tests/tests/longevity.spec.ts +++ b/packages/reliability-tests/tests/longevity.spec.ts @@ -1,16 +1,6 @@ import { LightNode, Protocols } from "@waku/interfaces"; -import { - createDecoder, - createEncoder, - createLightNode, - utf8ToBytes -} from "@waku/sdk"; -import { - delay, - shardInfoToPubsubTopics, - singleShardInfosToShardInfo, - singleShardInfoToPubsubTopic -} from "@waku/utils"; +import { createDecoder, createLightNode, utf8ToBytes } from "@waku/sdk"; +import { createRoutingInfo, delay } from "@waku/utils"; import { expect } from "chai"; import { @@ -41,8 +31,7 @@ describe("Longevity", function () { }); it("Filter - 2 hours", async function () { - const singleShardInfo = { clusterId: 0, shard: 0 }; - const shardInfo = singleShardInfosToShardInfo([singleShardInfo]); + const networkConfig = { clusterId: 0, numShardsInCluster: 8 }; const testStart = new Date(); @@ -68,29 +57,23 @@ describe("Longevity", function () { { retries: 3 } ); - await nwaku.ensureSubscriptions(shardInfoToPubsubTopics(shardInfo)); + // TODO await nwaku.ensureSubscriptions(shardInfoToPubsubTopics(shardInfo)); - waku = await createLightNode({ networkConfig: shardInfo }); + waku = await createLightNode({ networkConfig }); await waku.start(); await waku.dial(await nwaku.getMultiaddrWithId()); await waku.waitForPeers([Protocols.Filter]); - const decoder = createDecoder(ContentTopic, singleShardInfo); + const routingInfo = createRoutingInfo(networkConfig, { + contentTopic: ContentTopic + }); + const decoder = createDecoder(ContentTopic, routingInfo); const hasSubscribed = await waku.filter.subscribe( [decoder], messageCollector.callback ); if (!hasSubscribed) throw new Error("Failed to subscribe from the start."); - const encoder = createEncoder({ - contentTopic: ContentTopic, - pubsubTopicShardInfo: singleShardInfo - }); - - expect(encoder.pubsubTopic).to.eq( - singleShardInfoToPubsubTopic(singleShardInfo) - ); - let messageId = 0; while (Date.now() < testEnd) { @@ -105,7 +88,8 @@ describe("Longevity", function () { ServiceNode.toMessageRpcQuery({ contentTopic: ContentTopic, payload: utf8ToBytes(message) - }) + }), + routingInfo ); sent = true; @@ -117,7 +101,7 @@ describe("Longevity", function () { messageCollector.verifyReceivedMessage(0, { expectedMessageText: message, expectedContentTopic: ContentTopic, - expectedPubsubTopic: shardInfoToPubsubTopics(shardInfo)[0] + expectedPubsubTopic: routingInfo.pubsubTopic }); } } catch (e: any) { diff --git a/packages/reliability-tests/tests/throughput-sizes.spec.ts b/packages/reliability-tests/tests/throughput-sizes.spec.ts index 911f49bc1a..6d556adbd9 100644 --- a/packages/reliability-tests/tests/throughput-sizes.spec.ts +++ b/packages/reliability-tests/tests/throughput-sizes.spec.ts @@ -1,16 +1,6 @@ import { LightNode, Protocols } from "@waku/interfaces"; -import { - createDecoder, - createEncoder, - createLightNode, - utf8ToBytes -} from "@waku/sdk"; -import { - delay, - shardInfoToPubsubTopics, - singleShardInfosToShardInfo, - singleShardInfoToPubsubTopic -} from "@waku/utils"; +import { createDecoder, createLightNode, utf8ToBytes } from "@waku/sdk"; +import { createRoutingInfo, delay } from "@waku/utils"; import { expect } from "chai"; import { @@ -52,8 +42,7 @@ describe("Throughput Sanity Checks - Different Message Sizes", function () { }); it("Send/Receive messages of varying sizes", async function () { - const singleShardInfo = { clusterId: 0, shard: 0 }; - const shardInfo = singleShardInfosToShardInfo([singleShardInfo]); + const networkConfig = { clusterId: 0, numShardsInCluster: 8 }; const testStart = new Date(); const testEnd = Date.now() + testDurationMs; @@ -74,29 +63,23 @@ describe("Throughput Sanity Checks - Different Message Sizes", function () { await delay(1000); - await nwaku.ensureSubscriptions(shardInfoToPubsubTopics(shardInfo)); + // TODO await nwaku.ensureSubscriptions(shardInfoToPubsubTopics(shardInfo)); - waku = await createLightNode({ networkConfig: shardInfo }); + waku = await createLightNode({ networkConfig }); await waku.start(); await waku.dial(await nwaku.getMultiaddrWithId()); await waku.waitForPeers([Protocols.Filter]); - const decoder = createDecoder(ContentTopic, singleShardInfo); + const routingInfo = createRoutingInfo(networkConfig, { + contentTopic: ContentTopic + }); + const decoder = createDecoder(ContentTopic, routingInfo); const hasSubscribed = await waku.filter.subscribe( [decoder], messageCollector.callback ); if (!hasSubscribed) throw new Error("Failed to subscribe from the start."); - const encoder = createEncoder({ - contentTopic: ContentTopic, - pubsubTopicShardInfo: singleShardInfo - }); - - expect(encoder.pubsubTopic).to.eq( - singleShardInfoToPubsubTopic(singleShardInfo) - ); - let messageId = 0; const report: { messageId: number; @@ -121,7 +104,8 @@ describe("Throughput Sanity Checks - Different Message Sizes", function () { ServiceNode.toMessageRpcQuery({ contentTopic: ContentTopic, payload: utf8ToBytes(message) - }) + }), + routingInfo ); sent = true; @@ -133,7 +117,7 @@ describe("Throughput Sanity Checks - Different Message Sizes", function () { messageCollector.verifyReceivedMessage(0, { expectedMessageText: message, expectedContentTopic: ContentTopic, - expectedPubsubTopic: shardInfoToPubsubTopics(shardInfo)[0] + expectedPubsubTopic: routingInfo.pubsubTopic }); } } catch (e: any) { diff --git a/packages/rln/src/codec.spec.ts b/packages/rln/src/codec.spec.ts index 5182cd1e86..084ac1becf 100644 --- a/packages/rln/src/codec.spec.ts +++ b/packages/rln/src/codec.spec.ts @@ -24,8 +24,8 @@ import { import { createTestMetaSetter, createTestRLNCodecSetup, - EMPTY_PROTO_MESSAGE, - TEST_CONSTANTS, + EmptyProtoMessage, + TestConstants, verifyRLNMessage } from "./codec.test-utils.js"; import { RlnMessage } from "./message.js"; @@ -37,14 +37,20 @@ describe("RLN codec with version 0", () => { await createTestRLNCodecSetup(); const rlnEncoder = createRLNEncoder({ - encoder: createEncoder({ contentTopic: TEST_CONSTANTS.contentTopic }), + encoder: createEncoder({ + contentTopic: TestConstants.contentTopic, + routingInfo: TestConstants.routingInfo + }), rlnInstance, index, credential }); const rlnDecoder = createRLNDecoder({ rlnInstance, - decoder: createDecoder(TEST_CONSTANTS.contentTopic) + decoder: createDecoder( + TestConstants.contentTopic, + TestConstants.routingInfo + ) }); const bytes = await rlnEncoder.toWire({ payload }); @@ -53,11 +59,11 @@ describe("RLN codec with version 0", () => { const protoResult = await rlnDecoder.fromWireToProtoObj(bytes!); expect(protoResult).to.not.be.undefined; const msg = (await rlnDecoder.fromProtoObj( - TEST_CONSTANTS.emptyPubsubTopic, + TestConstants.emptyPubsubTopic, protoResult! ))!; - verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 0, rlnInstance); + verifyRLNMessage(msg, payload, TestConstants.contentTopic, 0, rlnInstance); }); it("toProtoObj", async function () { @@ -65,25 +71,28 @@ describe("RLN codec with version 0", () => { await createTestRLNCodecSetup(); const rlnEncoder = new RLNEncoder( - createEncoder({ contentTopic: TEST_CONSTANTS.contentTopic }), + createEncoder({ + contentTopic: TestConstants.contentTopic, + routingInfo: TestConstants.routingInfo + }), rlnInstance, index, credential ); const rlnDecoder = new RLNDecoder( rlnInstance, - createDecoder(TEST_CONSTANTS.contentTopic) + createDecoder(TestConstants.contentTopic, TestConstants.routingInfo) ); const proto = await rlnEncoder.toProtoObj({ payload }); expect(proto).to.not.be.undefined; const msg = (await rlnDecoder.fromProtoObj( - TEST_CONSTANTS.emptyPubsubTopic, + TestConstants.emptyPubsubTopic, proto! )) as RlnMessage; - verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 0, rlnInstance); + verifyRLNMessage(msg, payload, TestConstants.contentTopic, 0, rlnInstance); }); }); @@ -95,7 +104,8 @@ describe("RLN codec with version 1", () => { const rlnEncoder = new RLNEncoder( createSymEncoder({ - contentTopic: TEST_CONSTANTS.contentTopic, + contentTopic: TestConstants.contentTopic, + routingInfo: TestConstants.routingInfo, symKey }), rlnInstance, @@ -104,7 +114,11 @@ describe("RLN codec with version 1", () => { ); const rlnDecoder = new RLNDecoder( rlnInstance, - createSymDecoder(TEST_CONSTANTS.contentTopic, symKey) + createSymDecoder( + TestConstants.contentTopic, + TestConstants.routingInfo, + symKey + ) ); const bytes = await rlnEncoder.toWire({ payload }); @@ -113,11 +127,11 @@ describe("RLN codec with version 1", () => { const protoResult = await rlnDecoder.fromWireToProtoObj(bytes!); expect(protoResult).to.not.be.undefined; const msg = (await rlnDecoder.fromProtoObj( - TEST_CONSTANTS.emptyPubsubTopic, + TestConstants.emptyPubsubTopic, protoResult! ))!; - verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 1, rlnInstance); + verifyRLNMessage(msg, payload, TestConstants.contentTopic, 1, rlnInstance); }); it("Symmetric, toProtoObj", async function () { @@ -127,7 +141,8 @@ describe("RLN codec with version 1", () => { const rlnEncoder = new RLNEncoder( createSymEncoder({ - contentTopic: TEST_CONSTANTS.contentTopic, + contentTopic: TestConstants.contentTopic, + routingInfo: TestConstants.routingInfo, symKey }), rlnInstance, @@ -136,18 +151,22 @@ describe("RLN codec with version 1", () => { ); const rlnDecoder = new RLNDecoder( rlnInstance, - createSymDecoder(TEST_CONSTANTS.contentTopic, symKey) + createSymDecoder( + TestConstants.contentTopic, + TestConstants.routingInfo, + symKey + ) ); const proto = await rlnEncoder.toProtoObj({ payload }); expect(proto).to.not.be.undefined; const msg = await rlnDecoder.fromProtoObj( - TEST_CONSTANTS.emptyPubsubTopic, + TestConstants.emptyPubsubTopic, proto! ); - verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 1, rlnInstance); + verifyRLNMessage(msg, payload, TestConstants.contentTopic, 1, rlnInstance); }); it("Asymmetric, toWire", async function () { @@ -158,7 +177,8 @@ describe("RLN codec with version 1", () => { const rlnEncoder = new RLNEncoder( createAsymEncoder({ - contentTopic: TEST_CONSTANTS.contentTopic, + contentTopic: TestConstants.contentTopic, + routingInfo: TestConstants.routingInfo, publicKey }), rlnInstance, @@ -167,7 +187,11 @@ describe("RLN codec with version 1", () => { ); const rlnDecoder = new RLNDecoder( rlnInstance, - createAsymDecoder(TEST_CONSTANTS.contentTopic, privateKey) + createAsymDecoder( + TestConstants.contentTopic, + TestConstants.routingInfo, + privateKey + ) ); const bytes = await rlnEncoder.toWire({ payload }); @@ -176,11 +200,11 @@ describe("RLN codec with version 1", () => { const protoResult = await rlnDecoder.fromWireToProtoObj(bytes!); expect(protoResult).to.not.be.undefined; const msg = (await rlnDecoder.fromProtoObj( - TEST_CONSTANTS.emptyPubsubTopic, + TestConstants.emptyPubsubTopic, protoResult! ))!; - verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 1, rlnInstance); + verifyRLNMessage(msg, payload, TestConstants.contentTopic, 1, rlnInstance); }); it("Asymmetric, toProtoObj", async function () { @@ -191,7 +215,8 @@ describe("RLN codec with version 1", () => { const rlnEncoder = new RLNEncoder( createAsymEncoder({ - contentTopic: TEST_CONSTANTS.contentTopic, + contentTopic: TestConstants.contentTopic, + routingInfo: TestConstants.routingInfo, publicKey }), rlnInstance, @@ -200,18 +225,22 @@ describe("RLN codec with version 1", () => { ); const rlnDecoder = new RLNDecoder( rlnInstance, - createAsymDecoder(TEST_CONSTANTS.contentTopic, privateKey) + createAsymDecoder( + TestConstants.contentTopic, + TestConstants.routingInfo, + privateKey + ) ); const proto = await rlnEncoder.toProtoObj({ payload }); expect(proto).to.not.be.undefined; const msg = await rlnDecoder.fromProtoObj( - TEST_CONSTANTS.emptyPubsubTopic, + TestConstants.emptyPubsubTopic, proto! ); - verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 1, rlnInstance); + verifyRLNMessage(msg, payload, TestConstants.contentTopic, 1, rlnInstance); }); }); @@ -221,21 +250,24 @@ describe("RLN Codec - epoch", () => { await createTestRLNCodecSetup(); const rlnEncoder = new RLNEncoder( - createEncoder({ contentTopic: TEST_CONSTANTS.contentTopic }), + createEncoder({ + contentTopic: TestConstants.contentTopic, + routingInfo: TestConstants.routingInfo + }), rlnInstance, index, credential ); const rlnDecoder = new RLNDecoder( rlnInstance, - createDecoder(TEST_CONSTANTS.contentTopic) + createDecoder(TestConstants.contentTopic, TestConstants.routingInfo) ); const proto = await rlnEncoder.toProtoObj({ payload }); expect(proto).to.not.be.undefined; const msg = (await rlnDecoder.fromProtoObj( - TEST_CONSTANTS.emptyPubsubTopic, + TestConstants.emptyPubsubTopic, proto! )) as RlnMessage; @@ -245,7 +277,7 @@ describe("RLN Codec - epoch", () => { expect(msg.epoch!.toString(10).length).to.eq(9); expect(msg.epoch).to.eq(epoch); - verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 0, rlnInstance); + verifyRLNMessage(msg, payload, TestConstants.contentTopic, 0, rlnInstance); }); }); @@ -257,7 +289,8 @@ describe("RLN codec with version 0 and meta setter", () => { const rlnEncoder = createRLNEncoder({ encoder: createEncoder({ - contentTopic: TEST_CONSTANTS.contentTopic, + contentTopic: TestConstants.contentTopic, + routingInfo: TestConstants.routingInfo, metaSetter }), rlnInstance, @@ -266,7 +299,10 @@ describe("RLN codec with version 0 and meta setter", () => { }); const rlnDecoder = createRLNDecoder({ rlnInstance, - decoder: createDecoder(TEST_CONSTANTS.contentTopic) + decoder: createDecoder( + TestConstants.contentTopic, + TestConstants.routingInfo + ) }); const bytes = await rlnEncoder.toWire({ payload }); @@ -275,17 +311,17 @@ describe("RLN codec with version 0 and meta setter", () => { const protoResult = await rlnDecoder.fromWireToProtoObj(bytes!); expect(protoResult).to.not.be.undefined; const msg = (await rlnDecoder.fromProtoObj( - TEST_CONSTANTS.emptyPubsubTopic, + TestConstants.emptyPubsubTopic, protoResult! ))!; const expectedMeta = metaSetter({ - ...EMPTY_PROTO_MESSAGE, + ...EmptyProtoMessage, payload: protoResult!.payload }); expect(msg!.meta).to.deep.eq(expectedMeta); - verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 0, rlnInstance); + verifyRLNMessage(msg, payload, TestConstants.contentTopic, 0, rlnInstance); }); it("toProtoObj", async function () { @@ -294,30 +330,34 @@ describe("RLN codec with version 0 and meta setter", () => { const metaSetter = createTestMetaSetter(); const rlnEncoder = new RLNEncoder( - createEncoder({ contentTopic: TEST_CONSTANTS.contentTopic, metaSetter }), + createEncoder({ + contentTopic: TestConstants.contentTopic, + routingInfo: TestConstants.routingInfo, + metaSetter + }), rlnInstance, index, credential ); const rlnDecoder = new RLNDecoder( rlnInstance, - createDecoder(TEST_CONSTANTS.contentTopic) + createDecoder(TestConstants.contentTopic, TestConstants.routingInfo) ); const proto = await rlnEncoder.toProtoObj({ payload }); expect(proto).to.not.be.undefined; const msg = (await rlnDecoder.fromProtoObj( - TEST_CONSTANTS.emptyPubsubTopic, + TestConstants.emptyPubsubTopic, proto! )) as RlnMessage; const expectedMeta = metaSetter({ - ...EMPTY_PROTO_MESSAGE, + ...EmptyProtoMessage, payload: msg!.payload }); expect(msg!.meta).to.deep.eq(expectedMeta); - verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 0, rlnInstance); + verifyRLNMessage(msg, payload, TestConstants.contentTopic, 0, rlnInstance); }); }); diff --git a/packages/rln/src/codec.test-utils.ts b/packages/rln/src/codec.test-utils.ts index b3bfc29612..140a726007 100644 --- a/packages/rln/src/codec.test-utils.ts +++ b/packages/rln/src/codec.test-utils.ts @@ -1,4 +1,5 @@ import type { IProtoMessage } from "@waku/interfaces"; +import { createRoutingInfo } from "@waku/utils"; import { expect } from "chai"; import { createRLN } from "./create.js"; @@ -11,14 +12,21 @@ export interface TestRLNCodecSetup { payload: Uint8Array; } -export const TEST_CONSTANTS = { +export const TestConstants = { contentTopic: "/test/1/waku-message/utf8", emptyPubsubTopic: "", defaultIndex: 0, - defaultPayload: new Uint8Array([1, 2, 3, 4, 5]) + defaultPayload: new Uint8Array([1, 2, 3, 4, 5]), + routingInfo: createRoutingInfo( + { + clusterId: 0, + numShardsInCluster: 2 + }, + { contentTopic: "/test/1/waku-message/utf8" } + ) } as const; -export const EMPTY_PROTO_MESSAGE = { +export const EmptyProtoMessage = { timestamp: undefined, contentTopic: "", ephemeral: undefined, @@ -38,8 +46,8 @@ export async function createTestRLNCodecSetup(): Promise { return { rlnInstance, credential, - index: TEST_CONSTANTS.defaultIndex, - payload: TEST_CONSTANTS.defaultPayload + index: TestConstants.defaultIndex, + payload: TestConstants.defaultPayload }; } diff --git a/packages/rln/src/codec.ts b/packages/rln/src/codec.ts index 3a9036d4b1..21be117c1e 100644 --- a/packages/rln/src/codec.ts +++ b/packages/rln/src/codec.ts @@ -4,7 +4,8 @@ import type { IEncoder, IMessage, IProtoMessage, - IRateLimitProof + IRateLimitProof, + IRoutingInfo } from "@waku/interfaces"; import { Logger } from "@waku/utils"; @@ -47,17 +48,16 @@ export class RLNEncoder implements IEncoder { private async generateProof(message: IMessage): Promise { const signal = toRLNSignal(this.contentTopic, message); - const proof = await this.rlnInstance.zerokit.generateRLNProof( + return this.rlnInstance.zerokit.generateRLNProof( signal, this.index, message.timestamp, this.idSecretHash ); - return proof; } - public get pubsubTopic(): string { - return this.encoder.pubsubTopic; + public get routingInfo(): IRoutingInfo { + return this.encoder.routingInfo; } public get contentTopic(): string { @@ -93,8 +93,8 @@ export class RLNDecoder private readonly decoder: IDecoder ) {} - public get pubsubTopic(): string { - return this.decoder.pubsubTopic; + public get routingInfo(): IRoutingInfo { + return this.decoder.routingInfo; } public get contentTopic(): string { diff --git a/packages/rln/src/rln.ts b/packages/rln/src/rln.ts index ba709ed57c..8dbfc69fc0 100644 --- a/packages/rln/src/rln.ts +++ b/packages/rln/src/rln.ts @@ -2,9 +2,9 @@ import { createDecoder, createEncoder } from "@waku/core"; import type { ContentTopic, IDecodedMessage, - EncoderOptions as WakuEncoderOptions + IMetaSetter } from "@waku/interfaces"; -import { Logger } from "@waku/utils"; +import { Logger, RoutingInfo } from "@waku/utils"; import init from "@waku/zerokit-rln-wasm"; import * as zerokitRLN from "@waku/zerokit-rln-wasm"; @@ -27,7 +27,27 @@ import { Zerokit } from "./zerokit.js"; const log = new Logger("waku:rln"); -type WakuRLNEncoderOptions = WakuEncoderOptions & { +type WakuRLNEncoderOptions = { + /** + * The routing information for messages to encode. + */ + routingInfo: RoutingInfo; + /** The content topic to set on outgoing messages. */ + contentTopic: string; + /** + * An optional flag to mark message as ephemeral, i.e., not to be stored by Waku Store nodes. + * @defaultValue `false` + */ + ephemeral?: boolean; + /** + * A function called when encoding messages to set the meta field. + * @param IProtoMessage The message encoded for wire, without the meta field. + * If encryption is used, `metaSetter` only accesses _encrypted_ payload. + */ + metaSetter?: IMetaSetter; + /** + * RLN Credentials + */ credentials: EncryptedCredentials | DecryptedCredentials; }; @@ -87,11 +107,12 @@ export class RLNInstance extends RLNCredentialsManager { } public createDecoder( - contentTopic: ContentTopic + contentTopic: ContentTopic, + routingInfo: RoutingInfo ): RLNDecoder { return createRLNDecoder({ rlnInstance: this, - decoder: createDecoder(contentTopic) + decoder: createDecoder(contentTopic, routingInfo) }); } diff --git a/packages/sdk/src/filter/filter.spec.ts b/packages/sdk/src/filter/filter.spec.ts index 2010ba576c..a0819b36a5 100644 --- a/packages/sdk/src/filter/filter.spec.ts +++ b/packages/sdk/src/filter/filter.spec.ts @@ -5,6 +5,7 @@ import type { IProtoMessage, Libp2p } from "@waku/interfaces"; +import { createRoutingInfo } from "@waku/utils"; import { expect } from "chai"; import sinon from "sinon"; @@ -13,8 +14,15 @@ import { PeerManager } from "../peer_manager/index.js"; import { Filter } from "./filter.js"; import { Subscription } from "./subscription.js"; -const PUBSUB_TOPIC = "/waku/2/rs/1/4"; -const CONTENT_TOPIC = "/test/1/waku-filter/utf8"; +const testContentTopic = "/test/1/waku-filter/utf8"; +const testNetworkconfig = { + clusterId: 0, + numShardsInCluster: 9 +}; +const testRoutingInfo = createRoutingInfo(testNetworkconfig, { + contentTopic: testContentTopic +}); +const testPubsubTopic = testRoutingInfo.pubsubTopic; describe("Filter SDK", () => { let libp2p: Libp2p; @@ -29,7 +37,7 @@ describe("Filter SDK", () => { connectionManager = mockConnectionManager(); peerManager = mockPeerManager(); filter = mockFilter({ libp2p, connectionManager, peerManager }); - decoder = createDecoder(CONTENT_TOPIC, PUBSUB_TOPIC); + decoder = createDecoder(testContentTopic, testRoutingInfo); callback = sinon.spy(); }); @@ -80,10 +88,10 @@ describe("Filter SDK", () => { await filter.subscribe(decoder, callback); - const message = createMockMessage(CONTENT_TOPIC); + const message = createMockMessage(testContentTopic); const peerId = "peer1"; - await (filter as any).onIncomingMessage(PUBSUB_TOPIC, message, peerId); + await (filter as any).onIncomingMessage(testPubsubTopic, message, peerId); expect(subscriptionInvokeStub.calledOnce).to.be.true; expect(subscriptionInvokeStub.firstCall.args[0]).to.equal(message); @@ -91,7 +99,11 @@ describe("Filter SDK", () => { }); it("should successfully stop", async () => { - const decoder2 = createDecoder("/another-content-topic", PUBSUB_TOPIC); + const contentTopic2 = "/test/1/waku-filter-2/utf8"; + const decoder2 = createDecoder( + contentTopic2, + createRoutingInfo(testNetworkconfig, { contentTopic: contentTopic2 }) + ); const stopStub = sinon.stub(Subscription.prototype, "stop"); sinon.stub(Subscription.prototype, "add").resolves(true); @@ -129,7 +141,7 @@ function mockLibp2p(): Libp2p { function mockConnectionManager(): ConnectionManager { return { isTopicConfigured: sinon.stub().callsFake((topic: string) => { - return topic === PUBSUB_TOPIC; + return topic === testPubsubTopic; }) } as unknown as ConnectionManager; } diff --git a/packages/sdk/src/filter/filter.ts b/packages/sdk/src/filter/filter.ts index 43895fab7c..b686822f30 100644 --- a/packages/sdk/src/filter/filter.ts +++ b/packages/sdk/src/filter/filter.ts @@ -63,21 +63,21 @@ export class Filter implements IFilter { throw Error("Cannot subscribe with 0 decoders."); } - const pubsubTopics = decoders.map((v) => v.pubsubTopic); - const singlePubsubTopic = pubsubTopics[0]; + const routingInfos = decoders.map((v) => v.routingInfo); + const routingInfo = routingInfos[0]; const contentTopics = decoders.map((v) => v.contentTopic); log.info( - `Subscribing to contentTopics: ${contentTopics}, pubsubTopic: ${singlePubsubTopic}` + `Subscribing to contentTopics: ${contentTopics}, pubsubTopic: ${routingInfo.pubsubTopic}` ); - this.throwIfTopicNotSame(pubsubTopics); + this.throwIfTopicNotSame(routingInfos.map((r) => r.pubsubTopic)); - let subscription = this.subscriptions.get(singlePubsubTopic); + let subscription = this.subscriptions.get(routingInfo.pubsubTopic); if (!subscription) { subscription = new Subscription({ - pubsubTopic: singlePubsubTopic, + routingInfo: routingInfo, protocol: this.protocol, config: this.config, peerManager: this.peerManager @@ -86,7 +86,7 @@ export class Filter implements IFilter { } const result = await subscription.add(decoders, callback); - this.subscriptions.set(singlePubsubTopic, subscription); + this.subscriptions.set(routingInfo.pubsubTopic, subscription); log.info( `Subscription ${result ? "successful" : "failed"} for content topic: ${contentTopics}` @@ -104,7 +104,7 @@ export class Filter implements IFilter { throw Error("Cannot unsubscribe with 0 decoders."); } - const pubsubTopics = decoders.map((v) => v.pubsubTopic); + const pubsubTopics = decoders.map((v) => v.routingInfo.pubsubTopic); const singlePubsubTopic = pubsubTopics[0]; const contentTopics = decoders.map((v) => v.contentTopic); diff --git a/packages/sdk/src/filter/subscription.spec.ts b/packages/sdk/src/filter/subscription.spec.ts index 37f3d48ed3..e65128092f 100644 --- a/packages/sdk/src/filter/subscription.spec.ts +++ b/packages/sdk/src/filter/subscription.spec.ts @@ -1,10 +1,12 @@ import { FilterCore } from "@waku/core"; import type { + AutoSharding, FilterProtocolOptions, IDecodedMessage, IDecoder } from "@waku/interfaces"; import { WakuMessage } from "@waku/proto"; +import { createRoutingInfo } from "@waku/utils"; import { expect } from "chai"; import sinon from "sinon"; @@ -14,7 +16,13 @@ import { Subscription } from "./subscription.js"; const PUBSUB_TOPIC = "/waku/2/rs/1/4"; const CONTENT_TOPIC = "/test/1/waku-filter/utf8"; - +const NETWORK_CONFIG: AutoSharding = { + clusterId: 2, + numShardsInCluster: 3 +}; +const ROUTING_INFO = createRoutingInfo(NETWORK_CONFIG, { + contentTopic: CONTENT_TOPIC +}); describe("Filter Subscription", () => { let filterCore: FilterCore; let peerManager: PeerManager; @@ -32,7 +40,7 @@ describe("Filter Subscription", () => { }; subscription = new Subscription({ - pubsubTopic: PUBSUB_TOPIC, + routingInfo: ROUTING_INFO, protocol: filterCore, config, peerManager @@ -79,9 +87,11 @@ describe("Filter Subscription", () => { }); it("should invoke callbacks when receiving a message", async () => { - const testContentTopic = "/custom/content/topic"; + const testContentTopic = "/custom/0/content/proto"; const testDecoder = { - pubsubTopic: PUBSUB_TOPIC, + routingInfo: createRoutingInfo(NETWORK_CONFIG, { + contentTopic: testContentTopic + }), contentTopic: testContentTopic, fromProtoObj: sinon.stub().callsFake(() => { return Promise.resolve({ payload: new Uint8Array([1, 2, 3]) }); @@ -106,9 +116,11 @@ describe("Filter Subscription", () => { }); it("should invoke callbacks only when newly receiving message is given", async () => { - const testContentTopic = "/custom/content/topic"; + const testContentTopic = "/custom/0/content/topic"; const testDecoder = { - pubsubTopic: PUBSUB_TOPIC, + routingInfo: createRoutingInfo(NETWORK_CONFIG, { + contentTopic: testContentTopic + }), contentTopic: testContentTopic, fromProtoObj: sinon.stub().callsFake(() => { return Promise.resolve({ payload: new Uint8Array([1, 2, 3]) }); diff --git a/packages/sdk/src/filter/subscription.ts b/packages/sdk/src/filter/subscription.ts index 00804f5601..67b3f6aa33 100644 --- a/packages/sdk/src/filter/subscription.ts +++ b/packages/sdk/src/filter/subscription.ts @@ -10,11 +10,12 @@ import type { IDecodedMessage, IDecoder, IProtoMessage, - PeerIdStr + PeerIdStr, + PubsubTopic } from "@waku/interfaces"; import { Protocols } from "@waku/interfaces"; import { WakuMessage } from "@waku/proto"; -import { Logger } from "@waku/utils"; +import { Logger, RoutingInfo } from "@waku/utils"; import { PeerManager, PeerManagerEventNames } from "../peer_manager/index.js"; @@ -35,7 +36,8 @@ type AttemptUnsubscribeParams = { type Libp2pEventHandler = (e: CustomEvent) => void; export class Subscription { - private readonly pubsubTopic: string; + private readonly routingInfo: RoutingInfo; + private readonly pubsubTopic: PubsubTopic; private readonly protocol: FilterCore; private readonly peerManager: PeerManager; @@ -73,7 +75,8 @@ export class Subscription { public constructor(params: SubscriptionParams) { this.config = params.config; - this.pubsubTopic = params.pubsubTopic; + this.routingInfo = params.routingInfo; + this.pubsubTopic = params.routingInfo.pubsubTopic; this.protocol = params.protocol; this.peerManager = params.peerManager; @@ -193,7 +196,7 @@ export class Subscription { if (this.callbacks.has(decoder)) { log.warn( - `Replacing callback associated associated with decoder with pubsubTopic:${decoder.pubsubTopic} and contentTopic:${decoder.contentTopic}` + `Replacing callback associated associated with decoder with pubsubTopic:${decoder.routingInfo.pubsubTopic} and contentTopic:${decoder.contentTopic}` ); const callback = this.callbacks.get(decoder); @@ -205,7 +208,7 @@ export class Subscription { void (async (): Promise => { try { const message = await decoder.fromProtoObj( - decoder.pubsubTopic, + decoder.routingInfo.pubsubTopic, event.detail as IProtoMessage ); void callback(message!); @@ -230,7 +233,7 @@ export class Subscription { if (!callback) { log.warn( - `No callback associated with decoder with pubsubTopic:${decoder.pubsubTopic} and contentTopic:${decoder.contentTopic}` + `No callback associated with decoder with pubsubTopic:${decoder.routingInfo.pubsubTopic} and contentTopic:${decoder.contentTopic}` ); } @@ -413,11 +416,13 @@ export class Subscription { const usablePeer = await this.peerManager.isPeerOnPubsub( event.detail, - this.pubsubTopic + this.routingInfo.pubsubTopic ); if (!usablePeer) { - log.info(`Peer ${id} doesn't support pubsubTopic:${this.pubsubTopic}`); + log.info( + `Peer ${id} doesn't support pubsubTopic:${this.routingInfo.pubsubTopic}` + ); return; } @@ -483,7 +488,7 @@ export class Subscription { const prevPeers = new Set(this.peers.keys()); const peersToAdd = await this.peerManager.getPeers({ protocol: Protocols.Filter, - pubsubTopic: this.pubsubTopic + routingInfo: this.routingInfo }); for (const peer of peersToAdd) { diff --git a/packages/sdk/src/filter/types.ts b/packages/sdk/src/filter/types.ts index 44326728d1..f010f45440 100644 --- a/packages/sdk/src/filter/types.ts +++ b/packages/sdk/src/filter/types.ts @@ -1,5 +1,9 @@ import type { FilterCore } from "@waku/core"; -import type { FilterProtocolOptions, Libp2p } from "@waku/interfaces"; +import type { + FilterProtocolOptions, + IRoutingInfo, + Libp2p +} from "@waku/interfaces"; import type { WakuMessage } from "@waku/proto"; import type { PeerManager } from "../peer_manager/index.js"; @@ -15,7 +19,7 @@ export type SubscriptionEvents = { }; export type SubscriptionParams = { - pubsubTopic: string; + routingInfo: IRoutingInfo; protocol: FilterCore; config: FilterProtocolOptions; peerManager: PeerManager; diff --git a/packages/sdk/src/light_push/light_push.spec.ts b/packages/sdk/src/light_push/light_push.spec.ts index 114f0e413c..faabc16459 100644 --- a/packages/sdk/src/light_push/light_push.spec.ts +++ b/packages/sdk/src/light_push/light_push.spec.ts @@ -1,6 +1,7 @@ import { Peer, PeerId } from "@libp2p/interface"; import { createEncoder, Encoder, LightPushCodec } from "@waku/core"; import { Libp2p, ProtocolError } from "@waku/interfaces"; +import { createRoutingInfo } from "@waku/utils"; import { utf8ToBytes } from "@waku/utils/bytes"; import { expect } from "chai"; import sinon, { SinonSpy } from "sinon"; @@ -9,7 +10,14 @@ import { PeerManager } from "../peer_manager/index.js"; import { LightPush } from "./light_push.js"; -const CONTENT_TOPIC = "/test/1/waku-light-push/utf8"; +const testContentTopic = "/test/1/waku-light-push/utf8"; +const testRoutingInfo = createRoutingInfo( + { + clusterId: 0, + numShardsInCluster: 7 + }, + { contentTopic: testContentTopic } +); describe("LightPush SDK", () => { let libp2p: Libp2p; @@ -18,7 +26,10 @@ describe("LightPush SDK", () => { beforeEach(() => { libp2p = mockLibp2p(); - encoder = createEncoder({ contentTopic: CONTENT_TOPIC }); + encoder = createEncoder({ + contentTopic: testContentTopic, + routingInfo: testRoutingInfo + }); lightPush = mockLightPush({ libp2p }); }); diff --git a/packages/sdk/src/light_push/light_push.ts b/packages/sdk/src/light_push/light_push.ts index 5789f351bd..13dc92089e 100644 --- a/packages/sdk/src/light_push/light_push.ts +++ b/packages/sdk/src/light_push/light_push.ts @@ -77,13 +77,13 @@ export class LightPush implements ILightPush { ...options }; - const { pubsubTopic } = encoder; + const { pubsubTopic } = encoder.routingInfo; log.info("send: attempting to send a message to pubsubTopic:", pubsubTopic); const peerIds = await this.peerManager.getPeers({ protocol: Protocols.LightPush, - pubsubTopic: encoder.pubsubTopic + routingInfo: encoder.routingInfo }); const coreResults: CoreProtocolResult[] = @@ -124,7 +124,7 @@ export class LightPush implements ILightPush { this.retryManager.push( sendCallback.bind(this), options.maxAttempts || DEFAULT_MAX_ATTEMPTS, - encoder.pubsubTopic + encoder.routingInfo ); } diff --git a/packages/sdk/src/light_push/retry_manager.spec.ts b/packages/sdk/src/light_push/retry_manager.spec.ts index d5f415503a..425bb5837c 100644 --- a/packages/sdk/src/light_push/retry_manager.spec.ts +++ b/packages/sdk/src/light_push/retry_manager.spec.ts @@ -4,6 +4,7 @@ import { ProtocolError, Protocols } from "@waku/interfaces"; +import { createRoutingInfo } from "@waku/utils"; import { expect } from "chai"; import sinon from "sinon"; @@ -11,6 +12,11 @@ import { PeerManager } from "../peer_manager/index.js"; import { RetryManager, ScheduledTask } from "./retry_manager.js"; +const TestRoutingInfo = createRoutingInfo( + { clusterId: 0 }, + { pubsubTopic: "/waku/2/rs/0/0" } +); + describe("RetryManager", () => { let retryManager: RetryManager; let peerManager: PeerManager; @@ -59,7 +65,7 @@ describe("RetryManager", () => { }) ); - retryManager.push(successCallback, 3, "test-topic"); + retryManager.push(successCallback, 3, TestRoutingInfo); retryManager.start(); await clock.tickAsync(200); @@ -74,7 +80,7 @@ describe("RetryManager", () => { (peerManager as any).getPeers = () => []; const callback = sinon.spy(); - retryManager.push(callback, 2, "test-topic"); + retryManager.push(callback, 2, TestRoutingInfo); retryManager.start(); const queue = (retryManager as any)["queue"] as ScheduledTask[]; @@ -92,7 +98,7 @@ describe("RetryManager", () => { (peerManager as any).getPeers = () => []; const callback = sinon.spy(); - retryManager.push(callback, 1, "test-topic"); + retryManager.push(callback, 1, TestRoutingInfo); retryManager.start(); const queue = (retryManager as any)["queue"] as ScheduledTask[]; expect(queue.length).to.equal(1); @@ -117,7 +123,7 @@ describe("RetryManager", () => { const task = { callback: failingCallback, maxAttempts: 2, - pubsubTopic: "test-topic" + routingInfo: TestRoutingInfo }; await (retryManager as any)["taskExecutor"](task); @@ -136,14 +142,14 @@ describe("RetryManager", () => { await (retryManager as any)["taskExecutor"]({ callback: errorCallback, maxAttempts: 1, - pubsubTopic: "test-topic" + routingInfo: TestRoutingInfo }); expect((peerManager.renewPeer as sinon.SinonSpy).calledOnce).to.be.true; expect( (peerManager.renewPeer as sinon.SinonSpy).calledWith(mockPeerId, { protocol: Protocols.LightPush, - pubsubTopic: "test-topic" + routingInfo: TestRoutingInfo }) ).to.be.true; }); @@ -157,7 +163,7 @@ describe("RetryManager", () => { const task = { callback: slowCallback, maxAttempts: 1, - pubsubTopic: "test-topic" + routingInfo: TestRoutingInfo }; const executionPromise = (retryManager as any)["taskExecutor"](task); @@ -175,7 +181,7 @@ describe("RetryManager", () => { const task = { callback: failingCallback, maxAttempts: 0, - pubsubTopic: "test-topic" + routingInfo: TestRoutingInfo }; await (retryManager as any)["taskExecutor"](task); @@ -190,7 +196,7 @@ describe("RetryManager", () => { if (called === 1) retryManager.stop(); return Promise.resolve({ success: mockPeerId, failure: null }); }); - retryManager.push(successCallback, 2, "test-topic"); + retryManager.push(successCallback, 2, TestRoutingInfo); retryManager.start(); await clock.tickAsync(500); expect(called).to.equal(1); @@ -206,7 +212,7 @@ describe("RetryManager", () => { failure: { error: ProtocolError.GENERIC_FAIL } }); }); - retryManager.push(failCallback, 2, "test-topic"); + retryManager.push(failCallback, 2, TestRoutingInfo); retryManager.start(); await clock.tickAsync(1000); retryManager.stop(); diff --git a/packages/sdk/src/light_push/retry_manager.ts b/packages/sdk/src/light_push/retry_manager.ts index 5e42dfc1ae..9fe63fc92e 100644 --- a/packages/sdk/src/light_push/retry_manager.ts +++ b/packages/sdk/src/light_push/retry_manager.ts @@ -1,6 +1,6 @@ import type { PeerId } from "@libp2p/interface"; import { type CoreProtocolResult, Protocols } from "@waku/interfaces"; -import { Logger } from "@waku/utils"; +import { Logger, RoutingInfo } from "@waku/utils"; import type { PeerManager } from "../peer_manager/index.js"; @@ -15,7 +15,7 @@ type AttemptCallback = (peerId: PeerId) => Promise; export type ScheduledTask = { maxAttempts: number; - pubsubTopic: string; + routingInfo: RoutingInfo; callback: AttemptCallback; }; @@ -54,12 +54,12 @@ export class RetryManager { public push( callback: AttemptCallback, maxAttempts: number, - pubsubTopic: string + routingInfo: RoutingInfo ): void { this.queue.push({ maxAttempts, callback, - pubsubTopic + routingInfo }); } @@ -96,7 +96,7 @@ export class RetryManager { const peerId = ( await this.peerManager.getPeers({ protocol: Protocols.LightPush, - pubsubTopic: task.pubsubTopic + routingInfo: task.routingInfo }) )[0]; @@ -142,7 +142,7 @@ export class RetryManager { if (shouldPeerBeChanged(error.message)) { await this.peerManager.renewPeer(peerId, { protocol: Protocols.LightPush, - pubsubTopic: task.pubsubTopic + routingInfo: task.routingInfo }); } diff --git a/packages/sdk/src/peer_manager/peer_manager.spec.ts b/packages/sdk/src/peer_manager/peer_manager.spec.ts index 81a5ec58d1..cac779ca0e 100644 --- a/packages/sdk/src/peer_manager/peer_manager.spec.ts +++ b/packages/sdk/src/peer_manager/peer_manager.spec.ts @@ -5,6 +5,7 @@ import { Libp2p, Protocols } from "@waku/interfaces"; +import { createRoutingInfo } from "@waku/utils"; import { expect } from "chai"; import sinon from "sinon"; @@ -17,8 +18,12 @@ describe("PeerManager", () => { let peers: any[]; let mockConnections: any[]; - const TEST_PUBSUB_TOPIC = "/test/1/waku-light-push/utf8"; + const TEST_PUBSUB_TOPIC = "/waku/2/rs/0/0"; const TEST_PROTOCOL = Protocols.LightPush; + const TEST_ROUTING_INFO = createRoutingInfo( + { clusterId: 0 }, + { pubsubTopic: TEST_PUBSUB_TOPIC } + ); const clearPeerState = (): void => { (peerManager as any).lockedPeers.clear(); @@ -36,7 +41,7 @@ describe("PeerManager", () => { const getPeersForTest = async (): Promise => { return await peerManager.getPeers({ protocol: TEST_PROTOCOL, - pubsubTopic: TEST_PUBSUB_TOPIC + routingInfo: TEST_ROUTING_INFO }); }; @@ -81,7 +86,7 @@ describe("PeerManager", () => { pubsubTopics: [TEST_PUBSUB_TOPIC], getConnectedPeers: async () => peers, getPeers: async () => peers, - isPeerOnTopic: async (_id: PeerId, _topic: string) => true + isPeerOnShard: async (_id: PeerId, _topic: string) => true } as unknown as IConnectionManager; peerManager = new PeerManager({ libp2p, @@ -126,7 +131,7 @@ describe("PeerManager", () => { const peerId = ids[0]; await peerManager.renewPeer(peerId, { protocol: TEST_PROTOCOL, - pubsubTopic: TEST_PUBSUB_TOPIC + routingInfo: TEST_ROUTING_INFO }); expect((peerManager as any).lockedPeers.has(peerId.toString())).to.be.false; expect((peerManager as any).unlockedPeers.has(peerId.toString())).to.be @@ -224,7 +229,7 @@ describe("PeerManager", () => { if (skipIfNoPeers(first)) return; await peerManager.renewPeer(first[0], { protocol: TEST_PROTOCOL, - pubsubTopic: TEST_PUBSUB_TOPIC + routingInfo: TEST_ROUTING_INFO }); const second = await getPeersForTest(); if (skipIfNoPeers(second)) return; @@ -238,7 +243,7 @@ describe("PeerManager", () => { } as any; await peerManager.renewPeer(fakePeerId, { protocol: TEST_PROTOCOL, - pubsubTopic: TEST_PUBSUB_TOPIC + routingInfo: TEST_ROUTING_INFO }); expect(true).to.be.true; }); @@ -263,7 +268,7 @@ describe("PeerManager", () => { const peerId = result[0]; await peerManager.renewPeer(peerId, { protocol: TEST_PROTOCOL, - pubsubTopic: TEST_PUBSUB_TOPIC + routingInfo: TEST_ROUTING_INFO }); const connection = mockConnections.find((c) => c.remotePeer.equals(peerId)); diff --git a/packages/sdk/src/peer_manager/peer_manager.ts b/packages/sdk/src/peer_manager/peer_manager.ts index a42baf7215..73ab46c72a 100644 --- a/packages/sdk/src/peer_manager/peer_manager.ts +++ b/packages/sdk/src/peer_manager/peer_manager.ts @@ -16,7 +16,7 @@ import { Libp2pEventHandler, Protocols } from "@waku/interfaces"; -import { Logger } from "@waku/utils"; +import { Logger, RoutingInfo } from "@waku/utils"; const log = new Logger("peer-manager"); @@ -34,7 +34,7 @@ type PeerManagerParams = { type GetPeersParams = { protocol: Protocols; - pubsubTopic: string; + routingInfo: RoutingInfo; }; export enum PeerManagerEventNames { @@ -107,7 +107,9 @@ export class PeerManager { public async getPeers(params: GetPeersParams): Promise { log.info( - `Getting peers for protocol: ${params.protocol}, pubsubTopic: ${params.pubsubTopic}` + `Getting peers for protocol: ${params.protocol}, ` + + `clusterId: ${params.routingInfo.networkConfig.clusterId},` + + ` shard: ${params.routingInfo.shardId}` ); const connectedPeers = await this.connectionManager.getConnectedPeers(); @@ -117,13 +119,19 @@ export class PeerManager { for (const peer of connectedPeers) { const hasProtocol = this.hasPeerProtocol(peer, params.protocol); - const hasSamePubsub = await this.connectionManager.isPeerOnTopic( + + const isOnSameShard = await this.connectionManager.isPeerOnShard( peer.id, - params.pubsubTopic + params.routingInfo.networkConfig.clusterId, + params.routingInfo.shardId ); + if (!isOnSameShard) { + continue; + } + const isPeerAvailableForUse = this.isPeerAvailableForUse(peer.id); - if (hasProtocol && hasSamePubsub && isPeerAvailableForUse) { + if (hasProtocol && isPeerAvailableForUse) { results.push(peer); log.info(`Peer ${peer.id} qualifies for protocol ${params.protocol}`); } @@ -168,7 +176,7 @@ export class PeerManager { public async renewPeer(id: PeerId, params: GetPeersParams): Promise { log.info( - `Renewing peer ${id} for protocol: ${params.protocol}, pubsubTopic: ${params.pubsubTopic}` + `Renewing peer ${id} for protocol: ${params.protocol}, routingInfo: ${params.routingInfo}` ); const connectedPeers = await this.connectionManager.getConnectedPeers(); @@ -265,7 +273,7 @@ export class PeerManager { } const wasUnlocked = new Date(value).getTime(); - return Date.now() - wasUnlocked >= 10_000 ? true : false; + return Date.now() - wasUnlocked >= 10_000; } private dispatchFilterPeerConnect(id: PeerId): void { diff --git a/packages/sdk/src/store/store.ts b/packages/sdk/src/store/store.ts index 1297060cf2..3bbe1d48a1 100644 --- a/packages/sdk/src/store/store.ts +++ b/packages/sdk/src/store/store.ts @@ -12,7 +12,7 @@ import { StoreCursor, StoreProtocolOptions } from "@waku/interfaces"; -import { isDefined, Logger } from "@waku/utils"; +import { isDefined, Logger, RoutingInfo } from "@waku/utils"; import { PeerManager } from "../peer_manager/index.js"; @@ -181,7 +181,7 @@ export class Store implements IStore { private validateDecodersAndPubsubTopic( decoders: IDecoder[] ): { - pubsubTopic: string; + routingInfo: RoutingInfo; contentTopics: string[]; decodersAsMap: Map>; } { @@ -191,7 +191,7 @@ export class Store implements IStore { } const uniquePubsubTopicsInQuery = Array.from( - new Set(decoders.map((decoder) => decoder.pubsubTopic)) + new Set(decoders.map((decoder) => decoder.routingInfo.pubsubTopic)) ); if (uniquePubsubTopicsInQuery.length > 1) { log.error("API does not support querying multiple pubsub topics at once"); @@ -214,7 +214,9 @@ export class Store implements IStore { }); const contentTopics = decoders - .filter((decoder) => decoder.pubsubTopic === pubsubTopicForQuery) + .filter( + (decoder) => decoder.routingInfo.pubsubTopic === pubsubTopicForQuery + ) .map((dec) => dec.contentTopic); if (contentTopics.length === 0) { @@ -223,16 +225,18 @@ export class Store implements IStore { } return { - pubsubTopic: pubsubTopicForQuery, + routingInfo: decoders[0].routingInfo, contentTopics, decodersAsMap }; } - private async getPeerToUse(pubsubTopic: string): Promise { + private async getPeerToUse( + routingInfo: RoutingInfo + ): Promise { const peers = await this.peerManager.getPeers({ protocol: Protocols.Store, - pubsubTopic + routingInfo }); return this.options.peers diff --git a/packages/sdk/src/waku/utils.spec.ts b/packages/sdk/src/waku/utils.spec.ts deleted file mode 100644 index 57ed1e495c..0000000000 --- a/packages/sdk/src/waku/utils.spec.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { DEFAULT_NUM_SHARDS, DefaultNetworkConfig } from "@waku/interfaces"; -import { contentTopicToShardIndex } from "@waku/utils"; -import { expect } from "chai"; - -import { decoderParamsToShardInfo, isShardCompatible } from "./utils.js"; - -const TestContentTopic = "/test/1/waku-sdk/utf8"; - -describe("IWaku utils", () => { - describe("decoderParamsToShardInfo", () => { - it("should use provided shard info when available", () => { - const params = { - contentTopic: TestContentTopic, - shardInfo: { - clusterId: 10, - shard: 5 - } - }; - - const result = decoderParamsToShardInfo(params, DefaultNetworkConfig); - - expect(result.clusterId).to.equal(10); - expect(result.shard).to.equal(5); - }); - - it("should use network config clusterId when shard info clusterId is not provided", () => { - const params = { - contentTopic: TestContentTopic, - shardInfo: { - clusterId: 1, - shard: 5 - } - }; - - const result = decoderParamsToShardInfo(params, DefaultNetworkConfig); - - expect(result.clusterId).to.equal(1); - expect(result.shard).to.equal(5); - }); - - it("should use shardsUnderCluster when provided", () => { - const contentTopic = TestContentTopic; - const params = { - contentTopic, - shardInfo: { - clusterId: 10, - shardsUnderCluster: 64 - } - }; - - const result = decoderParamsToShardInfo(params, DefaultNetworkConfig); - const expectedShardIndex = contentTopicToShardIndex(contentTopic, 64); - - expect(result.clusterId).to.equal(10); - expect(result.shard).to.equal(expectedShardIndex); - }); - - it("should calculate shard index from content topic when shard is not provided", () => { - const contentTopic = TestContentTopic; - const params = { - contentTopic - }; - - const result = decoderParamsToShardInfo(params, DefaultNetworkConfig); - const expectedShardIndex = contentTopicToShardIndex( - contentTopic, - DEFAULT_NUM_SHARDS - ); - - expect(result.clusterId).to.equal(1); - expect(result.shard).to.equal(expectedShardIndex); - }); - }); - - describe("isShardCompatible", () => { - it("should return false when clusterId doesn't match", () => { - const shardInfo = { - clusterId: 10, - shard: 5 - }; - - const result = isShardCompatible(shardInfo, DefaultNetworkConfig); - - expect(result).to.be.false; - }); - - it("should return false when shard is not included in network shards", () => { - const shardInfo = { - clusterId: 1, - shard: 5 - }; - - const networkConfig = { - clusterId: 1, - shards: [1, 2, 3, 4] - }; - - const result = isShardCompatible(shardInfo, networkConfig); - - expect(result).to.be.false; - }); - - it("should return true when clusterId matches and shard is included in network shards", () => { - const shardInfo = { - clusterId: 1, - shard: 3 - }; - - const networkConfig = { - clusterId: 1, - shards: [1, 2, 3, 4] - }; - - const result = isShardCompatible(shardInfo, networkConfig); - - expect(result).to.be.true; - }); - }); -}); diff --git a/packages/sdk/src/waku/utils.ts b/packages/sdk/src/waku/utils.ts deleted file mode 100644 index 76c99a6eeb..0000000000 --- a/packages/sdk/src/waku/utils.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { - CreateDecoderParams, - NetworkConfig, - SingleShardInfo -} from "@waku/interfaces"; -import { DEFAULT_NUM_SHARDS } from "@waku/interfaces"; -import { contentTopicToShardIndex } from "@waku/utils"; - -export const decoderParamsToShardInfo = ( - params: CreateDecoderParams, - networkConfig: NetworkConfig -): SingleShardInfo => { - const clusterId = (params.shardInfo?.clusterId || - networkConfig.clusterId) as number; - const shardsUnderCluster = - params.shardInfo && "shardsUnderCluster" in params.shardInfo - ? params.shardInfo.shardsUnderCluster - : DEFAULT_NUM_SHARDS; - - const shardIndex = - params.shardInfo && "shard" in params.shardInfo - ? params.shardInfo.shard - : contentTopicToShardIndex(params.contentTopic, shardsUnderCluster); - - return { - clusterId, - shard: shardIndex - }; -}; - -export const isShardCompatible = ( - shardInfo: SingleShardInfo, - networkConfig: NetworkConfig -): boolean => { - if (networkConfig.clusterId !== shardInfo.clusterId) { - return false; - } - - if ( - "shards" in networkConfig && - !networkConfig.shards.includes(shardInfo.shard!) - ) { - return false; - } - - return true; -}; diff --git a/packages/sdk/src/waku/waku.ts b/packages/sdk/src/waku/waku.ts index df6e845e3a..5d44a2c609 100644 --- a/packages/sdk/src/waku/waku.ts +++ b/packages/sdk/src/waku/waku.ts @@ -27,7 +27,7 @@ import { HealthStatus, Protocols } from "@waku/interfaces"; -import { Logger } from "@waku/utils"; +import { createRoutingInfo, Logger, RoutingInfo } from "@waku/utils"; import { Filter } from "../filter/index.js"; import { HealthIndicator } from "../health_indicator/index.js"; @@ -35,7 +35,6 @@ import { LightPush } from "../light_push/index.js"; import { PeerManager } from "../peer_manager/index.js"; import { Store } from "../store/index.js"; -import { decoderParamsToShardInfo, isShardCompatible } from "./utils.js"; import { waitForRemotePeer } from "./wait_for_remote_peer.js"; const log = new Logger("waku"); @@ -260,40 +259,33 @@ export class WakuNode implements IWaku { } public createDecoder(params: CreateDecoderParams): IDecoder { - const singleShardInfo = decoderParamsToShardInfo( - params, - this.networkConfig + const routingInfo = getRoutingInfo( + this.networkConfig, + params.contentTopic, + params.shardId ); - - log.info( - `Creating Decoder with input:${JSON.stringify(params.shardInfo)}, determined:${JSON.stringify(singleShardInfo)}, expected:${JSON.stringify(this.networkConfig)}.` - ); - - if (!isShardCompatible(singleShardInfo, this.networkConfig)) { - throw Error(`Cannot create decoder: incompatible shard configuration.`); - } - - return createDecoder(params.contentTopic, singleShardInfo); + return createDecoder(params.contentTopic, routingInfo); } public createEncoder(params: CreateEncoderParams): IEncoder { - const singleShardInfo = decoderParamsToShardInfo( - params, - this.networkConfig + const routingInfo = getRoutingInfo( + this.networkConfig, + params.contentTopic, + params.shardId ); - log.info( - `Creating Encoder with input:${JSON.stringify(params.shardInfo)}, determined:${JSON.stringify(singleShardInfo)}, expected:${JSON.stringify(this.networkConfig)}.` - ); - - if (!isShardCompatible(singleShardInfo, this.networkConfig)) { - throw Error(`Cannot create encoder: incompatible shard configuration.`); - } - return createEncoder({ contentTopic: params.contentTopic, ephemeral: params.ephemeral, - pubsubTopicShardInfo: singleShardInfo + routingInfo: routingInfo }); } } + +function getRoutingInfo( + networkConfig: NetworkConfig, + contentTopic?: string, + shardId?: number +): RoutingInfo { + return createRoutingInfo(networkConfig, { contentTopic, shardId }); +} diff --git a/packages/tests/src/constants.ts b/packages/tests/src/constants.ts index 025026df39..6847f41dac 100644 --- a/packages/tests/src/constants.ts +++ b/packages/tests/src/constants.ts @@ -5,7 +5,8 @@ * @module */ -import { PubsubTopic, ShardInfo, SingleShardInfo } from "@waku/interfaces"; +import { AutoSharding, RelayShards } from "@waku/interfaces"; +import { createRoutingInfo } from "@waku/utils"; export const NOISE_KEY_1 = new Uint8Array( ((): number[] => { @@ -46,11 +47,27 @@ export const TEST_STRING = [ { description: "Arabic", value: "مرحبا" }, { description: "Russian", value: "Привет" }, { description: "SQL Injection", value: "'; DROP TABLE users; --" }, - { description: "Script", value: '' }, - { description: "XML", value: "Some content" }, - { description: "Basic HTML tag", value: "

Heading

" }, + { + description: "Script", + value: '', + invalidContentTopic: true + }, + { + description: "XML", + value: "Some content", + invalidContentTopic: true + }, + { + description: "Basic HTML tag", + value: "

Heading

", + invalidContentTopic: true + }, { description: "JSON", value: '{"user":"admin","password":"123456"}' }, - { description: "shell command", value: "`rm -rf /`" }, + { + description: "shell command", + value: "`rm -rf /`", + invalidContentTopic: true + }, { description: "escaped characters", value: "\\n\\t\\0" }, { description: "unicode special characters", value: "\u202Ereverse" }, { description: "emoji", value: "🤫 🤥 😶 😶‍🌫️ 😐 😑 😬 🫨 🫠 🙄 😯 😦 😧 😮" } @@ -68,12 +85,18 @@ export const MOCHA_HOOK_MAX_TIMEOUT = 50_000; export const SEPOLIA_RPC_URL = process.env.SEPOLIA_RPC_URL || "https://sepolia.gateway.tenderly.co"; -export const DefaultTestPubsubTopic: PubsubTopic = "/waku/2/rs/0/0"; -export const DefaultTestShardInfo: ShardInfo = { - clusterId: 0, +export const DefaultTestClusterId = 0; +export const DefaultTestNumShardsInCluster = 10; +export const DefaultTestNetworkConfig: AutoSharding = { + clusterId: DefaultTestClusterId, + numShardsInCluster: DefaultTestNumShardsInCluster +}; +export const DefaultTestRelayShards: RelayShards = { + clusterId: DefaultTestClusterId, shards: [0] }; -export const DefaultTestSingleShardInfo: SingleShardInfo = { - clusterId: 0, - shard: 0 -}; +export const DefaultTestContentTopic = "/test/1/content-topic/proto"; +export const DefaultTestRoutingInfo = createRoutingInfo( + DefaultTestNetworkConfig, + { contentTopic: DefaultTestContentTopic } +); diff --git a/packages/tests/src/lib/index.ts b/packages/tests/src/lib/index.ts index 22323ee2af..02b0b77fac 100644 --- a/packages/tests/src/lib/index.ts +++ b/packages/tests/src/lib/index.ts @@ -1,13 +1,7 @@ -import { - AutoSharding, - IDecodedMessage, - NetworkConfig, - StaticSharding -} from "@waku/interfaces"; -import { contentTopicToShardIndex, Logger } from "@waku/utils"; +import { ContentTopic, IDecodedMessage } from "@waku/interfaces"; +import { isAutoShardingRoutingInfo, Logger, RoutingInfo } from "@waku/utils"; import { expect } from "chai"; -import { DefaultTestPubsubTopic } from "../constants.js"; import { Args, MessageRpcQuery, MessageRpcResponse } from "../types.js"; import { delay, makeLogFileName } from "../utils/index.js"; @@ -29,7 +23,7 @@ export class ServiceNodesFleet { mochaContext: Mocha.Context, nodesToCreate: number = 3, strictChecking: boolean = false, - networkConfig: NetworkConfig, + routingInfo: RoutingInfo, _args?: Args, withoutFilter = false ): Promise { @@ -40,7 +34,7 @@ export class ServiceNodesFleet { makeLogFileName(mochaContext) + Math.random().toString(36).substring(7) ); - const args = getArgs(networkConfig, _args); + const args = applyDefaultArgs(routingInfo, _args); if (nodes[0]) { const addr = await nodes[0].getExternalMultiaddr(); @@ -93,15 +87,19 @@ export class ServiceNodesFleet { public async sendRelayMessage( message: MessageRpcQuery, - pubsubTopic: string = DefaultTestPubsubTopic + routingInfo: RoutingInfo ): Promise { const relayMessagePromises: Promise[] = this.nodes.map((node) => - node.sendMessage(message, pubsubTopic) + node.sendMessage(message, routingInfo) ); const relayMessages = await Promise.all(relayMessagePromises); return relayMessages.every((message) => message); } + /** + * This is a dodgy things to do as it assumes the nwaku node did not flush + * any messages from its cache. + */ public async confirmMessageLength(numMessages: number): Promise { if (this.strictChecking) { await Promise.all( @@ -203,13 +201,12 @@ class MultipleNodesMessageCollector { public async waitForMessages( numMessages: number, options?: { - pubsubTopic?: string; timeoutDuration?: number; exact?: boolean; + contentTopic?: ContentTopic; } ): Promise { const startTime = Date.now(); - const pubsubTopic = options?.pubsubTopic || DefaultTestPubsubTopic; const timeoutDuration = options?.timeoutDuration || 400; const exact = options?.exact || false; @@ -218,7 +215,7 @@ class MultipleNodesMessageCollector { if (this.strictChecking) { const results = await Promise.all( this.relayNodes.map(async (node) => { - const msgs = await node.messages(pubsubTopic); + const msgs = await node.messages(options?.contentTopic); return msgs.length >= numMessages; }) ); @@ -226,7 +223,7 @@ class MultipleNodesMessageCollector { } else { const results = await Promise.all( this.relayNodes.map(async (node) => { - const msgs = await node.messages(pubsubTopic); + const msgs = await node.messages(options?.contentTopic); return msgs.length >= numMessages; }) ); @@ -257,23 +254,25 @@ class MultipleNodesMessageCollector { } } -function getArgs(networkConfig: NetworkConfig, args?: Args): Args { - const defaultArgs = { +function applyDefaultArgs(routingInfo: RoutingInfo, args?: Args): Args { + const defaultArgs: Args = { lightpush: true, filter: true, discv5Discovery: true, peerExchange: true, - relay: true, - clusterId: networkConfig.clusterId - } as Args; + relay: true + }; - if ((networkConfig as StaticSharding).shards) { - defaultArgs.shard = (networkConfig as StaticSharding).shards; - } else if ((networkConfig as AutoSharding).contentTopics) { - defaultArgs.contentTopic = (networkConfig as AutoSharding).contentTopics; - defaultArgs.shard = (networkConfig as AutoSharding).contentTopics.map( - (topic) => contentTopicToShardIndex(topic) - ); + defaultArgs.clusterId = routingInfo.networkConfig.clusterId; + + if (isAutoShardingRoutingInfo(routingInfo)) { + defaultArgs.numShardsInNetwork = + routingInfo.networkConfig.numShardsInCluster; + + defaultArgs.contentTopic = [routingInfo.contentTopic]; + } else { + defaultArgs.numShardsInNetwork = 0; + defaultArgs.shard = [routingInfo.shardId]; } return { ...defaultArgs, ...args }; diff --git a/packages/tests/src/lib/message_collector.ts b/packages/tests/src/lib/message_collector.ts index 3f8a29ee48..456d1f881f 100644 --- a/packages/tests/src/lib/message_collector.ts +++ b/packages/tests/src/lib/message_collector.ts @@ -4,7 +4,6 @@ import { bytesToUtf8, utf8ToBytes } from "@waku/utils/bytes"; import { AssertionError, expect } from "chai"; import { equals } from "uint8arrays/equals"; -import { DefaultTestPubsubTopic } from "../constants.js"; import { MessageRpcResponse } from "../types.js"; import { base64ToUtf8 } from "../utils/base64_utf8.js"; import { delay } from "../utils/delay.js"; @@ -67,20 +66,19 @@ export class MessageCollector { public async waitForMessages( numMessages: number, options?: { - pubsubTopic?: string; + // pubsubTopic?: string; timeoutDuration?: number; exact?: boolean; } ): Promise { const startTime = Date.now(); - const pubsubTopic = this.getPubsubTopicToUse(options?.pubsubTopic); const timeoutDuration = options?.timeoutDuration || 400; const exact = options?.exact || false; while (this.count < numMessages) { if (this.nwaku) { try { - this.list = await this.nwaku.messages(pubsubTopic); + this.list = await this.nwaku.messages(); } catch (error) { log.error(`Can't retrieve messages because of ${error}`); await delay(10); @@ -237,15 +235,13 @@ export class MessageCollector { `Message text mismatch. Expected: ${options.expectedMessageText}. Got: ${receivedMessageText}` ); } else { - const pubsubTopicToUse = this.getPubsubTopicToUse( - options.expectedPubsubTopic - ); - // js-waku message specific assertions - expect(message.pubsubTopic).to.eq( - pubsubTopicToUse, - `Message pub/sub topic mismatch. Expected: ${pubsubTopicToUse}. Got: ${message.pubsubTopic}` - ); - + if (options.expectedPubsubTopic) { + // js-waku message specific assertions + expect(message.pubsubTopic).to.eq( + options.expectedPubsubTopic, + `Message pub/sub topic mismatch. Expected: ${options.expectedPubsubTopic}. Got: ${message.pubsubTopic}` + ); + } expect(bytesToUtf8(message.payload)).to.eq( options.expectedMessageText, `Message text mismatch. Expected: ${ @@ -267,8 +263,4 @@ export class MessageCollector { ); } } - - private getPubsubTopicToUse(pubsubTopic: string | undefined): string { - return pubsubTopic || DefaultTestPubsubTopic; - } } diff --git a/packages/tests/src/lib/runNodes.ts b/packages/tests/src/lib/runNodes.ts index 09f13c9dbd..19ba198bd3 100644 --- a/packages/tests/src/lib/runNodes.ts +++ b/packages/tests/src/lib/runNodes.ts @@ -1,14 +1,23 @@ -import { CreateNodeOptions, NetworkConfig, Protocols } from "@waku/interfaces"; -import { createRelayNode } from "@waku/relay"; +import { + ContentTopic, + type CreateNodeOptions, + type NetworkConfig, + Protocols, + type ShardId +} from "@waku/interfaces"; +import { createRelayNode, RelayCreateOptions } from "@waku/relay"; import { createLightNode, WakuNode } from "@waku/sdk"; import { - derivePubsubTopicsFromNetworkConfig, + createRoutingInfo, + isAutoSharding, + isStaticSharding, Logger, - pubsubTopicsToShardInfo + RoutingInfo } from "@waku/utils"; import { Context } from "mocha"; import { NOISE_KEY_1 } from "../constants.js"; +import { Args } from "../types.js"; import { makeLogFileName } from "../utils/index.js"; import { ServiceNode } from "./service_node.js"; @@ -24,6 +33,8 @@ export const DEFAULT_DISCOVERIES_ENABLED = { type RunNodesOptions = { context: Context; networkConfig: NetworkConfig; + relayShards?: ShardId[]; // Only for static sharding + contentTopics?: ContentTopic[]; // Only for auto sharding protocols: Protocols[]; createNode: typeof createLightNode | typeof createRelayNode; }; @@ -34,32 +45,61 @@ export async function runNodes( const { context, networkConfig, createNode, protocols } = options; const nwaku = new ServiceNode(makeLogFileName(context)); - const pubsubTopics = derivePubsubTopicsFromNetworkConfig(networkConfig); - const shardInfo = pubsubTopicsToShardInfo(pubsubTopics); - await nwaku.start( - { - filter: true, - lightpush: true, - relay: true, - store: true, - shard: shardInfo.shards, - clusterId: shardInfo.clusterId - }, - { retries: 3 } - ); - const waku_options: CreateNodeOptions = { + const nwakuArgs: Args = { + filter: true, + lightpush: true, + relay: true, + store: true, + clusterId: networkConfig.clusterId + }; + + const jswakuArgs: CreateNodeOptions = { staticNoiseKey: NOISE_KEY_1, libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } }, - networkConfig: shardInfo, + networkConfig, lightPush: { numPeersToUse: 2 }, discovery: DEFAULT_DISCOVERIES_ENABLED }; - log.info("Starting js waku node with :", JSON.stringify(waku_options)); + const routingInfos: RoutingInfo[] = []; + if (isAutoSharding(networkConfig)) { + nwakuArgs.numShardsInNetwork = networkConfig.numShardsInCluster; + nwakuArgs.contentTopic = options.contentTopics ?? []; + + nwakuArgs.contentTopic.map((ct) => + routingInfos.push(createRoutingInfo(networkConfig, { contentTopic: ct })) + ); + + if (options.relayShards && options.relayShards.length > 0) + throw "`relayShards` cannot be set for auto-sharding"; + } else if (isStaticSharding(networkConfig) && options.relayShards) { + const shards = options.relayShards; + nwakuArgs.shard = shards; + + shards.map((shardId) => + routingInfos.push(createRoutingInfo(networkConfig, { shardId })) + ); + + if (options.contentTopics && options.contentTopics.length > 0) + throw "`contentTopics` cannot be set for static sharding"; + } else { + throw "Invalid Network Config"; + } + + const jswakuRelayCreateOptions: RelayCreateOptions = { + routingInfos + }; + + await nwaku.start(nwakuArgs, { retries: 3 }); + + log.info("Starting js waku node with :", JSON.stringify(jswakuArgs)); let waku: WakuNode | undefined; try { - waku = (await createNode(waku_options)) as unknown as WakuNode; + waku = (await createNode({ + ...jswakuArgs, + ...jswakuRelayCreateOptions + })) as unknown as WakuNode; await waku.start(); } catch (error) { log.error("jswaku node failed to start:", error); @@ -68,7 +108,18 @@ export async function runNodes( if (waku) { await waku.dial(await nwaku.getMultiaddrWithId()); await waku.waitForPeers(protocols); - await nwaku.ensureSubscriptions(pubsubTopics); + + // TODO + + // const clusterId = networkConfig.clusterId; + + // await nwaku.ensureSubscriptions( + // relayShardsToPubsubTopics({ + // clusterId, + // shards: options.relayShards ?? [] + // }) + // ); + return [nwaku, waku as T]; } else { throw new Error("Failed to initialize waku"); diff --git a/packages/tests/src/lib/service_node.ts b/packages/tests/src/lib/service_node.ts index 6f7262a006..7048c763bc 100644 --- a/packages/tests/src/lib/service_node.ts +++ b/packages/tests/src/lib/service_node.ts @@ -1,12 +1,19 @@ import type { PeerId } from "@libp2p/interface"; import { peerIdFromString } from "@libp2p/peer-id"; import { Multiaddr, multiaddr } from "@multiformats/multiaddr"; -import { isDefined, shardInfoToPubsubTopics } from "@waku/utils"; +import { ContentTopic, PubsubTopic } from "@waku/interfaces"; +import { + formatPubsubTopic, + isAutoSharding, + isDefined, + isStaticSharding, + RoutingInfo +} from "@waku/utils"; import { Logger } from "@waku/utils"; import pRetry from "p-retry"; import portfinder from "portfinder"; -import { DefaultTestPubsubTopic } from "../constants.js"; +import { DefaultTestNetworkConfig } from "../constants.js"; import { Args, LogLevel, @@ -245,9 +252,7 @@ export class ServiceNode { ); } - public async ensureSubscriptions( - pubsubTopics: string[] = [DefaultTestPubsubTopic] - ): Promise { + public async ensureSubscriptions(pubsubTopics: string[]): Promise { return this.restCall( "/relay/v1/subscriptions", "POST", @@ -256,13 +261,51 @@ export class ServiceNode { ); } - public async messages(_pubsubTopic?: string): Promise { - const pubsubTopic = - _pubsubTopic ?? - shardInfoToPubsubTopics({ - clusterId: this.args?.clusterId, - shards: this.args?.shard - })[0]; + public async messages( + contentTopic?: ContentTopic + ): Promise { + if (contentTopic) { + return this.contentTopicMessages(contentTopic); + } + + if (this.args?.contentTopic) { + if (this.args?.contentTopic.length > 1) + throw "More that one content topic passed, not supported"; + const contentTopic = this.args?.contentTopic[0]; + + return this.contentTopicMessages(contentTopic); + } + + if (this.args?.shard) { + if (this.args?.shard.length > 1) + throw "More that one shard passed, not supported"; + const pubsubTopic = formatPubsubTopic( + this.args.clusterId ?? DefaultTestNetworkConfig.clusterId, + this.args?.shard[0] + ); + return this.pubsubTopicMessages(pubsubTopic); + } + + throw "Content topic, shard or pubsubTopic must be set"; + } + + private async contentTopicMessages( + contentTopic: ContentTopic + ): Promise { + return this.restCall( + `/relay/v1/auto/messages/${encodeURIComponent(contentTopic)}`, + "GET", + null, + async (response) => { + const data = await response.json(); + return data?.length ? data : []; + } + ); + } + + private async pubsubTopicMessages( + pubsubTopic: PubsubTopic + ): Promise { return this.restCall( `/relay/v1/messages/${encodeURIComponent(pubsubTopic)}`, "GET", @@ -289,7 +332,20 @@ export class ServiceNode { public async sendMessage( message: MessageRpcQuery, - _pubsubTopic?: string + routingInfo: RoutingInfo + ): Promise { + if (isAutoSharding(routingInfo.networkConfig)) { + return this.sendMessageAutoSharding(message); + } + if (isStaticSharding(routingInfo.networkConfig)) { + return this.sendMessageStaticSharding(message, routingInfo.pubsubTopic); + } + throw "Invalid network config"; + } + + private async sendMessageStaticSharding( + message: MessageRpcQuery, + pubsubTopic: PubsubTopic ): Promise { this.checkProcess(); @@ -297,21 +353,15 @@ export class ServiceNode { message.timestamp = BigInt(new Date().valueOf()) * OneMillion; } - const pubsubTopic = - _pubsubTopic ?? - shardInfoToPubsubTopics({ - clusterId: this.args?.clusterId, - shards: this.args?.shard - })[0]; return this.restCall( - `/relay/v1/messages/${encodeURIComponent(pubsubTopic || DefaultTestPubsubTopic)}`, + `/relay/v1/messages/${encodeURIComponent(pubsubTopic)}`, "POST", message, async (response) => response.status === 200 ); } - public async sendMessageAutosharding( + private async sendMessageAutoSharding( message: MessageRpcQuery ): Promise { this.checkProcess(); @@ -398,7 +448,11 @@ export class ServiceNode { if (body) options.body = JSON.stringify(body); const response = await fetch(`${this.httpUrl}${endpoint}`, options); - log.info(`Received REST Response: `, response.status); + log.info( + `Received REST Response: `, + response.status, + response.statusText + ); return await processResponse(response); } catch (error) { log.error(`${this.httpUrl} failed with error:`, error); @@ -429,9 +483,7 @@ export function defaultArgs(): Args { rest: true, restAdmin: true, websocketSupport: true, - logLevel: LogLevel.Trace, - clusterId: 0, - shard: [0] + logLevel: LogLevel.Trace }; } diff --git a/packages/tests/src/types.ts b/packages/tests/src/types.ts index 872cadbe5a..d7b1903f7a 100644 --- a/packages/tests/src/types.ts +++ b/packages/tests/src/types.ts @@ -1,3 +1,5 @@ +import type { ClusterId, ShardId } from "@waku/interfaces"; + export interface Args { staticnode?: string; nat?: "none"; @@ -21,8 +23,9 @@ export interface Args { websocketPort?: number; discv5BootstrapNode?: string; discv5UdpPort?: number; - clusterId?: number; - shard?: Array; + clusterId?: ClusterId; + shard?: Array; + numShardsInNetwork?: number; rlnRelayEthClientAddress?: string; } diff --git a/packages/tests/src/utils/generate_test_data.ts b/packages/tests/src/utils/generate_test_data.ts index cd1b6ed61b..420f06b56e 100644 --- a/packages/tests/src/utils/generate_test_data.ts +++ b/packages/tests/src/utils/generate_test_data.ts @@ -1,13 +1,11 @@ import { createDecoder, createEncoder, Decoder, Encoder } from "@waku/core"; - -type TestDataOptions = { - pubsubTopic: string; -}; +import { AutoSharding } from "@waku/interfaces"; +import { createRoutingInfo } from "@waku/utils"; // Utility to generate test data for multiple topics tests. export function generateTestData( topicCount: number, - options?: TestDataOptions + networkConfig: AutoSharding ): { contentTopics: string[]; encoders: Encoder[]; @@ -15,14 +13,22 @@ export function generateTestData( } { const contentTopics = Array.from( { length: topicCount }, - (_, i) => `/test/${i + 1}/waku-multi/default` + // Remember that auto-sharding uses both app name and app version fields + (_, i) => `/test/0/waku-multi-${i + 1}/default` ); const encoders = contentTopics.map((topic) => - createEncoder({ contentTopic: topic, pubsubTopic: options?.pubsubTopic }) + createEncoder({ + contentTopic: topic, + routingInfo: createRoutingInfo(networkConfig, { contentTopic: topic }) + }) ); const decoders = contentTopics.map((topic) => - createDecoder(topic, options?.pubsubTopic) + createDecoder( + topic, + createRoutingInfo(networkConfig, { contentTopic: topic }) + ) ); + return { contentTopics, encoders, diff --git a/packages/tests/src/utils/nodes.ts b/packages/tests/src/utils/nodes.ts index ef312f5868..3490193d84 100644 --- a/packages/tests/src/utils/nodes.ts +++ b/packages/tests/src/utils/nodes.ts @@ -1,13 +1,11 @@ import { CreateNodeOptions, - DefaultNetworkConfig, IWaku, LightNode, - NetworkConfig, Protocols } from "@waku/interfaces"; import { createLightNode } from "@waku/sdk"; -import { derivePubsubTopicsFromNetworkConfig } from "@waku/utils"; +import { RoutingInfo } from "@waku/utils"; import { Context } from "mocha"; import pRetry from "p-retry"; @@ -18,9 +16,20 @@ import { Args } from "../types.js"; import { waitForConnections } from "./waitForConnections.js"; +/** + * Runs both js-waku and nwaku nodes. + * + * @param context + * @param routingInfo + * @param customArgs passed to nwaku service nodes + * @param strictChecking + * @param numServiceNodes + * @param withoutFilter + * @param jsWakuParams + */ export async function runMultipleNodes( context: Context, - networkConfig: NetworkConfig = DefaultNetworkConfig, + routingInfo: RoutingInfo, customArgs?: Args, strictChecking: boolean = false, numServiceNodes = 2, @@ -32,7 +41,7 @@ export async function runMultipleNodes( context, numServiceNodes, strictChecking, - networkConfig, + routingInfo, customArgs, withoutFilter ); @@ -42,7 +51,7 @@ export async function runMultipleNodes( libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } }, - networkConfig, + networkConfig: routingInfo.networkConfig, lightPush: { numPeersToUse: numServiceNodes }, discovery: DEFAULT_DISCOVERIES_ENABLED, ...jsWakuParams @@ -57,9 +66,10 @@ export async function runMultipleNodes( for (const node of serviceNodes.nodes) { await waku.dial(await node.getMultiaddrWithId()); await waku.waitForPeers([Protocols.Filter, Protocols.LightPush]); - await node.ensureSubscriptions( - derivePubsubTopicsFromNetworkConfig(networkConfig) - ); + // TODO + // await node.ensureSubscriptions( + // derivePubsubTopicsFromNetworkConfig(networkConfig) + // ); const wakuConnections = waku.libp2p.getConnections(); diff --git a/packages/tests/tests/connection-mananger/connection_limiter.spec.ts b/packages/tests/tests/connection-mananger/connection_limiter.spec.ts index 57aa374b14..ee6b9f0da7 100644 --- a/packages/tests/tests/connection-mananger/connection_limiter.spec.ts +++ b/packages/tests/tests/connection-mananger/connection_limiter.spec.ts @@ -9,7 +9,7 @@ import { teardownNodesWithRedundancy } from "../../src/index.js"; -import { TestShardInfo } from "./utils.js"; +import { TestRoutingInfo } from "./utils.js"; describe("Connection Limiter", function () { let waku: LightNode; @@ -18,7 +18,7 @@ describe("Connection Limiter", function () { beforeEachCustom(this, async () => { [serviceNodes, waku] = await runMultipleNodes( this.ctx, - TestShardInfo, + TestRoutingInfo, { lightpush: true, filter: true, peerExchange: true }, false, 2, @@ -68,7 +68,7 @@ describe("Connection Limiter", function () { [serviceNodes, waku] = await runMultipleNodes( this.ctx, - TestShardInfo, + TestRoutingInfo, { lightpush: true, filter: true, peerExchange: true }, false, 2, @@ -126,7 +126,7 @@ describe("Connection Limiter", function () { [serviceNodes, waku] = await runMultipleNodes( this.ctx, - TestShardInfo, + TestRoutingInfo, { lightpush: true, filter: true, peerExchange: true }, false, 2, diff --git a/packages/tests/tests/connection-mananger/dialing.spec.ts b/packages/tests/tests/connection-mananger/dialing.spec.ts index 70c9a3e017..5f11541a26 100644 --- a/packages/tests/tests/connection-mananger/dialing.spec.ts +++ b/packages/tests/tests/connection-mananger/dialing.spec.ts @@ -10,7 +10,7 @@ import { teardownNodesWithRedundancy } from "../../src/index.js"; -import { TestShardInfo } from "./utils.js"; +import { TestRoutingInfo } from "./utils.js"; describe("Dialing", function () { const ctx: Context = this.ctx; @@ -20,7 +20,7 @@ describe("Dialing", function () { beforeEachCustom(this, async () => { [serviceNodes, waku] = await runMultipleNodes( this.ctx, - TestShardInfo, + TestRoutingInfo, { lightpush: true, filter: true, peerExchange: true }, false, 2, @@ -33,7 +33,7 @@ describe("Dialing", function () { ctx, 2, false, - TestShardInfo, + TestRoutingInfo, { lightpush: true, filter: true, diff --git a/packages/tests/tests/connection-mananger/discovery_dialer.spec.ts b/packages/tests/tests/connection-mananger/discovery_dialer.spec.ts index 8d33b69715..1105b16768 100644 --- a/packages/tests/tests/connection-mananger/discovery_dialer.spec.ts +++ b/packages/tests/tests/connection-mananger/discovery_dialer.spec.ts @@ -11,7 +11,7 @@ import { teardownNodesWithRedundancy } from "../../src/index.js"; -import { TestShardInfo } from "./utils.js"; +import { TestRoutingInfo } from "./utils.js"; // TODO: investigate and re-enable in https://github.com/waku-org/js-waku/issues/2453 describe.skip("DiscoveryDialer", function () { @@ -22,7 +22,7 @@ describe.skip("DiscoveryDialer", function () { beforeEachCustom(this, async () => { [serviceNodes, waku] = await runMultipleNodes( this.ctx, - TestShardInfo, + TestRoutingInfo, { lightpush: true, filter: true, peerExchange: true }, false, 2, @@ -35,7 +35,7 @@ describe.skip("DiscoveryDialer", function () { ctx, 2, false, - TestShardInfo, + TestRoutingInfo, { lightpush: true, filter: true, diff --git a/packages/tests/tests/connection-mananger/network_monitor.spec.ts b/packages/tests/tests/connection-mananger/network_monitor.spec.ts index 371660f378..bfc0c2c322 100644 --- a/packages/tests/tests/connection-mananger/network_monitor.spec.ts +++ b/packages/tests/tests/connection-mananger/network_monitor.spec.ts @@ -11,7 +11,8 @@ import { expect } from "chai"; import { afterEachCustom, beforeEachCustom, - DefaultTestShardInfo, + DefaultTestNetworkConfig, + DefaultTestRoutingInfo, delay, NOISE_KEY_1 } from "../../src/index.js"; @@ -36,7 +37,7 @@ describe("Connection state", function () { let originalNavigator: any; beforeEachCustom(this, async () => { - waku = await createLightNode({ networkConfig: DefaultTestShardInfo }); + waku = await createLightNode({ networkConfig: DefaultTestNetworkConfig }); nwaku1 = new ServiceNode(makeLogFileName(this.ctx) + "1"); nwaku2 = new ServiceNode(makeLogFileName(this.ctx) + "2"); await nwaku1.start({ filter: true }); @@ -104,11 +105,13 @@ describe("Connection state", function () { it("`waku:online` between 2 js-waku relay nodes", async function () { const waku1 = await createRelayNode({ staticNoiseKey: NOISE_KEY_1, - networkConfig: DefaultTestShardInfo + networkConfig: DefaultTestNetworkConfig, + routingInfos: [DefaultTestRoutingInfo] }); const waku2 = await createRelayNode({ libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } }, - networkConfig: DefaultTestShardInfo + networkConfig: DefaultTestNetworkConfig, + routingInfos: [DefaultTestRoutingInfo] }); let eventCount1 = 0; @@ -171,10 +174,12 @@ describe("Connection state", function () { it("isConnected between 2 js-waku relay nodes", async function () { const waku1 = await createRelayNode({ - staticNoiseKey: NOISE_KEY_1 + staticNoiseKey: NOISE_KEY_1, + routingInfos: [DefaultTestRoutingInfo] }); const waku2 = await createRelayNode({ - libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } } + libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } }, + routingInfos: [DefaultTestRoutingInfo] }); await waku1.libp2p.peerStore.merge(waku2.libp2p.peerId, { multiaddrs: waku2.libp2p.getMultiaddrs() diff --git a/packages/tests/tests/connection-mananger/utils.ts b/packages/tests/tests/connection-mananger/utils.ts index 2447e946a1..0ea69ed16f 100644 --- a/packages/tests/tests/connection-mananger/utils.ts +++ b/packages/tests/tests/connection-mananger/utils.ts @@ -1,6 +1,11 @@ +import { createRoutingInfo } from "@waku/utils"; + export const TestContentTopic = "/test/1/waku-light-push/utf8"; -export const ClusterId = 3; -export const TestShardInfo = { - contentTopics: [TestContentTopic], - clusterId: ClusterId +export const TestClusterId = 2; +export const TestNetworkConfig = { + clusterId: TestClusterId, + numShardsInCluster: 8 // Cannot be under 8 for nwaku 0.36.0 and below }; +export const TestRoutingInfo = createRoutingInfo(TestNetworkConfig, { + contentTopic: TestContentTopic +}); diff --git a/packages/tests/tests/enr.node.spec.ts b/packages/tests/tests/enr.node.spec.ts index 83bd80fd01..cd66d8621a 100644 --- a/packages/tests/tests/enr.node.spec.ts +++ b/packages/tests/tests/enr.node.spec.ts @@ -6,12 +6,16 @@ import { expect } from "chai"; import { afterEachCustom, + DefaultTestClusterId, + DefaultTestContentTopic, + DefaultTestNetworkConfig, + DefaultTestNumShardsInCluster, + DefaultTestRoutingInfo, makeLogFileName, NOISE_KEY_1, ServiceNode, tearDownNodes } from "../src/index.js"; -import { DefaultTestShardInfo } from "../src/index.js"; describe("ENR Interop: ServiceNode", function () { let waku: RelayNode; @@ -29,14 +33,16 @@ describe("ENR Interop: ServiceNode", function () { store: false, filter: false, lightpush: false, - clusterId: DefaultTestShardInfo.clusterId, - shard: DefaultTestShardInfo.shards + clusterId: DefaultTestClusterId, + numShardsInNetwork: DefaultTestNumShardsInCluster, + contentTopic: [DefaultTestContentTopic] }); const multiAddrWithId = await nwaku.getMultiaddrWithId(); waku = await createRelayNode({ staticNoiseKey: NOISE_KEY_1, - networkConfig: DefaultTestShardInfo + networkConfig: DefaultTestNetworkConfig, + routingInfos: [DefaultTestRoutingInfo] }); await waku.start(); await waku.dial(multiAddrWithId); @@ -64,14 +70,16 @@ describe("ENR Interop: ServiceNode", function () { store: true, filter: false, lightpush: false, - clusterId: DefaultTestShardInfo.clusterId, - shard: DefaultTestShardInfo.shards + clusterId: DefaultTestClusterId, + numShardsInNetwork: DefaultTestNumShardsInCluster, + contentTopic: [DefaultTestContentTopic] }); const multiAddrWithId = await nwaku.getMultiaddrWithId(); waku = await createRelayNode({ staticNoiseKey: NOISE_KEY_1, - networkConfig: DefaultTestShardInfo + networkConfig: DefaultTestNetworkConfig, + routingInfos: [DefaultTestRoutingInfo] }); await waku.start(); await waku.dial(multiAddrWithId); @@ -99,14 +107,16 @@ describe("ENR Interop: ServiceNode", function () { store: true, filter: true, lightpush: true, - clusterId: DefaultTestShardInfo.clusterId, - shard: DefaultTestShardInfo.shards + clusterId: DefaultTestClusterId, + numShardsInNetwork: DefaultTestNumShardsInCluster, + contentTopic: [DefaultTestContentTopic] }); const multiAddrWithId = await nwaku.getMultiaddrWithId(); waku = await createRelayNode({ staticNoiseKey: NOISE_KEY_1, - networkConfig: DefaultTestShardInfo + networkConfig: DefaultTestNetworkConfig, + routingInfos: [DefaultTestRoutingInfo] }); await waku.start(); await waku.dial(multiAddrWithId); diff --git a/packages/tests/tests/ephemeral.node.spec.ts b/packages/tests/tests/ephemeral.node.spec.ts index 8f8a3c15b6..848162ca4b 100644 --- a/packages/tests/tests/ephemeral.node.spec.ts +++ b/packages/tests/tests/ephemeral.node.spec.ts @@ -1,5 +1,5 @@ import { createDecoder, createEncoder } from "@waku/core"; -import { Protocols } from "@waku/interfaces"; +import { AutoSharding, Protocols } from "@waku/interfaces"; import type { IDecodedMessage, LightNode } from "@waku/interfaces"; import { generatePrivateKey, @@ -15,11 +15,7 @@ import { createEncoder as createSymEncoder } from "@waku/message-encryption/symmetric"; import { createLightNode } from "@waku/sdk"; -import { - contentTopicToPubsubTopic, - contentTopicToShardIndex, - Logger -} from "@waku/utils"; +import { createRoutingInfo, Logger } from "@waku/utils"; import { bytesToUtf8, utf8ToBytes } from "@waku/utils/bytes"; import { expect } from "chai"; @@ -36,15 +32,21 @@ import { const log = new Logger("test:ephemeral"); -const ClusterId = 2; +const TestClusterId = 2; +const TestNetworkConfig: AutoSharding = { + clusterId: TestClusterId, + numShardsInCluster: 8 +}; const TestContentTopic = "/test/1/ephemeral/utf8"; -const PubsubTopic = contentTopicToPubsubTopic(TestContentTopic, ClusterId); +const TestRoutingInfo = createRoutingInfo(TestNetworkConfig, { + contentTopic: TestContentTopic +}); const TestEncoder = createEncoder({ contentTopic: TestContentTopic, - pubsubTopic: PubsubTopic + routingInfo: TestRoutingInfo }); -const TestDecoder = createDecoder(TestContentTopic, PubsubTopic); +const TestDecoder = createDecoder(TestContentTopic, TestRoutingInfo); const privateKey = generatePrivateKey(); const symKey = generateSymmetricKey(); @@ -57,26 +59,26 @@ const AsymEncoder = createEciesEncoder({ contentTopic: AsymContentTopic, publicKey, ephemeral: true, - pubsubTopic: PubsubTopic + routingInfo: TestRoutingInfo }); const SymEncoder = createSymEncoder({ contentTopic: SymContentTopic, symKey, ephemeral: true, - pubsubTopic: PubsubTopic + routingInfo: TestRoutingInfo }); const ClearEncoder = createEncoder({ contentTopic: TestContentTopic, ephemeral: true, - pubsubTopic: PubsubTopic + routingInfo: TestRoutingInfo }); const AsymDecoder = createEciesDecoder( AsymContentTopic, - privateKey, - PubsubTopic + TestRoutingInfo, + privateKey ); -const SymDecoder = createSymDecoder(SymContentTopic, symKey, PubsubTopic); +const SymDecoder = createSymDecoder(SymContentTopic, TestRoutingInfo, symKey); describe("Waku Message Ephemeral field", function () { let waku: LightNode; @@ -95,8 +97,7 @@ describe("Waku Message Ephemeral field", function () { store: true, relay: true, contentTopic: contentTopics, - clusterId: ClusterId, - shard: contentTopics.map((t) => contentTopicToShardIndex(t)) + clusterId: TestClusterId }); await nwaku.ensureSubscriptionsAutosharding([ TestContentTopic, @@ -107,10 +108,7 @@ describe("Waku Message Ephemeral field", function () { waku = await createLightNode({ staticNoiseKey: NOISE_KEY_1, libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } }, - networkConfig: { - contentTopics: [TestContentTopic, AsymContentTopic, SymContentTopic], - clusterId: ClusterId - } + networkConfig: TestNetworkConfig }); await waku.start(); await waku.dial(await nwaku.getMultiaddrWithId()); @@ -138,17 +136,11 @@ describe("Waku Message Ephemeral field", function () { const [waku1, waku2, nimWakuMultiaddr] = await Promise.all([ createLightNode({ staticNoiseKey: NOISE_KEY_1, - networkConfig: { - contentTopics: [TestContentTopic, AsymContentTopic, SymContentTopic], - clusterId: ClusterId - } + networkConfig: TestNetworkConfig }).then((waku) => waku.start().then(() => waku)), createLightNode({ staticNoiseKey: NOISE_KEY_2, - networkConfig: { - contentTopics: [TestContentTopic, AsymContentTopic, SymContentTopic], - clusterId: ClusterId - } + networkConfig: TestNetworkConfig }).then((waku) => waku.start().then(() => waku)), nwaku.getMultiaddrWithId() ]); @@ -200,7 +192,7 @@ describe("Waku Message Ephemeral field", function () { const ephemeralEncoder = createEncoder({ contentTopic: TestContentTopic, ephemeral: true, - pubsubTopic: PubsubTopic + routingInfo: TestRoutingInfo }); const messages: IDecodedMessage[] = []; @@ -246,9 +238,9 @@ describe("Waku Message Ephemeral field", function () { const encoder = createSymEncoder({ contentTopic: SymContentTopic, symKey, - pubsubTopic: PubsubTopic + routingInfo: TestRoutingInfo }); - const decoder = createSymDecoder(SymContentTopic, symKey, PubsubTopic); + const decoder = createSymDecoder(SymContentTopic, TestRoutingInfo, symKey); const messages: IDecodedMessage[] = []; const callback = (msg: IDecodedMessage): void => { @@ -293,12 +285,12 @@ describe("Waku Message Ephemeral field", function () { const encoder = createEciesEncoder({ contentTopic: AsymContentTopic, publicKey: publicKey, - pubsubTopic: PubsubTopic + routingInfo: TestRoutingInfo }); const decoder = createEciesDecoder( AsymContentTopic, - privateKey, - PubsubTopic + TestRoutingInfo, + privateKey ); const messages: IDecodedMessage[] = []; diff --git a/packages/tests/tests/filter/push.node.spec.ts b/packages/tests/tests/filter/push.node.spec.ts index 3ecbf6a585..b183c64d93 100644 --- a/packages/tests/tests/filter/push.node.spec.ts +++ b/packages/tests/tests/filter/push.node.spec.ts @@ -1,5 +1,6 @@ import { LightNode, Protocols } from "@waku/interfaces"; import { utf8ToBytes } from "@waku/sdk"; +import { createRoutingInfo } from "@waku/utils"; import { expect } from "chai"; import { @@ -18,8 +19,7 @@ import { TestContentTopic, TestDecoder, TestEncoder, - TestPubsubTopic, - TestShardInfo + TestRoutingInfo } from "./utils.js"; const runTests = (strictCheckNodes: boolean): void => { @@ -32,7 +32,7 @@ const runTests = (strictCheckNodes: boolean): void => { beforeEachCustom(this, async () => { ctx = this.ctx; - [serviceNodes, waku] = await runMultipleNodes(this.ctx, TestShardInfo, { + [serviceNodes, waku] = await runMultipleNodes(this.ctx, TestRoutingInfo, { lightpush: true, filter: true }); @@ -59,7 +59,7 @@ const runTests = (strictCheckNodes: boolean): void => { serviceNodes.messageCollector.verifyReceivedMessage(0, { expectedMessageText: testItem.value, expectedContentTopic: TestContentTopic, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); }); }); @@ -78,7 +78,7 @@ const runTests = (strictCheckNodes: boolean): void => { payload: Buffer.from(utf8ToBytes(messageText)).toString("base64"), timestamp: testItem as any }, - TestPubsubTopic + TestRoutingInfo ); expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq( @@ -88,7 +88,7 @@ const runTests = (strictCheckNodes: boolean): void => { expectedMessageText: messageText, checkTimestamp: false, expectedContentTopic: TestContentTopic, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); // Check if the timestamp matches @@ -117,7 +117,7 @@ const runTests = (strictCheckNodes: boolean): void => { payload: Buffer.from(utf8ToBytes(messageText)).toString("base64"), timestamp: "2023-09-06T12:05:38.609Z" as any }, - TestPubsubTopic + TestRoutingInfo ); // Verify that no message was received @@ -133,20 +133,21 @@ const runTests = (strictCheckNodes: boolean): void => { ); await delay(400); + const wrongContentTopic = "/wrong/1/ContentTopic/proto"; await serviceNodes.sendRelayMessage( { - contentTopic: TestContentTopic, + contentTopic: wrongContentTopic, payload: Buffer.from(utf8ToBytes(messageText)).toString("base64"), timestamp: BigInt(Date.now()) * BigInt(1000000) }, - "WrongContentTopic" + createRoutingInfo(TestRoutingInfo.networkConfig, { + contentTopic: "/wrong/1/ContentTopic/proto" + }) ); - expect( - await serviceNodes.messageCollector.waitForMessages(1, { - pubsubTopic: TestPubsubTopic - }) - ).to.eq(false); + expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq( + false + ); }); it("Check message with no pubsub topic is not received", async function () { @@ -184,7 +185,7 @@ const runTests = (strictCheckNodes: boolean): void => { payload: Buffer.from(utf8ToBytes(messageText)).toString("base64"), timestamp: BigInt(Date.now()) * BigInt(1000000) }, - TestPubsubTopic + TestRoutingInfo ); expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq( @@ -205,7 +206,7 @@ const runTests = (strictCheckNodes: boolean): void => { timestamp: BigInt(Date.now()) * BigInt(1000000), payload: undefined as any }, - TestPubsubTopic + TestRoutingInfo ); expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq( @@ -226,7 +227,7 @@ const runTests = (strictCheckNodes: boolean): void => { payload: 12345 as unknown as string, timestamp: BigInt(Date.now()) * BigInt(1000000) }, - TestPubsubTopic + TestRoutingInfo ); expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq( @@ -267,12 +268,12 @@ const runTests = (strictCheckNodes: boolean): void => { serviceNodes.messageCollector.verifyReceivedMessage(0, { expectedMessageText: "M1", expectedContentTopic: TestContentTopic, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); serviceNodes.messageCollector.verifyReceivedMessage(1, { expectedMessageText: "M2", expectedContentTopic: TestContentTopic, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); }); @@ -289,7 +290,7 @@ const runTests = (strictCheckNodes: boolean): void => { serviceNodes.messageCollector.verifyReceivedMessage(0, { expectedMessageText: "M1", expectedContentTopic: TestContentTopic, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); await teardownNodesWithRedundancy(serviceNodes, []); @@ -297,7 +298,7 @@ const runTests = (strictCheckNodes: boolean): void => { ctx, 2, false, - TestShardInfo, + TestRoutingInfo, { lightpush: true, filter: true, @@ -334,7 +335,7 @@ const runTests = (strictCheckNodes: boolean): void => { serviceNodes.messageCollector.verifyReceivedMessage(1, { expectedMessageText: "M2", expectedContentTopic: TestContentTopic, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); }); }); diff --git a/packages/tests/tests/filter/subscribe.node.spec.ts b/packages/tests/tests/filter/subscribe.node.spec.ts index d8a0c4ea0d..6fd66b9ef2 100644 --- a/packages/tests/tests/filter/subscribe.node.spec.ts +++ b/packages/tests/tests/filter/subscribe.node.spec.ts @@ -8,6 +8,7 @@ import { symmetric } from "@waku/message-encryption"; import { Protocols, utf8ToBytes } from "@waku/sdk"; +import { createRoutingInfo } from "@waku/utils"; import { expect } from "chai"; import { @@ -27,15 +28,15 @@ import { } from "../../src/index.js"; import { - ClusterId, messagePayload, messageText, - ShardIndex, + TestClusterId, TestContentTopic, TestDecoder, TestEncoder, - TestPubsubTopic, - TestShardInfo + TestNetworkConfig, + TestRoutingInfo, + TestShardIndex } from "./utils.js"; const runTests = (strictCheckNodes: boolean): void => { @@ -47,7 +48,7 @@ const runTests = (strictCheckNodes: boolean): void => { beforeEachCustom(this, async () => { [serviceNodes, waku] = await runMultipleNodes( this.ctx, - TestShardInfo, + TestRoutingInfo, undefined, strictCheckNodes ); @@ -84,12 +85,12 @@ const runTests = (strictCheckNodes: boolean): void => { const encoder = ecies.createEncoder({ contentTopic: TestContentTopic, publicKey, - pubsubTopic: TestPubsubTopic + routingInfo: TestRoutingInfo }); const decoder = ecies.createDecoder( TestContentTopic, - privateKey, - TestPubsubTopic + TestRoutingInfo, + privateKey ); await waku.filter.subscribe( @@ -106,7 +107,7 @@ const runTests = (strictCheckNodes: boolean): void => { expectedMessageText: messageText, expectedContentTopic: TestContentTopic, expectedVersion: 1, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); await serviceNodes.confirmMessageLength(2); @@ -117,12 +118,12 @@ const runTests = (strictCheckNodes: boolean): void => { const encoder = symmetric.createEncoder({ contentTopic: TestContentTopic, symKey, - pubsubTopic: TestPubsubTopic + routingInfo: TestRoutingInfo }); const decoder = symmetric.createDecoder( TestContentTopic, - symKey, - TestPubsubTopic + TestRoutingInfo, + symKey ); await waku.filter.subscribe( @@ -139,7 +140,7 @@ const runTests = (strictCheckNodes: boolean): void => { expectedMessageText: messageText, expectedContentTopic: TestContentTopic, expectedVersion: 1, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); await serviceNodes.confirmMessageLength(2); @@ -158,7 +159,7 @@ const runTests = (strictCheckNodes: boolean): void => { contentTopic: TestContentTopic, payload: utf8ToBytes(messageText) }); - await serviceNodes.sendRelayMessage(relayMessage, TestPubsubTopic); + await serviceNodes.sendRelayMessage(relayMessage, TestRoutingInfo); expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq( true @@ -166,7 +167,7 @@ const runTests = (strictCheckNodes: boolean): void => { serviceNodes.messageCollector.verifyReceivedMessage(0, { expectedMessageText: messageText, expectedContentTopic: TestContentTopic, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); await serviceNodes.confirmMessageLength(1); @@ -219,18 +220,20 @@ const runTests = (strictCheckNodes: boolean): void => { serviceNodes.messageCollector.verifyReceivedMessage(0, { expectedMessageText: messageText, expectedContentTopic: TestContentTopic, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); // Modify subscription to include a new content topic and send a message. const newMessageText = "Filtering still works!"; - const newMessagePayload = { payload: utf8ToBytes(newMessageText) }; const newContentTopic = "/test/2/waku-filter/default"; + const newRoutingInfo = createRoutingInfo(TestNetworkConfig, { + contentTopic: newContentTopic + }); const newEncoder = createEncoder({ contentTopic: newContentTopic, - pubsubTopic: TestPubsubTopic + routingInfo: newRoutingInfo }); - const newDecoder = createDecoder(newContentTopic, TestPubsubTopic); + const newDecoder = createDecoder(newContentTopic, newRoutingInfo); await waku.filter.subscribe( newDecoder, serviceNodes.messageCollector.callback @@ -244,26 +247,30 @@ const runTests = (strictCheckNodes: boolean): void => { serviceNodes.messageCollector.verifyReceivedMessage(1, { expectedContentTopic: newContentTopic, expectedMessageText: newMessageText, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); // Send another message on the initial content topic to verify it still works. - await waku.lightPush.send(TestEncoder, newMessagePayload); + const thirdMessageText = "Filtering still works on first subscription!"; + const thirdMessagePayload = { payload: utf8ToBytes(thirdMessageText) }; + await waku.lightPush.send(TestEncoder, thirdMessagePayload); expect(await serviceNodes.messageCollector.waitForMessages(3)).to.eq( true ); serviceNodes.messageCollector.verifyReceivedMessage(2, { - expectedMessageText: newMessageText, + expectedMessageText: thirdMessageText, expectedContentTopic: TestContentTopic, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); - await serviceNodes.confirmMessageLength(3); + // This relies on nwaku not emptying the relay cache + // We received the 3 messages already, what else are checking? + // await serviceNodes.confirmMessageLength(3); }); it("Subscribe and receives messages on 20 topics", async function () { const topicCount = 20; - const td = generateTestData(topicCount, { pubsubTopic: TestPubsubTopic }); + const td = generateTestData(topicCount, TestNetworkConfig); // Subscribe to all 20 topics. for (let i = 0; i < topicCount; i++) { @@ -288,7 +295,7 @@ const runTests = (strictCheckNodes: boolean): void => { serviceNodes.messageCollector.verifyReceivedMessage(index, { expectedContentTopic: topic, expectedMessageText: `Message for Topic ${index + 1}`, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); }); }); @@ -297,7 +304,7 @@ const runTests = (strictCheckNodes: boolean): void => { it.skip("Subscribe to 30 topics in separate streams (30 streams for Filter is limit) at once and receives messages", async function () { this.timeout(100_000); const topicCount = 30; - const td = generateTestData(topicCount, { pubsubTopic: TestPubsubTopic }); + const td = generateTestData(topicCount, TestNetworkConfig); for (let i = 0; i < topicCount; i++) { await waku.filter.subscribe( @@ -321,7 +328,7 @@ const runTests = (strictCheckNodes: boolean): void => { serviceNodes.messageCollector.verifyReceivedMessage(index, { expectedContentTopic: topic, expectedMessageText: `Message for Topic ${index + 1}`, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); }); }); @@ -329,7 +336,7 @@ const runTests = (strictCheckNodes: boolean): void => { it("Subscribe to 100 topics (new limit) at once and receives messages", async function () { this.timeout(100_000); const topicCount = 100; - const td = generateTestData(topicCount, { pubsubTopic: TestPubsubTopic }); + const td = generateTestData(topicCount, TestNetworkConfig); await waku.filter.subscribe( td.decoders, @@ -351,14 +358,14 @@ const runTests = (strictCheckNodes: boolean): void => { serviceNodes.messageCollector.verifyReceivedMessage(index, { expectedContentTopic: topic, expectedMessageText: `Message for Topic ${index + 1}`, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); }); }); it("Error when try to subscribe to more than 101 topics (new limit)", async function () { const topicCount = 101; - const td = generateTestData(topicCount, { pubsubTopic: TestPubsubTopic }); + const td = generateTestData(topicCount, TestNetworkConfig); try { await waku.filter.subscribe( @@ -382,14 +389,10 @@ const runTests = (strictCheckNodes: boolean): void => { it("Overlapping topic subscription", async function () { // Define two sets of test data with overlapping topics. const topicCount1 = 2; - const td1 = generateTestData(topicCount1, { - pubsubTopic: TestPubsubTopic - }); + const td1 = generateTestData(topicCount1, TestNetworkConfig); const topicCount2 = 4; - const td2 = generateTestData(topicCount2, { - pubsubTopic: TestPubsubTopic - }); + const td2 = generateTestData(topicCount2, TestNetworkConfig); await waku.filter.subscribe( td1.decoders, @@ -445,31 +448,25 @@ const runTests = (strictCheckNodes: boolean): void => { serviceNodes.messageCollector.verifyReceivedMessage(0, { expectedMessageText: "M1", expectedContentTopic: TestContentTopic, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); serviceNodes.messageCollector.verifyReceivedMessage(1, { expectedMessageText: "M2", expectedContentTopic: TestContentTopic, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); }); TEST_STRING.forEach((testItem) => { it(`Subscribe to topic containing ${testItem.description} and receive message`, async function () { - const newContentTopic = testItem.value; + const newContentTopic = `/test/0/${testItem.description}/test`; const newEncoder = waku.createEncoder({ contentTopic: newContentTopic, - shardInfo: { - clusterId: ClusterId, - shard: ShardIndex - } + shardId: TestShardIndex }); const newDecoder = waku.createDecoder({ contentTopic: newContentTopic, - shardInfo: { - clusterId: ClusterId, - shard: ShardIndex - } + shardId: TestShardIndex }); await waku.filter.subscribe( @@ -484,7 +481,7 @@ const runTests = (strictCheckNodes: boolean): void => { serviceNodes.messageCollector.verifyReceivedMessage(0, { expectedMessageText: messageText, expectedContentTopic: newContentTopic, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); }); }); @@ -497,11 +494,15 @@ const runTests = (strictCheckNodes: boolean): void => { await waku.lightPush.send(TestEncoder, { payload: utf8ToBytes("M1") }); const newContentTopic = "/test/2/waku-filter/default"; + const newRoutingInfo = createRoutingInfo(TestNetworkConfig, { + contentTopic: newContentTopic + }); + const newEncoder = createEncoder({ contentTopic: newContentTopic, - pubsubTopic: TestPubsubTopic + routingInfo: newRoutingInfo }); - const newDecoder = createDecoder(newContentTopic, TestPubsubTopic); + const newDecoder = createDecoder(newContentTopic, newRoutingInfo); await waku.filter.subscribe( newDecoder, serviceNodes.messageCollector.callback @@ -516,12 +517,12 @@ const runTests = (strictCheckNodes: boolean): void => { serviceNodes.messageCollector.verifyReceivedMessage(0, { expectedMessageText: "M1", expectedContentTopic: TestContentTopic, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); serviceNodes.messageCollector.verifyReceivedMessage(1, { expectedContentTopic: newContentTopic, expectedMessageText: "M2", - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: newRoutingInfo.pubsubTopic }); }); @@ -570,96 +571,127 @@ const runTests = (strictCheckNodes: boolean): void => { expectedContentTopic: TestContentTopic }); }); + }); - it("Subscribe and receive messages from 2 nwaku nodes each with different pubsubtopics", async function () { + describe("Filter subscribe test with static sharding", function () { + this.timeout(100000); + let waku: LightNode; + let serviceNodes: ServiceNodesFleet; + const networkConfig = { clusterId: TestClusterId }; + const routingInfo = createRoutingInfo(networkConfig, { shardId: 3 }); + + beforeEachCustom(this, async () => { + [serviceNodes, waku] = await runMultipleNodes( + this.ctx, + routingInfo, + {}, + strictCheckNodes + ); + }); + + afterEachCustom(this, async () => { + await teardownNodesWithRedundancy(serviceNodes, waku); + }); + }); +}; + +[true, false].map((strictCheckNodes) => runTests(strictCheckNodes)); + +const runTestsStatic = (strictCheckNodes: boolean): void => { + describe(`Waku Filter: Subscribe: Multiple Service Nodes on Static Shard: Strict Check mode: ${strictCheckNodes}`, function () { + this.timeout(100000); + let waku: LightNode; + let serviceNodes: ServiceNodesFleet; + const staticNetworkConfig = { clusterId: 9 }; + const routingInfoShard1 = createRoutingInfo(staticNetworkConfig, { + shardId: 1 + }); + const encoderShard1 = createEncoder({ + contentTopic: TestContentTopic, + routingInfo: routingInfoShard1 + }); + const decoderShard1 = createDecoder(TestContentTopic, routingInfoShard1); + + beforeEachCustom(this, async () => { + [serviceNodes, waku] = await runMultipleNodes( + this.ctx, + routingInfoShard1, + undefined, + strictCheckNodes + ); + }); + + afterEachCustom(this, async () => { + await teardownNodesWithRedundancy(serviceNodes, waku); + }); + + it("Subscribe and receive messages from 2 nwaku nodes each with different static shards", async function () { await waku.filter.subscribe( - TestDecoder, + decoderShard1, serviceNodes.messageCollector.callback ); - // Set up and start a new nwaku node with customPubsubTopic1 + // Set up and start a new nwaku node on different shard const nwaku2 = new ServiceNode(makeLogFileName(this) + "3"); try { - const customContentTopic = "/test/4/waku-filter/default"; - const customDecoder = createDecoder(customContentTopic, { - clusterId: ClusterId, - shard: 4 + const routingInfoShard2 = createRoutingInfo(staticNetworkConfig, { + shardId: 2 }); - const customEncoder = createEncoder({ - contentTopic: customContentTopic, - pubsubTopicShardInfo: { clusterId: ClusterId, shard: 4 } + const contentTopic2 = "/test/4/waku-filter/default"; + const decoderShard2 = createDecoder(contentTopic2, routingInfoShard2); + const encoderShard2 = createEncoder({ + contentTopic: contentTopic2, + routingInfo: routingInfoShard2 }); await nwaku2.start({ filter: true, lightpush: true, relay: true, - clusterId: ClusterId, - shard: [4] + clusterId: TestClusterId, + shard: [2] }); await waku.dial(await nwaku2.getMultiaddrWithId()); await waku.waitForPeers([Protocols.Filter, Protocols.LightPush]); - await nwaku2.ensureSubscriptions([customDecoder.pubsubTopic]); + // TODO + // await nwaku2.ensureSubscriptions([customDecoder.pubsubTopic]); const messageCollector2 = new MessageCollector(); - await waku.filter.subscribe(customDecoder, messageCollector2.callback); + await waku.filter.subscribe(decoderShard2, messageCollector2.callback); - // Making sure that messages are send and reveiced for both subscriptions + // Making sure that messages are send and received for both subscriptions // While loop is done because of https://github.com/waku-org/js-waku/issues/1606 while ( - !(await serviceNodes.messageCollector.waitForMessages(1, { - pubsubTopic: TestDecoder.pubsubTopic - })) || - !(await messageCollector2.waitForMessages(1, { - pubsubTopic: customDecoder.pubsubTopic - })) + !(await serviceNodes.messageCollector.waitForMessages(1)) || + !(await messageCollector2.waitForMessages(1)) ) { - await waku.lightPush.send(TestEncoder, { + await waku.lightPush.send(encoderShard1, { payload: utf8ToBytes("M1") }); - await waku.lightPush.send(customEncoder, { + await waku.lightPush.send(encoderShard2, { payload: utf8ToBytes("M2") }); } serviceNodes.messageCollector.verifyReceivedMessage(0, { - expectedContentTopic: TestDecoder.contentTopic, - expectedPubsubTopic: TestDecoder.pubsubTopic, + expectedContentTopic: encoderShard1.contentTopic, + expectedPubsubTopic: routingInfoShard1.pubsubTopic, expectedMessageText: "M1" }); messageCollector2.verifyReceivedMessage(0, { - expectedContentTopic: customDecoder.contentTopic, - expectedPubsubTopic: customDecoder.pubsubTopic, + expectedContentTopic: encoderShard2.contentTopic, + expectedPubsubTopic: routingInfoShard2.pubsubTopic, expectedMessageText: "M2" }); } catch (e) { await tearDownNodes([nwaku2], []); } }); - - it("Should fail to subscribe with decoder with wrong shard", async function () { - const wrongDecoder = createDecoder(TestDecoder.contentTopic, { - clusterId: ClusterId, - shard: 5 - }); - - // this subscription object is set up with the `customPubsubTopic1` but we're passing it a Decoder with the `customPubsubTopic2` - try { - await waku.filter.subscribe( - wrongDecoder, - serviceNodes.messageCollector.callback - ); - } catch (error) { - expect((error as Error).message).to.include( - `Pubsub topic ${wrongDecoder.pubsubTopic} has not been configured on this instance.` - ); - } - }); }); }; -[true, false].map((strictCheckNodes) => runTests(strictCheckNodes)); +[true, false].map((strictCheckNodes) => runTestsStatic(strictCheckNodes)); diff --git a/packages/tests/tests/filter/unsubscribe.node.spec.ts b/packages/tests/tests/filter/unsubscribe.node.spec.ts index 27816742cd..72b91acd79 100644 --- a/packages/tests/tests/filter/unsubscribe.node.spec.ts +++ b/packages/tests/tests/filter/unsubscribe.node.spec.ts @@ -1,6 +1,7 @@ import { createDecoder, createEncoder } from "@waku/core"; import { type LightNode } from "@waku/interfaces"; import { utf8ToBytes } from "@waku/sdk"; +import { createRoutingInfo } from "@waku/utils"; import { expect } from "chai"; import { @@ -13,13 +14,13 @@ import { } from "../../src/index.js"; import { - ClusterId, messagePayload, messageText, TestContentTopic, TestDecoder, TestEncoder, - TestPubsubTopic + TestNetworkConfig, + TestRoutingInfo } from "./utils.js"; const runTests = (strictCheckNodes: boolean): void => { @@ -30,14 +31,10 @@ const runTests = (strictCheckNodes: boolean): void => { let serviceNodes: ServiceNodesFleet; beforeEachCustom(this, async () => { - [serviceNodes, waku] = await runMultipleNodes( - this.ctx, - { - contentTopics: [TestContentTopic], - clusterId: ClusterId - }, - { filter: true, lightpush: true } - ); + [serviceNodes, waku] = await runMultipleNodes(this.ctx, TestRoutingInfo, { + filter: true, + lightpush: true + }); }); afterEachCustom(this, async () => { @@ -77,12 +74,15 @@ const runTests = (strictCheckNodes: boolean): void => { serviceNodes.messageCollector.callback ); - const newContentTopic = "/test/2/waku-filter"; + const newContentTopic = "/test/2/waku-filter/proto"; + const newRoutingInfo = createRoutingInfo(TestNetworkConfig, { + contentTopic: newContentTopic + }); const newEncoder = createEncoder({ contentTopic: newContentTopic, - pubsubTopic: TestPubsubTopic + routingInfo: newRoutingInfo }); - const newDecoder = createDecoder(newContentTopic, TestPubsubTopic); + const newDecoder = createDecoder(newContentTopic, newRoutingInfo); await waku.filter.subscribe( newDecoder, serviceNodes.messageCollector.callback @@ -103,7 +103,6 @@ const runTests = (strictCheckNodes: boolean): void => { // Check that from 4 messages send 3 were received expect(serviceNodes.messageCollector.count).to.eq(3); - await serviceNodes.confirmMessageLength(4); }); it("Unsubscribe 2 topics - node subscribed to 2 topics", async function () { @@ -112,12 +111,15 @@ const runTests = (strictCheckNodes: boolean): void => { TestDecoder, serviceNodes.messageCollector.callback ); - const newContentTopic = "/test/2/waku-filter"; + const newContentTopic = "/test/2/waku-filter/proto"; + const newRoutingInfo = createRoutingInfo(TestNetworkConfig, { + contentTopic: newContentTopic + }); const newEncoder = createEncoder({ contentTopic: newContentTopic, - pubsubTopic: TestPubsubTopic + routingInfo: newRoutingInfo }); - const newDecoder = createDecoder(newContentTopic, TestPubsubTopic); + const newDecoder = createDecoder(newContentTopic, newRoutingInfo); await waku.filter.subscribe( newDecoder, serviceNodes.messageCollector.callback @@ -140,7 +142,6 @@ const runTests = (strictCheckNodes: boolean): void => { // Check that from 4 messages send 2 were received expect(serviceNodes.messageCollector.count).to.eq(2); - await serviceNodes.confirmMessageLength(4); }); it("Unsubscribe topics the node is not subscribed to", async function () { @@ -159,7 +160,12 @@ const runTests = (strictCheckNodes: boolean): void => { // Unsubscribe from topics that the node is not not subscribed to and send again await waku.filter.unsubscribe( - createDecoder("/test/2/waku-filter", TestDecoder.pubsubTopic) + createDecoder( + "/test/2/waku-filter/proto", + createRoutingInfo(TestNetworkConfig, { + contentTopic: "/test/2/waku-filter/proto" + }) + ) ); await waku.lightPush.send(TestEncoder, { payload: utf8ToBytes("M2") }); expect(await serviceNodes.messageCollector.waitForMessages(2)).to.eq( @@ -174,7 +180,7 @@ const runTests = (strictCheckNodes: boolean): void => { it("Unsubscribe from 100 topics (new limit) at once and receives messages", async function () { this.timeout(100_000); const topicCount = 100; - const td = generateTestData(topicCount, { pubsubTopic: TestPubsubTopic }); + const td = generateTestData(topicCount, TestNetworkConfig); await waku.filter.subscribe( td.decoders, @@ -194,7 +200,7 @@ const runTests = (strictCheckNodes: boolean): void => { serviceNodes.messageCollector.verifyReceivedMessage(index, { expectedContentTopic: topic, expectedMessageText: `Message for Topic ${index + 1}`, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); }); diff --git a/packages/tests/tests/filter/utils.ts b/packages/tests/tests/filter/utils.ts index 01a5220b05..a679f5337a 100644 --- a/packages/tests/tests/filter/utils.ts +++ b/packages/tests/tests/filter/utils.ts @@ -1,142 +1,27 @@ import { createDecoder, createEncoder } from "@waku/core"; import { - CreateNodeOptions, - DefaultNetworkConfig, - IWaku, - LightNode, - NetworkConfig, - Protocols -} from "@waku/interfaces"; -import { createLightNode } from "@waku/sdk"; -import { - contentTopicToPubsubTopic, contentTopicToShardIndex, - derivePubsubTopicsFromNetworkConfig, + createRoutingInfo, Logger } from "@waku/utils"; import { utf8ToBytes } from "@waku/utils/bytes"; -import { Context } from "mocha"; -import pRetry from "p-retry"; - -import { - NOISE_KEY_1, - ServiceNodesFleet, - waitForConnections -} from "../../src/index.js"; // Constants for test configuration. export const log = new Logger("test:filter"); export const TestContentTopic = "/test/1/waku-filter/default"; -export const ClusterId = 2; -export const ShardIndex = contentTopicToShardIndex(TestContentTopic); -export const TestShardInfo = { - contentTopics: [TestContentTopic], - clusterId: ClusterId +export const TestClusterId = 2; +export const TestShardIndex = contentTopicToShardIndex(TestContentTopic); +export const TestNetworkConfig = { + clusterId: TestClusterId, + numShardsInCluster: 8 }; -export const TestPubsubTopic = contentTopicToPubsubTopic( - TestContentTopic, - ClusterId -); +export const TestRoutingInfo = createRoutingInfo(TestNetworkConfig, { + contentTopic: TestContentTopic +}); export const TestEncoder = createEncoder({ contentTopic: TestContentTopic, - pubsubTopic: TestPubsubTopic + routingInfo: TestRoutingInfo }); -export const TestDecoder = createDecoder(TestContentTopic, TestPubsubTopic); +export const TestDecoder = createDecoder(TestContentTopic, TestRoutingInfo); export const messageText = "Filtering works!"; export const messagePayload = { payload: utf8ToBytes(messageText) }; - -export async function runMultipleNodes( - context: Context, - networkConfig: NetworkConfig = DefaultNetworkConfig, - strictChecking: boolean = false, - numServiceNodes = 3, - withoutFilter = false -): Promise<[ServiceNodesFleet, LightNode]> { - const pubsubTopics = derivePubsubTopicsFromNetworkConfig(networkConfig); - // create numServiceNodes nodes - const serviceNodes = await ServiceNodesFleet.createAndRun( - context, - numServiceNodes, - strictChecking, - networkConfig, - undefined, - withoutFilter - ); - - const wakuOptions: CreateNodeOptions = { - staticNoiseKey: NOISE_KEY_1, - libp2p: { - addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } - } - }; - - log.info("Starting js waku node with :", JSON.stringify(wakuOptions)); - let waku: LightNode | undefined; - try { - waku = await createLightNode(wakuOptions); - await waku.start(); - } catch (error) { - log.error("jswaku node failed to start:", error); - } - - if (!waku) { - throw new Error("Failed to initialize waku"); - } - - for (const node of serviceNodes.nodes) { - await waku.dial(await node.getMultiaddrWithId()); - await waku.waitForPeers([Protocols.Filter, Protocols.LightPush]); - await node.ensureSubscriptions(pubsubTopics); - - const wakuConnections = waku.libp2p.getConnections(); - - if (wakuConnections.length < 1) { - throw new Error(`Expected at least 1 connection for js-waku.`); - } - - await node.waitForLog(waku.libp2p.peerId.toString(), 100); - } - - await waitForConnections(numServiceNodes, waku); - - return [serviceNodes, waku]; -} - -export async function teardownNodesWithRedundancy( - serviceNodes: ServiceNodesFleet, - wakuNodes: IWaku | IWaku[] -): Promise { - const wNodes = Array.isArray(wakuNodes) ? wakuNodes : [wakuNodes]; - - const stopNwakuNodes = serviceNodes.nodes.map(async (node) => { - await pRetry( - async () => { - try { - await node.stop(); - } catch (error) { - log.error("Service Node failed to stop:", error); - throw error; - } - }, - { retries: 3 } - ); - }); - - const stopWakuNodes = wNodes.map(async (waku) => { - if (waku) { - await pRetry( - async () => { - try { - await waku.stop(); - } catch (error) { - log.error("Waku failed to stop:", error); - throw error; - } - }, - { retries: 3 } - ); - } - }); - - await Promise.all([...stopNwakuNodes, ...stopWakuNodes]); -} diff --git a/packages/tests/tests/light-push/index.node.spec.ts b/packages/tests/tests/light-push/index.node.spec.ts index b57429c410..d750c6c77f 100644 --- a/packages/tests/tests/light-push/index.node.spec.ts +++ b/packages/tests/tests/light-push/index.node.spec.ts @@ -14,14 +14,11 @@ import { } from "../../src/index.js"; import { - ClusterId, messagePayload, messageText, - ShardIndex, TestContentTopic, TestEncoder, - TestPubsubTopic, - TestShardInfo + TestRoutingInfo } from "./utils.js"; const runTests = (strictNodeCheck: boolean): void => { @@ -35,7 +32,7 @@ const runTests = (strictNodeCheck: boolean): void => { beforeEachCustom(this, async () => { [serviceNodes, waku] = await runMultipleNodes( this.ctx, - TestShardInfo, + TestRoutingInfo, { lightpush: true, filter: true }, strictNodeCheck, numServiceNodes, @@ -54,20 +51,18 @@ const runTests = (strictNodeCheck: boolean): void => { }); expect(pushResponse.successes.length).to.eq(numServiceNodes); - expect( - await serviceNodes.messageCollector.waitForMessages(1, { - pubsubTopic: TestPubsubTopic - }) - ).to.eq(true); + expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq( + true + ); serviceNodes.messageCollector.verifyReceivedMessage(0, { expectedMessageText: testItem.value, expectedContentTopic: TestContentTopic, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); }); }); - // TODO: skiped till https://github.com/waku-org/nwaku/issues/3369 resolved + // TODO: skipped till https://github.com/waku-org/nwaku/issues/3369 resolved it.skip("Push 30 different messages", async function () { const generateMessageText = (index: number): string => `M${index}`; @@ -79,17 +74,15 @@ const runTests = (strictNodeCheck: boolean): void => { expect(pushResponse.successes.length).to.eq(numServiceNodes); } - expect( - await serviceNodes.messageCollector.waitForMessages(30, { - pubsubTopic: TestPubsubTopic - }) - ).to.eq(true); + expect(await serviceNodes.messageCollector.waitForMessages(30)).to.eq( + true + ); for (let i = 0; i < 30; i++) { serviceNodes.messageCollector.verifyReceivedMessage(i, { expectedMessageText: generateMessageText(i), expectedContentTopic: TestContentTopic, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); } }); @@ -105,21 +98,16 @@ const runTests = (strictNodeCheck: boolean): void => { ProtocolError.EMPTY_PAYLOAD ); - expect( - await serviceNodes.messageCollector.waitForMessages(1, { - pubsubTopic: TestPubsubTopic - }) - ).to.eq(false); + expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq( + false + ); }); - TEST_STRING.forEach((testItem) => { + [{ description: "short", value: "hi" }].forEach((testItem) => { it(`Push message with content topic containing ${testItem.description}`, async function () { + const contentTopic = `/test/1/${testItem.value}/proto`; const customEncoder = waku.createEncoder({ - contentTopic: testItem.value, - shardInfo: { - clusterId: ClusterId, - shard: ShardIndex - } + contentTopic }); const pushResponse = await waku.lightPush.send( customEncoder, @@ -129,13 +117,13 @@ const runTests = (strictNodeCheck: boolean): void => { expect( await serviceNodes.messageCollector.waitForMessages(1, { - pubsubTopic: TestPubsubTopic + contentTopic }) ).to.eq(true); serviceNodes.messageCollector.verifyReceivedMessage(0, { expectedMessageText: messageText, - expectedContentTopic: testItem.value, - expectedPubsubTopic: TestPubsubTopic + expectedContentTopic: contentTopic, + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); }); }); @@ -144,7 +132,7 @@ const runTests = (strictNodeCheck: boolean): void => { const customTestEncoder = createEncoder({ contentTopic: TestContentTopic, metaSetter: () => new Uint8Array(10), - pubsubTopic: TestPubsubTopic + routingInfo: TestRoutingInfo }); const pushResponse = await waku.lightPush.send( @@ -153,22 +141,20 @@ const runTests = (strictNodeCheck: boolean): void => { ); expect(pushResponse.successes.length).to.eq(numServiceNodes); - expect( - await serviceNodes.messageCollector.waitForMessages(1, { - pubsubTopic: TestPubsubTopic - }) - ).to.eq(true); + expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq( + true + ); serviceNodes.messageCollector.verifyReceivedMessage(0, { expectedMessageText: messageText, expectedContentTopic: TestContentTopic, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); }); it("Fails to push message with large meta", async function () { const customTestEncoder = createEncoder({ contentTopic: TestContentTopic, - pubsubTopic: TestPubsubTopic, + routingInfo: TestRoutingInfo, metaSetter: () => new Uint8Array(105024) // see the note below *** }); @@ -176,7 +162,7 @@ const runTests = (strictNodeCheck: boolean): void => { // `nwaku` establishes the max lightpush msg size as `const MaxRpcSize* = MaxWakuMessageSize + 64 * 1024` // see: https://github.com/waku-org/nwaku/blob/07beea02095035f4f4c234ec2dec1f365e6955b8/waku/waku_lightpush/rpc_codec.nim#L15 // In the PR https://github.com/waku-org/nwaku/pull/2298 we reduced the MaxWakuMessageSize - // from 1MiB to 150KiB. Therefore, the 105024 number comes from substracting ( 1*2^20 - 150*2^10 ) + // from 1MiB to 150KiB. Therefore, the 105024 number comes from subtracting ( 1*2^20 - 150*2^10 ) // to the original 10^6 that this test had when MaxWakuMessageSize == 1*2^20 const pushResponse = await waku.lightPush.send( @@ -188,11 +174,9 @@ const runTests = (strictNodeCheck: boolean): void => { expect(pushResponse.failures?.map((failure) => failure.error)).to.include( ProtocolError.REMOTE_PEER_REJECTED ); - expect( - await serviceNodes.messageCollector.waitForMessages(1, { - pubsubTopic: TestPubsubTopic - }) - ).to.eq(false); + expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq( + false + ); }); it("Push message with rate limit", async function () { @@ -212,15 +196,13 @@ const runTests = (strictNodeCheck: boolean): void => { }); expect(pushResponse.successes.length).to.eq(numServiceNodes); - expect( - await serviceNodes.messageCollector.waitForMessages(1, { - pubsubTopic: TestPubsubTopic - }) - ).to.eq(true); + expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq( + true + ); serviceNodes.messageCollector.verifyReceivedMessage(0, { expectedMessageText: messageText, expectedContentTopic: TestContentTopic, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); }); @@ -236,16 +218,14 @@ const runTests = (strictNodeCheck: boolean): void => { }); expect(pushResponse.successes.length).to.eq(numServiceNodes); - expect( - await serviceNodes.messageCollector.waitForMessages(1, { - pubsubTopic: TestPubsubTopic - }) - ).to.eq(true); + expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq( + true + ); serviceNodes.messageCollector.verifyReceivedMessage(0, { expectedMessageText: messageText, expectedTimestamp: testItem, expectedContentTopic: TestContentTopic, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }); }); }); @@ -268,11 +248,9 @@ const runTests = (strictNodeCheck: boolean): void => { expect(pushResponse.failures?.map((failure) => failure.error)).to.include( ProtocolError.SIZE_TOO_BIG ); - expect( - await serviceNodes.messageCollector.waitForMessages(1, { - pubsubTopic: TestPubsubTopic - }) - ).to.eq(false); + expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq( + false + ); }); }); }; diff --git a/packages/tests/tests/light-push/multiple_pubsub.node.spec.ts b/packages/tests/tests/light-push/multiple_pubsub.node.spec.ts index ac82a0cab5..a446f3b56b 100644 --- a/packages/tests/tests/light-push/multiple_pubsub.node.spec.ts +++ b/packages/tests/tests/light-push/multiple_pubsub.node.spec.ts @@ -1,141 +1,144 @@ -import { createEncoder } from "@waku/core"; -import { LightNode, Protocols } from "@waku/interfaces"; -import { contentTopicToPubsubTopic } from "@waku/utils"; -import { utf8ToBytes } from "@waku/utils/bytes"; -import { expect } from "chai"; +// TODO: This test is useless because the content topics all start +// with `/test/` meaning they are in the same shard -import { - afterEachCustom, - beforeEachCustom, - makeLogFileName, - MessageCollector, - runMultipleNodes, - ServiceNode, - ServiceNodesFleet, - tearDownNodes, - teardownNodesWithRedundancy -} from "../../src/index.js"; - -import { ClusterId, TestEncoder } from "./utils.js"; - -describe("Waku Light Push (Autosharding): Multiple PubsubTopics", function () { - this.timeout(30000); - const numServiceNodes = 2; - - let waku: LightNode; - let serviceNodes: ServiceNodesFleet; - - const customEncoder2 = createEncoder({ - contentTopic: "/test/2/waku-light-push/utf8", - pubsubTopic: contentTopicToPubsubTopic( - "/test/2/waku-light-push/utf8", - ClusterId - ) - }); - - beforeEachCustom(this, async () => { - [serviceNodes, waku] = await runMultipleNodes( - this.ctx, - { - clusterId: ClusterId, - contentTopics: [TestEncoder.contentTopic, customEncoder2.contentTopic] - }, - { lightpush: true, filter: true }, - false, - numServiceNodes, - false - ); - }); - - afterEachCustom(this, async () => { - await teardownNodesWithRedundancy(serviceNodes, waku); - }); - - it("Subscribe and receive messages on 2 different pubsubtopics", async function () { - const pushResponse1 = await waku.lightPush.send(TestEncoder, { - payload: utf8ToBytes("M1") - }); - const pushResponse2 = await waku.lightPush.send(customEncoder2, { - payload: utf8ToBytes("M2") - }); - - expect(pushResponse1.successes.length).to.eq(numServiceNodes); - expect(pushResponse2.successes.length).to.eq(numServiceNodes); - - const messageCollector1 = new MessageCollector(serviceNodes.nodes[0]); - const messageCollector2 = new MessageCollector(serviceNodes.nodes[1]); - - expect( - await messageCollector1.waitForMessages(1, { - pubsubTopic: TestEncoder.pubsubTopic - }) - ).to.eq(true); - - expect( - await messageCollector2.waitForMessages(1, { - pubsubTopic: customEncoder2.pubsubTopic - }) - ).to.eq(true); - - messageCollector1.verifyReceivedMessage(0, { - expectedMessageText: "M1", - expectedContentTopic: TestEncoder.contentTopic, - expectedPubsubTopic: TestEncoder.pubsubTopic - }); - - messageCollector2.verifyReceivedMessage(0, { - expectedMessageText: "M2", - expectedContentTopic: customEncoder2.contentTopic, - expectedPubsubTopic: customEncoder2.pubsubTopic - }); - }); - - it("Light push messages to 2 nwaku nodes each with different pubsubtopics", async function () { - // Set up and start a new nwaku node with Default PubsubTopic - const nwaku2 = new ServiceNode(makeLogFileName(this) + "3"); - - try { - await nwaku2.start({ - filter: true, - lightpush: true, - relay: true, - clusterId: ClusterId, - shard: [2] - }); - await nwaku2.ensureSubscriptionsAutosharding([ - customEncoder2.pubsubTopic - ]); - await waku.dial(await nwaku2.getMultiaddrWithId()); - await waku.waitForPeers([Protocols.LightPush]); - - const messageCollector2 = new MessageCollector(nwaku2); - - await waku.lightPush.send(TestEncoder, { - payload: utf8ToBytes("M1") - }); - await waku.lightPush.send(customEncoder2, { - payload: utf8ToBytes("M2") - }); - - await serviceNodes.messageCollector.waitForMessages(1, { - pubsubTopic: TestEncoder.pubsubTopic - }); - await messageCollector2.waitForMessagesAutosharding(1, { - contentTopic: customEncoder2.contentTopic - }); - - serviceNodes.messageCollector.verifyReceivedMessage(0, { - expectedMessageText: "M1", - expectedContentTopic: TestEncoder.contentTopic, - expectedPubsubTopic: TestEncoder.pubsubTopic - }); - messageCollector2.verifyReceivedMessage(0, { - expectedMessageText: "M2", - expectedContentTopic: customEncoder2.contentTopic, - expectedPubsubTopic: customEncoder2.pubsubTopic - }); - } catch (e) { - await tearDownNodes([nwaku2], []); - } - }); -}); +// import { createEncoder } from "@waku/core"; +// import { LightNode, Protocols } from "@waku/interfaces"; +// import { contentTopicToPubsubTopic } from "@waku/utils"; +// import { utf8ToBytes } from "@waku/utils/bytes"; +// import { expect } from "chai"; +// +// import { +// afterEachCustom, +// beforeEachCustom, +// makeLogFileName, +// MessageCollector, +// runMultipleNodes, +// ServiceNode, +// ServiceNodesFleet, +// tearDownNodes, +// teardownNodesWithRedundancy +// } from "../../src/index.js"; +// +// import { TestClusterId, TestEncoder } from "./utils.js"; +// +// describe("Waku Light Push (Autosharding): Multiple Shards", function () { +// this.timeout(30000); +// const numServiceNodes = 2; +// +// let waku: LightNode; +// let serviceNodes: ServiceNodesFleet; +// +// const customEncoder2 = createEncoder({ +// contentTopic: "/test/2/waku-light-push/utf8", +// pubsubTopic: contentTopicToPubsubTopic( +// "/test/2/waku-light-push/utf8", +// TestClusterId +// ) +// }); +// +// beforeEachCustom(this, async () => { +// [serviceNodes, waku] = await runMultipleNodes( +// this.ctx, +// { +// clusterId: TestClusterId, +// contentTopics: [TestEncoder.contentTopic, customEncoder2.contentTopic] +// }, +// { lightpush: true, filter: true }, +// false, +// numServiceNodes, +// false +// ); +// }); +// +// afterEachCustom(this, async () => { +// await teardownNodesWithRedundancy(serviceNodes, waku); +// }); +// +// it("Subscribe and receive messages on 2 different pubsubtopics", async function () { +// const pushResponse1 = await waku.lightPush.send(TestEncoder, { +// payload: utf8ToBytes("M1") +// }); +// const pushResponse2 = await waku.lightPush.send(customEncoder2, { +// payload: utf8ToBytes("M2") +// }); +// +// expect(pushResponse1.successes.length).to.eq(numServiceNodes); +// expect(pushResponse2.successes.length).to.eq(numServiceNodes); +// +// const messageCollector1 = new MessageCollector(serviceNodes.nodes[0]); +// const messageCollector2 = new MessageCollector(serviceNodes.nodes[1]); +// +// expect( +// await messageCollector1.waitForMessages(1, { +// pubsubTopic: TestEncoder.pubsubTopic +// }) +// ).to.eq(true); +// +// expect( +// await messageCollector2.waitForMessages(1, { +// pubsubTopic: customEncoder2.pubsubTopic +// }) +// ).to.eq(true); +// +// messageCollector1.verifyReceivedMessage(0, { +// expectedMessageText: "M1", +// expectedContentTopic: TestEncoder.contentTopic, +// expectedPubsubTopic: TestEncoder.pubsubTopic +// }); +// +// messageCollector2.verifyReceivedMessage(0, { +// expectedMessageText: "M2", +// expectedContentTopic: customEncoder2.contentTopic, +// expectedPubsubTopic: customEncoder2.pubsubTopic +// }); +// }); +// +// it("Light push messages to 2 nwaku nodes each with different pubsubtopics", async function () { +// // Set up and start a new nwaku node with Default PubsubTopic +// const nwaku2 = new ServiceNode(makeLogFileName(this) + "3"); +// +// try { +// await nwaku2.start({ +// filter: true, +// lightpush: true, +// relay: true, +// clusterId: TestClusterId, +// shard: [2] +// }); +// await nwaku2.ensureSubscriptionsAutosharding([ +// customEncoder2.pubsubTopic +// ]); +// await waku.dial(await nwaku2.getMultiaddrWithId()); +// await waku.waitForPeers([Protocols.LightPush]); +// +// const messageCollector2 = new MessageCollector(nwaku2); +// +// await waku.lightPush.send(TestEncoder, { +// payload: utf8ToBytes("M1") +// }); +// await waku.lightPush.send(customEncoder2, { +// payload: utf8ToBytes("M2") +// }); +// +// await serviceNodes.messageCollector.waitForMessages(1, { +// pubsubTopic: TestEncoder.pubsubTopic +// }); +// await messageCollector2.waitForMessagesAutosharding(1, { +// contentTopic: customEncoder2.contentTopic +// }); +// +// serviceNodes.messageCollector.verifyReceivedMessage(0, { +// expectedMessageText: "M1", +// expectedContentTopic: TestEncoder.contentTopic, +// expectedPubsubTopic: TestEncoder.pubsubTopic +// }); +// messageCollector2.verifyReceivedMessage(0, { +// expectedMessageText: "M2", +// expectedContentTopic: customEncoder2.contentTopic, +// expectedPubsubTopic: customEncoder2.pubsubTopic +// }); +// } catch (e) { +// await tearDownNodes([nwaku2], []); +// } +// }); +// }); diff --git a/packages/tests/tests/light-push/utils.ts b/packages/tests/tests/light-push/utils.ts index 8538d48ffa..85db44b426 100644 --- a/packages/tests/tests/light-push/utils.ts +++ b/packages/tests/tests/light-push/utils.ts @@ -1,43 +1,22 @@ import { createEncoder } from "@waku/core"; -import { LightNode, NetworkConfig, Protocols } from "@waku/interfaces"; import { utf8ToBytes } from "@waku/sdk"; -import { createLightNode } from "@waku/sdk"; -import { - contentTopicToPubsubTopic, - contentTopicToShardIndex, - Logger -} from "@waku/utils"; -import { Context } from "mocha"; - -import { runNodes as runNodesBuilder, ServiceNode } from "../../src/index.js"; +import { createRoutingInfo, Logger } from "@waku/utils"; // Constants for test configuration. export const log = new Logger("test:lightpush"); export const TestContentTopic = "/test/1/waku-light-push/utf8"; -export const ClusterId = 3; -export const ShardIndex = contentTopicToShardIndex(TestContentTopic); -export const TestPubsubTopic = contentTopicToPubsubTopic( - TestContentTopic, - ClusterId -); -export const TestShardInfo = { - contentTopics: [TestContentTopic], - clusterId: ClusterId +export const TestClusterId = 3; +export const TestNumShardsInCluster = 8; +export const TestNetworkConfig = { + clusterId: TestClusterId, + numShardsInCluster: TestNumShardsInCluster }; +export const TestRoutingInfo = createRoutingInfo(TestNetworkConfig, { + contentTopic: TestContentTopic +}); export const TestEncoder = createEncoder({ contentTopic: TestContentTopic, - pubsubTopic: TestPubsubTopic + routingInfo: TestRoutingInfo }); export const messageText = "Light Push works!"; export const messagePayload = { payload: utf8ToBytes(messageText) }; - -export const runNodes = ( - context: Context, - shardInfo: NetworkConfig -): Promise<[ServiceNode, LightNode]> => - runNodesBuilder({ - context, - createNode: createLightNode, - protocols: [Protocols.LightPush, Protocols.Filter], - networkConfig: shardInfo - }); diff --git a/packages/tests/tests/metadata.spec.ts b/packages/tests/tests/metadata.spec.ts index ee954f09de..450621b0e6 100644 --- a/packages/tests/tests/metadata.spec.ts +++ b/packages/tests/tests/metadata.spec.ts @@ -1,5 +1,5 @@ import { MetadataCodec } from "@waku/core"; -import type { LightNode, ShardInfo } from "@waku/interfaces"; +import type { LightNode } from "@waku/interfaces"; import { createLightNode } from "@waku/sdk"; import { decodeRelayShard } from "@waku/utils"; import chai, { expect } from "chai"; @@ -29,25 +29,27 @@ describe("Metadata Protocol", function () { await tearDownNodes([nwaku1], waku); }); - describe("connections", function () { - it("same cluster, same shard: nodes connect", async function () { - const shardInfo: ShardInfo = { - clusterId: 2, - shards: [1] - }; + describe("static sharding", function () { + it("same cluster, static sharding: nodes connect", async function () { + const clusterId = 2; + const shards = [1]; + const numShardsInCluster = 8; await nwaku1.start({ relay: true, discv5Discovery: true, peerExchange: true, - clusterId: shardInfo.clusterId, - shard: shardInfo.shards + clusterId, + shard: shards, + numShardsInNetwork: numShardsInCluster }); const nwaku1Ma = await nwaku1.getMultiaddrWithId(); const nwaku1PeerId = await nwaku1.getPeerId(); - waku = await createLightNode({ networkConfig: shardInfo }); + waku = await createLightNode({ + networkConfig: { clusterId, numShardsInCluster } + }); await waku.start(); await waku.libp2p.dialProtocol(nwaku1Ma, MetadataCodec); @@ -65,82 +67,33 @@ describe("Metadata Protocol", function () { } expect(shardInfoRes).to.not.be.undefined; - expect(shardInfoRes.clusterId).to.equal(shardInfo.clusterId); - expect(shardInfoRes.shards).to.include.members(shardInfo.shards); + expect(shardInfoRes.clusterId).to.equal(clusterId); + expect(shardInfoRes.shards).to.include.members(shards); const activeConnections = waku.libp2p.getConnections(); expect(activeConnections.length).to.equal(1); }); - it("same cluster, different shard: nodes connect", async function () { - const shardInfo1: ShardInfo = { - clusterId: 2, - shards: [1] - }; - - const shardInfo2: ShardInfo = { - clusterId: 2, - shards: [2] - }; + it("different cluster: nodes don't connect", async function () { + const clusterIdNwaku = 2; + const custerIdJsWaku = 3; + const shards = [1]; + const numShardsInCluster = 8; await nwaku1.start({ relay: true, discv5Discovery: true, peerExchange: true, - clusterId: shardInfo1.clusterId, - shard: shardInfo1.shards - }); - - const nwaku1Ma = await nwaku1.getMultiaddrWithId(); - const nwaku1PeerId = await nwaku1.getPeerId(); - - waku = await createLightNode({ networkConfig: shardInfo2 }); - await waku.start(); - await waku.libp2p.dialProtocol(nwaku1Ma, MetadataCodec); - - if (!waku.libp2p.services.metadata) { - expect(waku.libp2p.services.metadata).to.not.be.undefined; - return; - } - - const { error, shardInfo: shardInfoRes } = - await waku.libp2p.services.metadata.query(nwaku1PeerId); - - if (error) { - expect(error).to.be.null; - return; - } - - expect(shardInfoRes).to.not.be.undefined; - expect(shardInfoRes.clusterId).to.equal(shardInfo1.clusterId); - expect(shardInfoRes.shards).to.include.members(shardInfo1.shards); - - const activeConnections = waku.libp2p.getConnections(); - expect(activeConnections.length).to.equal(1); - }); - - it("different cluster, same shard: nodes don't connect", async function () { - const shardInfo1: ShardInfo = { - clusterId: 2, - shards: [1] - }; - - const shardInfo2: ShardInfo = { - clusterId: 3, - shards: [1] - }; - - await nwaku1.start({ - relay: true, - discv5Discovery: true, - peerExchange: true, - clusterId: shardInfo1.clusterId, - shard: shardInfo1.shards + clusterId: clusterIdNwaku, + shard: shards, + numShardsInNetwork: numShardsInCluster }); const nwaku1Ma = await nwaku1.getMultiaddrWithId(); - waku = await createLightNode({ networkConfig: shardInfo2 }); + waku = await createLightNode({ + networkConfig: { clusterId: custerIdJsWaku, numShardsInCluster } + }); await waku.start(); await waku.libp2p.dialProtocol(nwaku1Ma, MetadataCodec); @@ -157,28 +110,151 @@ describe("Metadata Protocol", function () { expect(waku.libp2p.getConnections().length).to.equal(0); }); - it("different cluster, different shard: nodes don't connect", async function () { - const shardInfo1: ShardInfo = { - clusterId: 2, - shards: [1] - }; - - const shardInfo2: ShardInfo = { - clusterId: 3, - shards: [2] - }; + it("PeerStore has remote peer's shard info after successful connection", async function () { + const clusterId = 2; + const shards = [1]; + const numShardsInCluster = 8; await nwaku1.start({ relay: true, discv5Discovery: true, peerExchange: true, - clusterId: shardInfo1.clusterId, - shard: shardInfo1.shards + clusterId, + shard: shards, + numShardsInNetwork: numShardsInCluster + }); + + const nwaku1Ma = await nwaku1.getMultiaddrWithId(); + const nwaku1PeerId = await nwaku1.getPeerId(); + + waku = await createLightNode({ + networkConfig: { clusterId, numShardsInCluster } + }); + await waku.start(); + await waku.libp2p.dialProtocol(nwaku1Ma, MetadataCodec); + + // delay to ensure the connection is estabilished and shardInfo is updated + await delay(500); + + const encodedShardInfo = ( + await waku.libp2p.peerStore.get(nwaku1PeerId) + ).metadata.get("shardInfo"); + expect(encodedShardInfo).to.not.be.undefined; + + const metadataShardInfo = decodeRelayShard(encodedShardInfo!); + expect(metadataShardInfo).not.be.undefined; + + expect(metadataShardInfo!.clusterId).to.eq(clusterId); + expect(metadataShardInfo.shards).to.include.members(shards); + }); + + it("receiving a ping from a peer does not overwrite shard info", async function () { + const clusterId = 2; + const shards = [1]; + const numShardsInCluster = 8; + + await nwaku1.start({ + relay: true, + discv5Discovery: true, + peerExchange: true, + clusterId, + shard: shards + }); + + const nwaku1Ma = await nwaku1.getMultiaddrWithId(); + const nwaku1PeerId = await nwaku1.getPeerId(); + + waku = await createLightNode({ + networkConfig: { + clusterId, + numShardsInCluster + }, + connectionManager: { + pingKeepAlive: 1 + } + }); + await waku.start(); + await waku.libp2p.dialProtocol(nwaku1Ma, MetadataCodec); + + // delay to ensure the connection is estabilished, shardInfo is updated, and there is a ping + await delay(1500); + + const metadata = (await waku.libp2p.peerStore.get(nwaku1PeerId)).metadata; + expect(metadata.get("shardInfo")).to.not.be.undefined; + + const pingInfo = metadata.get("ping"); + expect(pingInfo).to.not.be.undefined; + }); + }); + describe("auto sharding", function () { + it("same cluster: nodes connect", async function () { + const clusterId = 2; + const contentTopic = "/foo/1/bar/proto"; + const numShardsInCluster = 0; + + await nwaku1.start({ + relay: true, + discv5Discovery: true, + peerExchange: true, + clusterId, + contentTopic: [contentTopic], + numShardsInNetwork: numShardsInCluster + }); + + const nwaku1Ma = await nwaku1.getMultiaddrWithId(); + const nwaku1PeerId = await nwaku1.getPeerId(); + + waku = await createLightNode({ + networkConfig: { clusterId, numShardsInCluster } + }); + await waku.start(); + await waku.libp2p.dialProtocol(nwaku1Ma, MetadataCodec); + + if (!waku.libp2p.services.metadata) { + expect(waku.libp2p.services.metadata).to.not.be.undefined; + return; + } + + const { error, shardInfo: shardInfoRes } = + await waku.libp2p.services.metadata.query(nwaku1PeerId); + + if (error) { + expect(error).to.be.null; + return; + } + + expect(shardInfoRes).to.not.be.undefined; + expect(shardInfoRes.clusterId).to.equal(clusterId); + // TODO: calculate shards from content topics + //expect(shardInfoRes.shards).to.include.members(shards); + + const activeConnections = waku.libp2p.getConnections(); + expect(activeConnections.length).to.equal(1); + }); + + it("different cluster: nodes don't connect", async function () { + const clusterIdNwaku = 2; + const clusterIdJSWaku = 3; + const contentTopic = ["/foo/1/bar/proto"]; + const numShardsInCluster = 0; + + await nwaku1.start({ + relay: true, + discv5Discovery: true, + peerExchange: true, + clusterId: clusterIdNwaku, + contentTopic, + numShardsInNetwork: numShardsInCluster }); const nwaku1Ma = await nwaku1.getMultiaddrWithId(); - waku = await createLightNode({ networkConfig: shardInfo2 }); + waku = await createLightNode({ + networkConfig: { + clusterId: clusterIdJSWaku, + numShardsInCluster + } + }); await waku.start(); await waku.libp2p.dialProtocol(nwaku1Ma, MetadataCodec); @@ -195,77 +271,81 @@ describe("Metadata Protocol", function () { expect(waku.libp2p.getConnections().length).to.equal(0); }); - }); - it("PeerStore has remote peer's shard info after successful connection", async function () { - const shardInfo: ShardInfo = { - clusterId: 2, - shards: [1] - }; + it("PeerStore has remote peer's shard info after successful connection", async function () { + const clusterId = 2; + const contentTopic = ["/foo/1/bar/proto"]; + const numShardsInCluster = 0; - await nwaku1.start({ - relay: true, - discv5Discovery: true, - peerExchange: true, - clusterId: shardInfo.clusterId, - shard: shardInfo.shards + await nwaku1.start({ + relay: true, + discv5Discovery: true, + peerExchange: true, + clusterId, + contentTopic + }); + + const nwaku1Ma = await nwaku1.getMultiaddrWithId(); + const nwaku1PeerId = await nwaku1.getPeerId(); + + waku = await createLightNode({ + networkConfig: { clusterId, numShardsInCluster } + }); + await waku.start(); + await waku.libp2p.dialProtocol(nwaku1Ma, MetadataCodec); + + // delay to ensure the connection is estabilished and shardInfo is updated + await delay(500); + + const encodedShardInfo = ( + await waku.libp2p.peerStore.get(nwaku1PeerId) + ).metadata.get("shardInfo"); + expect(encodedShardInfo).to.not.be.undefined; + + const metadataShardInfo = decodeRelayShard(encodedShardInfo!); + expect(metadataShardInfo).not.be.undefined; + + expect(metadataShardInfo!.clusterId).to.eq(clusterId); + // TODO derive shard from content topic + // expect(metadataShardInfo.shards).to.include.members(shards); }); - const nwaku1Ma = await nwaku1.getMultiaddrWithId(); - const nwaku1PeerId = await nwaku1.getPeerId(); + it("receiving a ping from a peer does not overwrite shard info", async function () { + const clusterId = 2; + const contentTopic = ["/foo/1/bar/proto"]; + const numShardsInCluster = 0; - waku = await createLightNode({ networkConfig: shardInfo }); - await waku.start(); - await waku.libp2p.dialProtocol(nwaku1Ma, MetadataCodec); + await nwaku1.start({ + relay: true, + discv5Discovery: true, + peerExchange: true, + clusterId, + contentTopic + }); - // delay to ensure the connection is estabilished and shardInfo is updated - await delay(500); + const nwaku1Ma = await nwaku1.getMultiaddrWithId(); + const nwaku1PeerId = await nwaku1.getPeerId(); - const encodedShardInfo = ( - await waku.libp2p.peerStore.get(nwaku1PeerId) - ).metadata.get("shardInfo"); - expect(encodedShardInfo).to.not.be.undefined; + waku = await createLightNode({ + networkConfig: { + clusterId, + numShardsInCluster + }, + connectionManager: { + pingKeepAlive: 1 + } + }); + await waku.start(); + await waku.libp2p.dialProtocol(nwaku1Ma, MetadataCodec); - const metadataShardInfo = decodeRelayShard(encodedShardInfo!); - expect(metadataShardInfo).not.be.undefined; + // delay to ensure the connection is estabilished, shardInfo is updated, and there is a ping + await delay(1500); - expect(metadataShardInfo!.clusterId).to.eq(shardInfo.clusterId); - expect(metadataShardInfo.shards).to.include.members(shardInfo.shards); - }); + const metadata = (await waku.libp2p.peerStore.get(nwaku1PeerId)).metadata; + expect(metadata.get("shardInfo")).to.not.be.undefined; - it("receiving a ping from a peer does not overwrite shard info", async function () { - const shardInfo: ShardInfo = { - clusterId: 2, - shards: [1] - }; - - await nwaku1.start({ - relay: true, - discv5Discovery: true, - peerExchange: true, - clusterId: shardInfo.clusterId, - shard: shardInfo.shards + const pingInfo = metadata.get("ping"); + expect(pingInfo).to.not.be.undefined; }); - - const nwaku1Ma = await nwaku1.getMultiaddrWithId(); - const nwaku1PeerId = await nwaku1.getPeerId(); - - waku = await createLightNode({ - networkConfig: shardInfo, - connectionManager: { - pingKeepAlive: 1 - } - }); - await waku.start(); - await waku.libp2p.dialProtocol(nwaku1Ma, MetadataCodec); - - // delay to ensure the connection is estabilished, shardInfo is updated, and there is a ping - await delay(1500); - - const metadata = (await waku.libp2p.peerStore.get(nwaku1PeerId)).metadata; - expect(metadata.get("shardInfo")).to.not.be.undefined; - - const pingInfo = metadata.get("ping"); - expect(pingInfo).to.not.be.undefined; }); }); diff --git a/packages/tests/tests/nwaku.node.spec.ts b/packages/tests/tests/nwaku.node.spec.ts index 84c9d36d85..4a72b78e4b 100644 --- a/packages/tests/tests/nwaku.node.spec.ts +++ b/packages/tests/tests/nwaku.node.spec.ts @@ -17,8 +17,6 @@ describe("nwaku", () => { "--rest-admin=true", "--websocket-support=true", "--log-level=TRACE", - "--cluster-id=0", - "--shard=0", "--ports-shift=42" ]; diff --git a/packages/tests/tests/peer-exchange/compliance.spec.ts b/packages/tests/tests/peer-exchange/compliance.spec.ts index 5ac95252c4..48a081c318 100644 --- a/packages/tests/tests/peer-exchange/compliance.spec.ts +++ b/packages/tests/tests/peer-exchange/compliance.spec.ts @@ -5,7 +5,7 @@ import { createLightNode } from "@waku/sdk"; import { beforeEachCustom, - DefaultTestShardInfo, + DefaultTestNetworkConfig, makeLogFileName, ServiceNode, tearDownNodes @@ -40,7 +40,9 @@ describe("Peer Exchange", function () { tests({ async setup() { - waku = await createLightNode({ networkConfig: DefaultTestShardInfo }); + waku = await createLightNode({ + networkConfig: DefaultTestNetworkConfig + }); await waku.start(); const nwaku2Ma = await nwaku2.getMultiaddrWithId(); diff --git a/packages/tests/tests/peer-exchange/continuous_discovery.spec.ts b/packages/tests/tests/peer-exchange/continuous_discovery.spec.ts index 08fa5027d3..e85ba125e0 100644 --- a/packages/tests/tests/peer-exchange/continuous_discovery.spec.ts +++ b/packages/tests/tests/peer-exchange/continuous_discovery.spec.ts @@ -3,8 +3,8 @@ import { type PeerId } from "@libp2p/interface"; import { peerIdFromPrivateKey } from "@libp2p/peer-id"; import { multiaddr } from "@multiformats/multiaddr"; import { PeerExchangeDiscovery } from "@waku/discovery"; -import { IEnr, LightNode } from "@waku/interfaces"; -import { createLightNode, ShardInfo } from "@waku/sdk"; +import { IEnr, LightNode, RelayShards } from "@waku/interfaces"; +import { createLightNode } from "@waku/sdk"; import { decodeRelayShard } from "@waku/utils"; import { expect } from "chai"; import Sinon from "sinon"; @@ -15,8 +15,8 @@ describe("Peer Exchange Continuous Discovery", () => { let peerId: PeerId; let randomPeerId: PeerId; let waku: LightNode; - const shardInfo: ShardInfo = { - clusterId: 1, + const relayShards: RelayShards = { + clusterId: 2, shards: [1, 2] }; const multiaddrs = [multiaddr("/ip4/127.0.0.1/udp/1234")]; @@ -38,7 +38,7 @@ describe("Peer Exchange Continuous Discovery", () => { const newPeerInfo = { ENR: { peerId, - shardInfo, + shardInfo: relayShards, peerInfo: { multiaddrs: newMultiaddrs, id: peerId @@ -59,14 +59,14 @@ describe("Peer Exchange Continuous Discovery", () => { }); it("Should update shard info", async () => { - const newShardInfo: ShardInfo = { + const newRelayShards: RelayShards = { clusterId: 2, shards: [1, 2, 3] }; const newPeerInfo = { ENR: { peerId, - shardInfo: newShardInfo, + shardInfo: newRelayShards, peerInfo: { multiaddrs: multiaddrs, id: peerId @@ -86,7 +86,7 @@ describe("Peer Exchange Continuous Discovery", () => { ); const _shardInfo = decodeRelayShard(newPeer.metadata.get("shardInfo")!); - expect(_shardInfo).to.deep.equal(newShardInfo); + expect(_shardInfo).to.deep.equal(newRelayShards); }); async function discoverPeerOnce(): Promise { @@ -95,7 +95,7 @@ describe("Peer Exchange Continuous Discovery", () => { const enr: IEnr = { peerId, - shardInfo, + shardInfo: relayShards, peerInfo: { multiaddrs: multiaddrs, id: peerId @@ -122,6 +122,6 @@ describe("Peer Exchange Continuous Discovery", () => { multiaddrs[0].toString() ); const _shardInfo = decodeRelayShard(peer.metadata.get("shardInfo")!); - expect(_shardInfo).to.deep.equal(shardInfo); + expect(_shardInfo).to.deep.equal(relayShards); } }); diff --git a/packages/tests/tests/peer-exchange/index.spec.ts b/packages/tests/tests/peer-exchange/index.spec.ts index 57638756e3..c0a3128363 100644 --- a/packages/tests/tests/peer-exchange/index.spec.ts +++ b/packages/tests/tests/peer-exchange/index.spec.ts @@ -10,7 +10,9 @@ import Sinon, { SinonSpy } from "sinon"; import { afterEachCustom, beforeEachCustom, - DefaultTestShardInfo, + DefaultTestClusterId, + DefaultTestNetworkConfig, + DefaultTestRelayShards, makeLogFileName, ServiceNode, tearDownNodes @@ -30,15 +32,15 @@ describe("Peer Exchange", function () { nwaku1 = new ServiceNode(makeLogFileName(this.ctx) + "1"); nwaku2 = new ServiceNode(makeLogFileName(this.ctx) + "2"); await nwaku1.start({ - clusterId: DefaultTestShardInfo.clusterId, - shard: DefaultTestShardInfo.shards, + clusterId: DefaultTestClusterId, + shard: DefaultTestRelayShards.shards, discv5Discovery: true, peerExchange: true, relay: true }); await nwaku2.start({ - clusterId: DefaultTestShardInfo.clusterId, - shard: DefaultTestShardInfo.shards, + clusterId: DefaultTestClusterId, + shard: DefaultTestRelayShards.shards, discv5Discovery: true, peerExchange: true, discv5BootstrapNode: (await nwaku1.info()).enrUri, @@ -52,7 +54,7 @@ describe("Peer Exchange", function () { it("peer exchange sets tag", async function () { waku = await createLightNode({ - networkConfig: DefaultTestShardInfo, + networkConfig: DefaultTestNetworkConfig, libp2p: { peerDiscovery: [ bootstrap({ list: [(await nwaku2.getMultiaddrWithId()).toString()] }), @@ -117,8 +119,8 @@ describe("Peer Exchange", function () { nwaku3 = new ServiceNode(makeLogFileName(this) + "3"); await nwaku3.start({ - clusterId: DefaultTestShardInfo.clusterId, - shard: DefaultTestShardInfo.shards, + clusterId: DefaultTestClusterId, + shard: DefaultTestRelayShards.shards, discv5Discovery: true, peerExchange: true, discv5BootstrapNode: (await nwaku1.info()).enrUri, diff --git a/packages/tests/tests/peer-exchange/pe.optional.spec.ts b/packages/tests/tests/peer-exchange/pe.optional.spec.ts index 9b93f1d660..354c13325f 100644 --- a/packages/tests/tests/peer-exchange/pe.optional.spec.ts +++ b/packages/tests/tests/peer-exchange/pe.optional.spec.ts @@ -6,7 +6,6 @@ import { } from "@waku/discovery"; import type { LightNode } from "@waku/interfaces"; import { createLightNode } from "@waku/sdk"; -import { singleShardInfosToShardInfo } from "@waku/utils"; import { expect } from "chai"; import { afterEachCustom, tearDownNodes } from "../../src/index.js"; @@ -36,8 +35,7 @@ describe("Peer Exchange", () => { ) .filter((ma) => ma.includes("wss")); - const singleShardInfo = { clusterId: 1, shard: 1 }; - const shardInfo = singleShardInfosToShardInfo([singleShardInfo]); + const networkConfig = { clusterId: 2, numShardsInCluster: 0 }; waku = await createLightNode({ libp2p: { peerDiscovery: [ @@ -45,7 +43,7 @@ describe("Peer Exchange", () => { wakuPeerExchangeDiscovery() ] }, - networkConfig: shardInfo + networkConfig }); await waku.start(); diff --git a/packages/tests/tests/relay/index.node.spec.ts b/packages/tests/tests/relay/index.node.spec.ts index 91e038975f..2712715c72 100644 --- a/packages/tests/tests/relay/index.node.spec.ts +++ b/packages/tests/tests/relay/index.node.spec.ts @@ -13,6 +13,7 @@ import { createDecoder as createSymDecoder, createEncoder as createSymEncoder } from "@waku/message-encryption/symmetric"; +import { createRoutingInfo } from "@waku/utils"; import { bytesToUtf8, utf8ToBytes } from "@waku/utils/bytes"; import { expect } from "chai"; @@ -23,7 +24,7 @@ import { tearDownNodes } from "../../src/index.js"; -import { runJSNodes, TestPubsubTopic } from "./utils.js"; +import { runJSNodes, TestNetworkConfig, TestRoutingInfo } from "./utils.js"; describe("Waku Relay", function () { this.timeout(15000); @@ -51,20 +52,20 @@ describe("Waku Relay", function () { const eciesEncoder = createEciesEncoder({ contentTopic: asymTopic, publicKey, - pubsubTopic: TestPubsubTopic + routingInfo: TestRoutingInfo }); const symEncoder = createSymEncoder({ contentTopic: symTopic, symKey, - pubsubTopic: TestPubsubTopic + routingInfo: TestRoutingInfo }); const eciesDecoder = createEciesDecoder( asymTopic, - privateKey, - TestPubsubTopic + TestRoutingInfo, + privateKey ); - const symDecoder = createSymDecoder(symTopic, symKey, TestPubsubTopic); + const symDecoder = createSymDecoder(symTopic, TestRoutingInfo, symKey); const msgs: IDecodedMessage[] = []; void waku2.relay.subscribeWithUnsubscribe([eciesDecoder], (wakuMsg) => { @@ -93,19 +94,20 @@ describe("Waku Relay", function () { "Published on content topic with added then deleted observer"; const contentTopic = "/test/1/observer/proto"; + const routingInfo = createRoutingInfo(TestNetworkConfig, { contentTopic }); // The promise **fails** if we receive a message on this observer. const receivedMsgPromise: Promise = new Promise( (resolve, reject) => { const deleteObserver = waku2.relay.subscribeWithUnsubscribe( - [createDecoder(contentTopic)], + [createDecoder(contentTopic, routingInfo)], reject ) as () => void; deleteObserver(); setTimeout(resolve, 500); } ); - await waku1.relay.send(createEncoder({ contentTopic }), { + await waku1.relay.send(createEncoder({ contentTopic, routingInfo }), { payload: utf8ToBytes(messageText) }); diff --git a/packages/tests/tests/relay/interop.node.spec.ts b/packages/tests/tests/relay/interop.node.spec.ts index 211df51b9b..664fda4c18 100644 --- a/packages/tests/tests/relay/interop.node.spec.ts +++ b/packages/tests/tests/relay/interop.node.spec.ts @@ -19,8 +19,8 @@ import { TestContentTopic, TestDecoder, TestEncoder, - TestPubsubTopic, - TestShardInfo + TestNetworkConfig, + TestRoutingInfo } from "./utils.js"; import { runRelayNodes } from "./utils.js"; @@ -30,7 +30,12 @@ describe("Waku Relay, Interop", function () { let nwaku: ServiceNode; beforeEachCustom(this, async () => { - [nwaku, waku] = await runRelayNodes(this.ctx, TestShardInfo); + [nwaku, waku] = await runRelayNodes( + this.ctx, + TestNetworkConfig, + undefined, + [TestContentTopic] + ); }); afterEachCustom(this, async () => { @@ -42,8 +47,9 @@ describe("Waku Relay, Interop", function () { while (subscribers.length === 0) { await delay(200); - subscribers = - waku.libp2p.services.pubsub!.getSubscribers(TestPubsubTopic); + subscribers = waku.libp2p.services.pubsub!.getSubscribers( + TestRoutingInfo.pubsubTopic + ); } const nimPeerId = await nwaku.getPeerId(); @@ -86,7 +92,8 @@ describe("Waku Relay, Interop", function () { ServiceNode.toMessageRpcQuery({ contentTopic: TestContentTopic, payload: utf8ToBytes(messageText) - }) + }), + TestRoutingInfo ); const receivedMsg = await receivedMsgPromise; @@ -98,9 +105,10 @@ describe("Waku Relay, Interop", function () { it("Js publishes, other Js receives", async function () { const waku2 = await createRelayNode({ + routingInfos: [TestRoutingInfo], staticNoiseKey: NOISE_KEY_2, emitSelf: true, - networkConfig: TestShardInfo + networkConfig: TestNetworkConfig }); await waku2.start(); diff --git a/packages/tests/tests/relay/multiple_pubsub.node.spec.ts b/packages/tests/tests/relay/multiple_pubsub.node.spec.ts index d531cca547..4c3990fd08 100644 --- a/packages/tests/tests/relay/multiple_pubsub.node.spec.ts +++ b/packages/tests/tests/relay/multiple_pubsub.node.spec.ts @@ -1,18 +1,7 @@ import { createDecoder, createEncoder } from "@waku/core"; -import { - ContentTopicInfo, - IDecodedMessage, - Protocols, - RelayNode, - ShardInfo, - SingleShardInfo -} from "@waku/interfaces"; +import { IDecodedMessage, Protocols, RelayNode } from "@waku/interfaces"; import { createRelayNode } from "@waku/relay"; -import { - contentTopicToPubsubTopic, - pubsubTopicToSingleShardInfo, - singleShardInfoToPubsubTopic -} from "@waku/utils"; +import { createRoutingInfo } from "@waku/utils"; import { bytesToUtf8, utf8ToBytes } from "@waku/utils/bytes"; import { expect } from "chai"; @@ -26,43 +15,38 @@ import { } from "../../src/index.js"; import { TestDecoder } from "../filter/utils.js"; -describe("Waku Relay, multiple pubsub topics", function () { +describe("Waku Relay, static sharding, multiple pubsub topics", function () { this.timeout(15000); let waku1: RelayNode; let waku2: RelayNode; let waku3: RelayNode; - const customPubsubTopic1 = singleShardInfoToPubsubTopic({ - clusterId: 3, - shard: 1 - }); - const customPubsubTopic2 = singleShardInfoToPubsubTopic({ - clusterId: 3, - shard: 2 - }); - const shardInfo1: ShardInfo = { clusterId: 3, shards: [1] }; - const singleShardInfo1: SingleShardInfo = { - clusterId: 3, - shard: 1 - }; + const clusterId = 3; + const networkConfig = { clusterId }; + + const shardOne = 1; + const shardTwo = 2; + const customContentTopic1 = "/test/2/waku-relay/utf8"; const customContentTopic2 = "/test/3/waku-relay/utf8"; - const shardInfo2: ShardInfo = { clusterId: 3, shards: [2] }; - const singleShardInfo2: SingleShardInfo = { - clusterId: 3, - shard: 2 - }; + + const routingInfoOne = createRoutingInfo(networkConfig, { + shardId: shardOne + }); + const routingInfoTwo = createRoutingInfo(networkConfig, { + shardId: shardTwo + }); + const customEncoder1 = createEncoder({ - pubsubTopicShardInfo: singleShardInfo1, - contentTopic: customContentTopic1 + contentTopic: customContentTopic1, + routingInfo: routingInfoOne }); - const customDecoder1 = createDecoder(customContentTopic1, singleShardInfo1); + const customDecoder1 = createDecoder(customContentTopic1, routingInfoOne); const customEncoder2 = createEncoder({ - pubsubTopicShardInfo: singleShardInfo2, - contentTopic: customContentTopic2 + contentTopic: customContentTopic2, + routingInfo: routingInfoTwo }); - const customDecoder2 = createDecoder(customContentTopic2, singleShardInfo2); - const shardInfoBothShards: ShardInfo = { clusterId: 3, shards: [1, 2] }; + const customDecoder2 = createDecoder(customContentTopic2, routingInfoTwo); afterEachCustom(this, async () => { await tearDownNodes([], [waku1, waku2, waku3]); @@ -70,35 +54,36 @@ describe("Waku Relay, multiple pubsub topics", function () { [ { - pubsub: customPubsubTopic1, - shardInfo: shardInfo1, + routingInfo: routingInfoOne, encoder: customEncoder1, decoder: customDecoder1 }, { - pubsub: customPubsubTopic2, - shardInfo: shardInfo2, + routingInfo: routingInfoTwo, encoder: customEncoder2, decoder: customDecoder2 } ].forEach((testItem) => { - it(`3 nodes on ${testItem.pubsub} topic`, async function () { + it(`3 nodes on ${testItem.routingInfo.pubsubTopic} topic`, async function () { const [msgCollector1, msgCollector2, msgCollector3] = Array(3) .fill(null) .map(() => new MessageCollector()); [waku1, waku2, waku3] = await Promise.all([ createRelayNode({ - networkConfig: testItem.shardInfo, + networkConfig: networkConfig, + routingInfos: [testItem.routingInfo], staticNoiseKey: NOISE_KEY_1 }).then((waku) => waku.start().then(() => waku)), createRelayNode({ - networkConfig: testItem.shardInfo, + networkConfig: networkConfig, + routingInfos: [testItem.routingInfo], staticNoiseKey: NOISE_KEY_2, libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } } }).then((waku) => waku.start().then(() => waku)), createRelayNode({ - networkConfig: testItem.shardInfo, + networkConfig: networkConfig, + routingInfos: [testItem.routingInfo], staticNoiseKey: NOISE_KEY_3 }).then((waku) => waku.start().then(() => waku)) ]); @@ -196,16 +181,19 @@ describe("Waku Relay, multiple pubsub topics", function () { // Waku1 and waku2 are using multiple pubsub topis [waku1, waku2, waku3] = await Promise.all([ createRelayNode({ - networkConfig: shardInfoBothShards, + networkConfig: networkConfig, + routingInfos: [routingInfoOne, routingInfoTwo], staticNoiseKey: NOISE_KEY_1 }).then((waku) => waku.start().then(() => waku)), createRelayNode({ - networkConfig: shardInfoBothShards, + networkConfig: networkConfig, + routingInfos: [routingInfoOne, routingInfoTwo], staticNoiseKey: NOISE_KEY_2, libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } } }).then((waku) => waku.start().then(() => waku)), createRelayNode({ - networkConfig: shardInfo1, + networkConfig: networkConfig, + routingInfos: [routingInfoOne], staticNoiseKey: NOISE_KEY_3 }).then((waku) => waku.start().then(() => waku)) ]); @@ -262,18 +250,22 @@ describe("Waku Relay, multiple pubsub topics", function () { expect(msgCollector3.hasMessage(customContentTopic1, "M3")).to.eq(true); }); - it("n1 and n2 uses a custom pubsub, n3 uses the default pubsub", async function () { + it("n1 and n2 uses relay shard 1, n3 uses relay shard 2", async function () { [waku1, waku2, waku3] = await Promise.all([ createRelayNode({ - networkConfig: shardInfo1, + networkConfig, + routingInfos: [routingInfoOne], staticNoiseKey: NOISE_KEY_1 }).then((waku) => waku.start().then(() => waku)), createRelayNode({ - networkConfig: shardInfo1, + networkConfig, + routingInfos: [routingInfoOne], staticNoiseKey: NOISE_KEY_2, libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } } }).then((waku) => waku.start().then(() => waku)), createRelayNode({ + networkConfig, + routingInfos: [routingInfoTwo], staticNoiseKey: NOISE_KEY_3 }).then((waku) => waku.start().then(() => waku)) ]); @@ -319,55 +311,45 @@ describe("Waku Relay, multiple pubsub topics", function () { await waku3NoMsgPromise; expect(bytesToUtf8(waku2ReceivedMsg.payload!)).to.eq(messageText); - expect(waku2ReceivedMsg.pubsubTopic).to.eq(customPubsubTopic1); + expect(waku2ReceivedMsg.pubsubTopic).to.eq(routingInfoOne.pubsubTopic); }); }); -describe("Waku Relay (Autosharding), multiple pubsub topics", function () { +describe("Waku Relay auto-sharding, multiple pubsub topics", function () { this.timeout(15000); const clusterId = 7; let waku1: RelayNode; let waku2: RelayNode; let waku3: RelayNode; + const networkConfig = { clusterId, numShardsInCluster: 8 }; + const customContentTopic1 = "/waku/2/content/utf8"; const customContentTopic2 = "/myapp/1/latest/proto"; - const autoshardingPubsubTopic1 = contentTopicToPubsubTopic( - customContentTopic1, - clusterId - ); - const autoshardingPubsubTopic2 = contentTopicToPubsubTopic( - customContentTopic2, - clusterId - ); - const contentTopicInfo1: ContentTopicInfo = { - clusterId: clusterId, - contentTopics: [customContentTopic1] - }; - const contentTopicInfo2: ContentTopicInfo = { - clusterId: clusterId, - contentTopics: [customContentTopic2] - }; + + const routingInfo1 = createRoutingInfo(networkConfig, { + contentTopic: customContentTopic1 + }); + const routingInfo2 = createRoutingInfo(networkConfig, { + contentTopic: customContentTopic2 + }); + + if (routingInfo1.pubsubTopic == routingInfo2.pubsubTopic) + throw "Internal error, both content topics resolve to same shard"; + const customEncoder1 = createEncoder({ contentTopic: customContentTopic1, - pubsubTopicShardInfo: pubsubTopicToSingleShardInfo(autoshardingPubsubTopic1) + routingInfo: routingInfo1 }); - const customDecoder1 = createDecoder( - customContentTopic1, - pubsubTopicToSingleShardInfo(autoshardingPubsubTopic1) - ); + const customDecoder1 = createDecoder(customContentTopic1, routingInfo1); const customEncoder2 = createEncoder({ contentTopic: customContentTopic2, - pubsubTopicShardInfo: pubsubTopicToSingleShardInfo(autoshardingPubsubTopic2) + routingInfo: routingInfo2 }); - const customDecoder2 = createDecoder( - customContentTopic2, - pubsubTopicToSingleShardInfo(autoshardingPubsubTopic2) - ); - const contentTopicInfoBothShards: ContentTopicInfo = { - clusterId: clusterId, - contentTopics: [customContentTopic1, customContentTopic2] - }; + const customDecoder2 = createDecoder(customContentTopic2, routingInfo2); + + const relayShard1 = { clusterId, shards: [routingInfo1.shardId] }; + const relayShard2 = { clusterId, shards: [routingInfo2.shardId] }; afterEachCustom(this, async () => { await tearDownNodes([], [waku1, waku2, waku3]); @@ -375,35 +357,38 @@ describe("Waku Relay (Autosharding), multiple pubsub topics", function () { [ { - pubsub: autoshardingPubsubTopic1, - shardInfo: contentTopicInfo1, + routingInfo: routingInfo1, + relayShards: relayShard1, encoder: customEncoder1, decoder: customDecoder1 }, { - pubsub: autoshardingPubsubTopic2, - shardInfo: contentTopicInfo2, + routingInfo: routingInfo2, + relayShards: relayShard2, encoder: customEncoder2, decoder: customDecoder2 } ].forEach((testItem) => { - it(`3 nodes on ${testItem.pubsub} topic`, async function () { + it(`3 nodes on ${testItem.routingInfo.pubsubTopic} topic`, async function () { const [msgCollector1, msgCollector2, msgCollector3] = Array(3) .fill(null) .map(() => new MessageCollector()); [waku1, waku2, waku3] = await Promise.all([ createRelayNode({ - networkConfig: testItem.shardInfo, + networkConfig, + routingInfos: [testItem.routingInfo], staticNoiseKey: NOISE_KEY_1 }).then((waku) => waku.start().then(() => waku)), createRelayNode({ - networkConfig: testItem.shardInfo, + networkConfig, + routingInfos: [testItem.routingInfo], staticNoiseKey: NOISE_KEY_2, libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } } }).then((waku) => waku.start().then(() => waku)), createRelayNode({ - networkConfig: testItem.shardInfo, + networkConfig, + routingInfos: [testItem.routingInfo], staticNoiseKey: NOISE_KEY_3 }).then((waku) => waku.start().then(() => waku)) ]); @@ -510,16 +495,19 @@ describe("Waku Relay (Autosharding), multiple pubsub topics", function () { // Waku1 and waku2 are using multiple pubsub topis [waku1, waku2, waku3] = await Promise.all([ createRelayNode({ - networkConfig: contentTopicInfoBothShards, + networkConfig, + routingInfos: [routingInfo1, routingInfo2], staticNoiseKey: NOISE_KEY_1 }).then((waku) => waku.start().then(() => waku)), createRelayNode({ - networkConfig: contentTopicInfoBothShards, + networkConfig, + routingInfos: [routingInfo1, routingInfo2], staticNoiseKey: NOISE_KEY_2, libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } } }).then((waku) => waku.start().then(() => waku)), createRelayNode({ - networkConfig: contentTopicInfo1, + networkConfig, + routingInfos: [routingInfo1], staticNoiseKey: NOISE_KEY_3 }).then((waku) => waku.start().then(() => waku)) ]); @@ -603,18 +591,22 @@ describe("Waku Relay (Autosharding), multiple pubsub topics", function () { expect(msgCollector3.hasMessage(customContentTopic1, "M3")).to.eq(true); }); - it("n1 and n2 uses a custom pubsub, n3 uses the default pubsub", async function () { + it("n1 and n2 uses first shard, n3 uses the second shard", async function () { [waku1, waku2, waku3] = await Promise.all([ createRelayNode({ - networkConfig: contentTopicInfo1, + networkConfig, + routingInfos: [routingInfo1], staticNoiseKey: NOISE_KEY_1 }).then((waku) => waku.start().then(() => waku)), createRelayNode({ - networkConfig: contentTopicInfo1, + networkConfig, + routingInfos: [routingInfo1], staticNoiseKey: NOISE_KEY_2, libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } } }).then((waku) => waku.start().then(() => waku)), createRelayNode({ + networkConfig, + routingInfos: [routingInfo2], staticNoiseKey: NOISE_KEY_3 }).then((waku) => waku.start().then(() => waku)) ]); @@ -660,6 +652,6 @@ describe("Waku Relay (Autosharding), multiple pubsub topics", function () { await waku3NoMsgPromise; expect(bytesToUtf8(waku2ReceivedMsg.payload!)).to.eq(messageText); - expect(waku2ReceivedMsg.pubsubTopic).to.eq(autoshardingPubsubTopic1); + expect(waku2ReceivedMsg.pubsubTopic).to.eq(routingInfo1.pubsubTopic); }); }); diff --git a/packages/tests/tests/relay/publish.node.spec.ts b/packages/tests/tests/relay/publish.node.spec.ts index f7fc8be951..e8b8afed8a 100644 --- a/packages/tests/tests/relay/publish.node.spec.ts +++ b/packages/tests/tests/relay/publish.node.spec.ts @@ -1,5 +1,6 @@ import { createEncoder } from "@waku/core"; import { IRateLimitProof, ProtocolError, RelayNode } from "@waku/interfaces"; +import { createRoutingInfo } from "@waku/utils"; import { utf8ToBytes } from "@waku/utils/bytes"; import { expect } from "chai"; @@ -16,13 +17,12 @@ import { import { messageText, runJSNodes, + TestClusterId, TestContentTopic, TestDecoder, TestEncoder, TestExpectOptions, - TestPubsubTopic, - TestShardInfo, - TestWaitMessageOptions, + TestRoutingInfo, waitForAllRemotePeers } from "./utils.js"; @@ -54,9 +54,7 @@ describe("Waku Relay, Publish", function () { expect(pushResponse.successes[0].toString()).to.eq( waku2.libp2p.peerId.toString() ); - expect( - await messageCollector.waitForMessages(1, TestWaitMessageOptions) - ).to.eq(true); + expect(await messageCollector.waitForMessages(1)).to.eq(true); messageCollector.verifyReceivedMessage(0, { ...TestExpectOptions, expectedMessageText: testItem.value @@ -81,9 +79,7 @@ describe("Waku Relay, Publish", function () { waku2.libp2p.peerId.toString() ); - expect( - await messageCollector.waitForMessages(1, TestWaitMessageOptions) - ).to.eq(true); + expect(await messageCollector.waitForMessages(1)).to.eq(true); messageCollector.verifyReceivedMessage(0, { ...TestExpectOptions, @@ -107,31 +103,16 @@ describe("Waku Relay, Publish", function () { it("Fails to publish message with empty text", async function () { await waku1.relay.send(TestEncoder, { payload: utf8ToBytes("") }); await delay(400); - expect( - await messageCollector.waitForMessages(1, TestWaitMessageOptions) - ).to.eq(false); - }); - - it("Fails to publish message with wrong content topic", async function () { - const wrong_encoder = createEncoder({ - contentTopic: "/test/1/wrong/utf8", - pubsubTopic: TestPubsubTopic - }); - await waku1.relay.send(wrong_encoder, { - payload: utf8ToBytes("") - }); - expect( - await messageCollector.waitForMessages(1, TestWaitMessageOptions) - ).to.eq(false); + expect(await messageCollector.waitForMessages(1)).to.eq(false); }); it("Fails to publish message with wrong pubsubtopic", async function () { const wrong_encoder = createEncoder({ - pubsubTopicShardInfo: { - clusterId: TestShardInfo.clusterId, - shard: TestShardInfo.shards[0] + 1 - }, - contentTopic: TestContentTopic + contentTopic: TestContentTopic, + routingInfo: createRoutingInfo( + { clusterId: TestClusterId }, + { shardId: 32 } + ) }); const pushResponse = await waku1.relay.send(wrong_encoder, { payload: utf8ToBytes("") @@ -140,9 +121,7 @@ describe("Waku Relay, Publish", function () { ProtocolError.TOPIC_NOT_CONFIGURED ); await delay(400); - expect( - await messageCollector.waitForMessages(1, TestWaitMessageOptions) - ).to.eq(false); + expect(await messageCollector.waitForMessages(1)).to.eq(false); }); [1024 ** 2 + 65536, 2 * 1024 ** 2].forEach((testItem) => { @@ -155,9 +134,7 @@ describe("Waku Relay, Publish", function () { ProtocolError.SIZE_TOO_BIG ); await delay(400); - expect( - await messageCollector.waitForMessages(1, TestWaitMessageOptions) - ).to.eq(false); + expect(await messageCollector.waitForMessages(1)).to.eq(false); }); }); @@ -183,9 +160,7 @@ describe("Waku Relay, Publish", function () { expect(pushResponse.successes[0].toString()).to.eq( waku2.libp2p.peerId.toString() ); - expect( - await messageCollector.waitForMessages(2, TestWaitMessageOptions) - ).to.eq(true); + expect(await messageCollector.waitForMessages(2)).to.eq(true); }); // Will be skipped until https://github.com/waku-org/js-waku/issues/1464 si done @@ -210,15 +185,13 @@ describe("Waku Relay, Publish", function () { expect(pushResponse.successes[0].toString()).to.eq( waku2.libp2p.peerId.toString() ); - expect( - await messageCollector.waitForMessages(2, TestWaitMessageOptions) - ).to.eq(true); + expect(await messageCollector.waitForMessages(2)).to.eq(true); }); it("Publish message with large meta", async function () { const customTestEncoder = createEncoder({ contentTopic: TestContentTopic, - pubsubTopic: TestPubsubTopic, + routingInfo: TestRoutingInfo, metaSetter: () => new Uint8Array(10 ** 6) }); @@ -229,9 +202,7 @@ describe("Waku Relay, Publish", function () { expect(pushResponse.successes[0].toString()).to.eq( waku2.libp2p.peerId.toString() ); - expect( - await messageCollector.waitForMessages(1, TestWaitMessageOptions) - ).to.eq(true); + expect(await messageCollector.waitForMessages(1)).to.eq(true); }); it("Publish message with rate limit", async function () { @@ -251,9 +222,7 @@ describe("Waku Relay, Publish", function () { }); expect(pushResponse.successes.length).to.eq(1); - expect( - await messageCollector.waitForMessages(1, TestWaitMessageOptions) - ).to.eq(true); + expect(await messageCollector.waitForMessages(1)).to.eq(true); messageCollector.verifyReceivedMessage(0, { ...TestExpectOptions, expectedMessageText: messageText diff --git a/packages/tests/tests/relay/subscribe.node.spec.ts b/packages/tests/tests/relay/subscribe.node.spec.ts index c147ed3595..698d5ee45c 100644 --- a/packages/tests/tests/relay/subscribe.node.spec.ts +++ b/packages/tests/tests/relay/subscribe.node.spec.ts @@ -1,6 +1,7 @@ import { createDecoder, createEncoder } from "@waku/core"; import { RelayNode } from "@waku/interfaces"; import { createRelayNode } from "@waku/relay"; +import { createRoutingInfo } from "@waku/utils"; import { utf8ToBytes } from "@waku/utils/bytes"; import { expect } from "chai"; @@ -20,9 +21,8 @@ import { TestDecoder, TestEncoder, TestExpectOptions, - TestPubsubTopic, - TestShardInfo, - TestWaitMessageOptions, + TestNetworkConfig, + TestRoutingInfo, waitForAllRemotePeers } from "./utils.js"; @@ -44,10 +44,10 @@ describe("Waku Relay, Subscribe", function () { it("Mutual subscription", async function () { await waitForAllRemotePeers(waku1, waku2); const subscribers1 = waku1.libp2p.services - .pubsub!.getSubscribers(TestPubsubTopic) + .pubsub!.getSubscribers(TestRoutingInfo.pubsubTopic) .map((p) => p.toString()); const subscribers2 = waku2.libp2p.services - .pubsub!.getSubscribers(TestPubsubTopic) + .pubsub!.getSubscribers(TestRoutingInfo.pubsubTopic) .map((p) => p.toString()); expect(subscribers1).to.contain(waku2.libp2p.peerId.toString()); @@ -65,7 +65,8 @@ describe("Waku Relay, Subscribe", function () { try { const waku = await createRelayNode({ staticNoiseKey: NOISE_KEY_1, - networkConfig: TestShardInfo + networkConfig: TestNetworkConfig, + routingInfos: [TestRoutingInfo] }); await waku.start(); @@ -90,9 +91,7 @@ describe("Waku Relay, Subscribe", function () { messageCollector.callback ); await waku1.relay.send(TestEncoder, { payload: utf8ToBytes(messageText) }); - expect( - await messageCollector.waitForMessages(1, TestWaitMessageOptions) - ).to.eq(true); + expect(await messageCollector.waitForMessages(1)).to.eq(true); messageCollector.verifyReceivedMessage(0, { ...TestExpectOptions, expectedMessageText: messageText @@ -115,7 +114,6 @@ describe("Waku Relay, Subscribe", function () { // Verify that each message was received on the corresponding topic. expect( await messageCollector.waitForMessages(messageCount, { - ...TestWaitMessageOptions, exact: true }) ).to.eq(true); @@ -130,12 +128,15 @@ describe("Waku Relay, Subscribe", function () { }); it("Subscribe and publish messages on 2 different content topics", async function () { - const secondContentTopic = "/test/2/waku-relay/utf8"; + const secondContentTopic = "/test/0/waku-relay-2/utf8"; + const secondRoutingInfo = createRoutingInfo(TestNetworkConfig, { + contentTopic: secondContentTopic + }); const secondEncoder = createEncoder({ contentTopic: secondContentTopic, - pubsubTopic: TestPubsubTopic + routingInfo: secondRoutingInfo }); - const secondDecoder = createDecoder(secondContentTopic, TestPubsubTopic); + const secondDecoder = createDecoder(secondContentTopic, secondRoutingInfo); await waku2.relay.subscribeWithUnsubscribe( [TestDecoder], @@ -149,7 +150,6 @@ describe("Waku Relay, Subscribe", function () { await waku1.relay.send(secondEncoder, { payload: utf8ToBytes("M2") }); expect( await messageCollector.waitForMessages(2, { - ...TestWaitMessageOptions, exact: true }) ).to.eq(true); @@ -166,7 +166,7 @@ describe("Waku Relay, Subscribe", function () { it("Subscribe one by one to 100 topics and publish messages", async function () { const topicCount = 100; - const td = generateTestData(topicCount, TestWaitMessageOptions); + const td = generateTestData(topicCount, TestNetworkConfig); // Subscribe to topics one by one for (let i = 0; i < topicCount; i++) { @@ -186,7 +186,6 @@ describe("Waku Relay, Subscribe", function () { // Verify that each message was received on the corresponding topic. expect( await messageCollector.waitForMessages(topicCount, { - ...TestWaitMessageOptions, exact: true }) ).to.eq(true); @@ -201,7 +200,7 @@ describe("Waku Relay, Subscribe", function () { it("Subscribe at once to 10000 topics and publish messages", async function () { const topicCount = 10000; - const td = generateTestData(topicCount, TestWaitMessageOptions); + const td = generateTestData(topicCount, TestNetworkConfig); // Subscribe to all topics at once await waku2.relay.subscribeWithUnsubscribe( @@ -219,7 +218,6 @@ describe("Waku Relay, Subscribe", function () { // Verify that each message was received on the corresponding topic. expect( await messageCollector.waitForMessages(topicCount, { - ...TestWaitMessageOptions, exact: true }) ).to.eq(true); @@ -248,7 +246,6 @@ describe("Waku Relay, Subscribe", function () { expect( await messageCollector.waitForMessages(1, { - ...TestWaitMessageOptions, exact: true }) ).to.eq(true); @@ -258,9 +255,9 @@ describe("Waku Relay, Subscribe", function () { it.skip("Overlapping topic subscription", async function () { // Define two sets of test data with overlapping topics. const topicCount1 = 2; - const td1 = generateTestData(topicCount1, TestWaitMessageOptions); + const td1 = generateTestData(topicCount1, TestNetworkConfig); const topicCount2 = 4; - const td2 = generateTestData(topicCount2, TestWaitMessageOptions); + const td2 = generateTestData(topicCount2, TestNetworkConfig); // Subscribe to the first set of topics. await waku2.relay.subscribeWithUnsubscribe( @@ -293,7 +290,6 @@ describe("Waku Relay, Subscribe", function () { // Since there are overlapping topics, there should be 6 messages in total (2 from the first set + 4 from the second set). expect( await messageCollector.waitForMessages(6, { - ...TestWaitMessageOptions, exact: true }) ).to.eq(true); @@ -301,29 +297,39 @@ describe("Waku Relay, Subscribe", function () { TEST_STRING.forEach((testItem) => { it(`Subscribe to topic containing ${testItem.description} and publish message`, async function () { - const newContentTopic = testItem.value; - const newEncoder = createEncoder({ - contentTopic: newContentTopic, - pubsubTopic: TestPubsubTopic - }); - const newDecoder = createDecoder(newContentTopic, TestPubsubTopic); + const newContentTopic = `/test/0/${testItem.value}/null`; - await waku2.relay.subscribeWithUnsubscribe( - [newDecoder], - messageCollector.callback - ); - await waku1.relay.send(newEncoder, { - payload: utf8ToBytes(messageText) - }); + try { + const newRoutingInfo = createRoutingInfo(TestNetworkConfig, { + contentTopic: newContentTopic + }); - expect( - await messageCollector.waitForMessages(1, TestWaitMessageOptions) - ).to.eq(true); - messageCollector.verifyReceivedMessage(0, { - ...TestExpectOptions, - expectedMessageText: messageText, - expectedContentTopic: newContentTopic - }); + const newEncoder = createEncoder({ + contentTopic: newContentTopic, + routingInfo: newRoutingInfo + }); + const newDecoder = createDecoder(newContentTopic, newRoutingInfo); + + await waku2.relay.subscribeWithUnsubscribe( + [newDecoder], + messageCollector.callback + ); + await waku1.relay.send(newEncoder, { + payload: utf8ToBytes(messageText) + }); + + expect(await messageCollector.waitForMessages(1)).to.eq(true); + messageCollector.verifyReceivedMessage(0, { + ...TestExpectOptions, + expectedMessageText: messageText, + expectedContentTopic: newContentTopic + }); + } catch (err: unknown) { + if (testItem.invalidContentTopic) { + const e = err as Error; + expect(e.message).to.contain("Invalid generation field"); + } + } }); }); }); diff --git a/packages/tests/tests/relay/utils.ts b/packages/tests/tests/relay/utils.ts index 2feb0c7a69..f865a25515 100644 --- a/packages/tests/tests/relay/utils.ts +++ b/packages/tests/tests/relay/utils.ts @@ -1,12 +1,14 @@ import { createDecoder, createEncoder } from "@waku/core"; import { + AutoSharding, + ContentTopic, NetworkConfig, Protocols, RelayNode, - ShardInfo + type ShardId } from "@waku/interfaces"; import { createRelayNode } from "@waku/relay"; -import { contentTopicToPubsubTopic, Logger } from "@waku/utils"; +import { createRoutingInfo, Logger } from "@waku/utils"; import { Context } from "mocha"; import { @@ -16,25 +18,25 @@ import { ServiceNode } from "../../src/index.js"; +export const TestClusterId = 4; export const messageText = "Relay works!"; -export const TestContentTopic = "/test/1/waku-relay/utf8"; -export const TestShardInfo: ShardInfo = { - clusterId: 2, - shards: [4] +export const TestContentTopic = "/test/0/waku-relay/utf8"; + +export const TestNetworkConfig: AutoSharding = { + clusterId: TestClusterId, + numShardsInCluster: 8 }; -export const TestPubsubTopic = contentTopicToPubsubTopic( - TestContentTopic, - TestShardInfo.clusterId -); +export const TestRoutingInfo = createRoutingInfo(TestNetworkConfig, { + contentTopic: TestContentTopic +}); export const TestEncoder = createEncoder({ contentTopic: TestContentTopic, - pubsubTopic: TestPubsubTopic + routingInfo: TestRoutingInfo }); -export const TestDecoder = createDecoder(TestContentTopic, TestPubsubTopic); -export const TestWaitMessageOptions = { pubsubTopic: TestPubsubTopic }; +export const TestDecoder = createDecoder(TestContentTopic, TestRoutingInfo); export const TestExpectOptions = { expectedContentTopic: TestContentTopic, - expectedPubsubTopic: TestPubsubTopic + expectedPubsubTopic: TestRoutingInfo.pubsubTopic }; export const log = new Logger("test:relay"); @@ -51,10 +53,14 @@ export async function waitForAllRemotePeers( export const runRelayNodes = ( context: Context, - networkConfig: NetworkConfig + networkConfig: NetworkConfig, + relayShards?: ShardId[], // Only for static sharding + contentTopics?: ContentTopic[] // Only for auto sharding ): Promise<[ServiceNode, RelayNode]> => runNodes({ networkConfig, + relayShards, + contentTopics, context, protocols: RELAY_PROTOCOLS, createNode: createRelayNode @@ -64,12 +70,14 @@ export async function runJSNodes(): Promise<[RelayNode, RelayNode]> { log.info("Starting JS Waku instances"); const [waku1, waku2] = await Promise.all([ createRelayNode({ + routingInfos: [TestRoutingInfo], staticNoiseKey: NOISE_KEY_1, - networkConfig: TestShardInfo + networkConfig: TestNetworkConfig }).then((waku) => waku.start().then(() => waku)), createRelayNode({ + routingInfos: [TestRoutingInfo], staticNoiseKey: NOISE_KEY_2, - networkConfig: TestShardInfo, + networkConfig: TestNetworkConfig, libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } } }).then((waku) => waku.start().then(() => waku)) ]); diff --git a/packages/tests/tests/sharding/auto_sharding.spec.ts b/packages/tests/tests/sharding/auto_sharding.spec.ts index 15d0c9b476..306c211a11 100644 --- a/packages/tests/tests/sharding/auto_sharding.spec.ts +++ b/packages/tests/tests/sharding/auto_sharding.spec.ts @@ -1,9 +1,6 @@ -import { LightNode } from "@waku/interfaces"; +import { AutoSharding, LightNode } from "@waku/interfaces"; import { createEncoder, utf8ToBytes } from "@waku/sdk"; -import { - contentTopicToPubsubTopic, - contentTopicToShardIndex -} from "@waku/utils"; +import { contentTopicToPubsubTopic, createRoutingInfo } from "@waku/utils"; import { expect } from "chai"; import { @@ -33,10 +30,14 @@ describe("Autosharding: Running Nodes", function () { // js-waku allows autosharding for cluster IDs different than 1 it("Cluster ID 0 - Default/Global Cluster", async function () { const clusterId = 0; + const networkConfig: AutoSharding = { clusterId, numShardsInCluster: 8 }; + const routingInfo = createRoutingInfo(networkConfig, { + contentTopic: ContentTopic + }); [serviceNodes, waku] = await runMultipleNodes( this.ctx, - { clusterId, contentTopics: [ContentTopic] }, + routingInfo, { lightpush: true, filter: true }, false, numServiceNodes, @@ -45,10 +46,7 @@ describe("Autosharding: Running Nodes", function () { const encoder = createEncoder({ contentTopic: ContentTopic, - pubsubTopicShardInfo: { - clusterId: clusterId, - shard: contentTopicToShardIndex(ContentTopic) - } + routingInfo }); const request = await waku.lightPush.send(encoder, { @@ -56,19 +54,19 @@ describe("Autosharding: Running Nodes", function () { }); expect(request.successes.length).to.eq(numServiceNodes); - expect( - await serviceNodes.messageCollector.waitForMessages(1, { - pubsubTopic: encoder.pubsubTopic - }) - ).to.eq(true); + expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq(true); }); it("Non TWN Cluster", async function () { const clusterId = 5; + const networkConfig: AutoSharding = { clusterId, numShardsInCluster: 10 }; + const routingInfo = createRoutingInfo(networkConfig, { + contentTopic: ContentTopic + }); [serviceNodes, waku] = await runMultipleNodes( this.ctx, - { clusterId, contentTopics: [ContentTopic] }, + routingInfo, { lightpush: true, filter: true }, false, numServiceNodes, @@ -77,10 +75,7 @@ describe("Autosharding: Running Nodes", function () { const encoder = createEncoder({ contentTopic: ContentTopic, - pubsubTopicShardInfo: { - clusterId: clusterId, - shard: contentTopicToShardIndex(ContentTopic) - } + routingInfo }); const request = await waku.lightPush.send(encoder, { @@ -88,11 +83,7 @@ describe("Autosharding: Running Nodes", function () { }); expect(request.successes.length).to.eq(numServiceNodes); - expect( - await serviceNodes.messageCollector.waitForMessages(1, { - pubsubTopic: encoder.pubsubTopic - }) - ).to.eq(true); + expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq(true); }); const numTest = 10; @@ -109,9 +100,14 @@ describe("Autosharding: Running Nodes", function () { it(`random auto sharding ${ i + 1 } - Cluster ID: ${clusterId}, Content Topic: ${ContentTopic}`, async function () { + const networkConfig: AutoSharding = { clusterId, numShardsInCluster: 8 }; + const routingInfo = createRoutingInfo(networkConfig, { + contentTopic: ContentTopic + }); + [serviceNodes, waku] = await runMultipleNodes( this.ctx, - { clusterId, contentTopics: [ContentTopic] }, + routingInfo, { lightpush: true, filter: true }, false, numServiceNodes, @@ -120,10 +116,7 @@ describe("Autosharding: Running Nodes", function () { const encoder = createEncoder({ contentTopic: ContentTopic, - pubsubTopicShardInfo: { - clusterId: clusterId, - shard: contentTopicToShardIndex(ContentTopic) - } + routingInfo }); const request = await waku.lightPush.send(encoder, { @@ -133,7 +126,7 @@ describe("Autosharding: Running Nodes", function () { expect(request.successes.length).to.eq(numServiceNodes); expect( await serviceNodes.messageCollector.waitForMessages(1, { - pubsubTopic: encoder.pubsubTopic + contentTopic: ContentTopic }) ).to.eq(true); }); @@ -143,7 +136,7 @@ describe("Autosharding: Running Nodes", function () { it("Wrong topic", async function () { const wrongTopic = "wrong_format"; try { - contentTopicToPubsubTopic(wrongTopic, clusterId); + contentTopicToPubsubTopic(wrongTopic, clusterId, 8); throw new Error("Wrong topic should've thrown an error"); } catch (err) { if ( @@ -156,10 +149,19 @@ describe("Autosharding: Running Nodes", function () { }); it("configure the node with multiple content topics", async function () { + const networkConfig: AutoSharding = { clusterId, numShardsInCluster: 8 }; + const routingInfo = createRoutingInfo(networkConfig, { + contentTopic: ContentTopic + }); + [serviceNodes, waku] = await runMultipleNodes( this.ctx, - { clusterId, contentTopics: [ContentTopic, ContentTopic2] }, - { lightpush: true, filter: true }, + routingInfo, + { + lightpush: true, + filter: true, + contentTopic: [ContentTopic, ContentTopic2] + }, false, numServiceNodes, true @@ -167,18 +169,14 @@ describe("Autosharding: Running Nodes", function () { const encoder1 = createEncoder({ contentTopic: ContentTopic, - pubsubTopicShardInfo: { - clusterId: clusterId, - shard: contentTopicToShardIndex(ContentTopic) - } + routingInfo }); const encoder2 = createEncoder({ contentTopic: ContentTopic2, - pubsubTopicShardInfo: { - clusterId: clusterId, - shard: contentTopicToShardIndex(ContentTopic2) - } + routingInfo: createRoutingInfo(networkConfig, { + contentTopic: ContentTopic2 + }) }); const request1 = await waku.lightPush.send(encoder1, { @@ -187,7 +185,7 @@ describe("Autosharding: Running Nodes", function () { expect(request1.successes.length).to.eq(numServiceNodes); expect( await serviceNodes.messageCollector.waitForMessages(1, { - pubsubTopic: encoder1.pubsubTopic + contentTopic: ContentTopic }) ).to.eq(true); @@ -197,7 +195,7 @@ describe("Autosharding: Running Nodes", function () { expect(request2.successes.length).to.eq(numServiceNodes); expect( await serviceNodes.messageCollector.waitForMessages(1, { - pubsubTopic: encoder2.pubsubTopic + contentTopic: ContentTopic2 }) ).to.eq(true); }); diff --git a/packages/tests/tests/sharding/peer_management.spec.ts b/packages/tests/tests/sharding/peer_management.spec.ts index 6b42aa4e90..c0dec2e1fd 100644 --- a/packages/tests/tests/sharding/peer_management.spec.ts +++ b/packages/tests/tests/sharding/peer_management.spec.ts @@ -1,13 +1,8 @@ import { bootstrap } from "@libp2p/bootstrap"; import type { PeerId } from "@libp2p/interface"; import { wakuPeerExchangeDiscovery } from "@waku/discovery"; -import { - ContentTopicInfo, - createLightNode, - LightNode, - ShardInfo, - Tags -} from "@waku/sdk"; +import { AutoSharding } from "@waku/interfaces"; +import { createLightNode, LightNode, Tags } from "@waku/sdk"; import { contentTopicToShardIndex } from "@waku/utils"; import chai, { expect } from "chai"; import chaiAsPromised from "chai-as-promised"; @@ -48,14 +43,17 @@ describe("Static Sharding: Peer Management", function () { it("all px service nodes subscribed to the shard topic should be dialed", async function () { this.timeout(100_000); - const shardInfo: ShardInfo = { clusterId: clusterId, shards: [2] }; + const shard = 2; + const numShardsInCluster = 8; + const networkConfig: AutoSharding = { clusterId, numShardsInCluster }; await nwaku1.start({ discv5Discovery: true, peerExchange: true, relay: true, clusterId: clusterId, - shard: [2] + shard: [shard], + numShardsInNetwork: numShardsInCluster }); const enr1 = (await nwaku1.info()).enrUri; @@ -66,7 +64,8 @@ describe("Static Sharding: Peer Management", function () { discv5BootstrapNode: enr1, relay: true, clusterId: clusterId, - shard: [2] + shard: [shard], + numShardsInNetwork: numShardsInCluster }); const enr2 = (await nwaku2.info()).enrUri; @@ -77,12 +76,13 @@ describe("Static Sharding: Peer Management", function () { discv5BootstrapNode: enr2, relay: true, clusterId: clusterId, - shard: [2] + shard: [shard], + numShardsInNetwork: numShardsInCluster }); const nwaku3Ma = await nwaku3.getMultiaddrWithId(); waku = await createLightNode({ - networkConfig: shardInfo, + networkConfig: networkConfig, libp2p: { peerDiscovery: [ bootstrap({ list: [nwaku3Ma.toString()] }), @@ -118,9 +118,11 @@ describe("Static Sharding: Peer Management", function () { expect(dialPeerSpy.callCount).to.equal(3); }); - it("px service nodes not subscribed to the shard should not be dialed", async function () { + it("px service nodes in same cluster, no matter the shard, should be dialed", async function () { this.timeout(100_000); - const shardInfoToDial: ShardInfo = { clusterId: clusterId, shards: [2] }; + + const numShardsInCluster = 8; + const networkConfig: AutoSharding = { clusterId, numShardsInCluster }; // this service node is not subscribed to the shard await nwaku1.start({ @@ -128,7 +130,8 @@ describe("Static Sharding: Peer Management", function () { discv5Discovery: true, peerExchange: true, clusterId: clusterId, - shard: [1] + shard: [1], + numShardsInNetwork: numShardsInCluster }); const enr1 = (await nwaku1.info()).enrUri; @@ -139,7 +142,8 @@ describe("Static Sharding: Peer Management", function () { peerExchange: true, discv5BootstrapNode: enr1, clusterId: clusterId, - shard: [2] + shard: [2], + numShardsInNetwork: numShardsInCluster }); const enr2 = (await nwaku2.info()).enrUri; @@ -150,12 +154,13 @@ describe("Static Sharding: Peer Management", function () { peerExchange: true, discv5BootstrapNode: enr2, clusterId: clusterId, - shard: [2] + shard: [2], + numShardsInNetwork: numShardsInCluster }); const nwaku3Ma = await nwaku3.getMultiaddrWithId(); waku = await createLightNode({ - networkConfig: shardInfoToDial, + networkConfig: networkConfig, libp2p: { peerDiscovery: [ bootstrap({ list: [nwaku3Ma.toString()] }), @@ -178,7 +183,7 @@ describe("Static Sharding: Peer Management", function () { const tags = Array.from(peer.tags.keys()); if (tags.includes(Tags.PEER_EXCHANGE)) { pxPeersDiscovered.add(peerId); - if (pxPeersDiscovered.size === 1) { + if (pxPeersDiscovered.size === 2) { resolve(); } } @@ -187,7 +192,7 @@ describe("Static Sharding: Peer Management", function () { }); await delay(1000); - expect(dialPeerSpy.callCount).to.equal(2); + expect(dialPeerSpy.callCount).to.equal(3); }); }); }); @@ -219,9 +224,9 @@ describe("Autosharding: Peer Management", function () { it("all px service nodes subscribed to the shard topic should be dialed", async function () { this.timeout(100_000); - const contentTopicInfo: ContentTopicInfo = { + const networkConfig: AutoSharding = { clusterId: clusterId, - contentTopics: [ContentTopic] + numShardsInCluster: 8 }; await nwaku1.start({ @@ -259,7 +264,7 @@ describe("Autosharding: Peer Management", function () { const nwaku3Ma = await nwaku3.getMultiaddrWithId(); waku = await createLightNode({ - networkConfig: contentTopicInfo, + networkConfig: networkConfig, libp2p: { peerDiscovery: [ bootstrap({ list: [nwaku3Ma.toString()] }), @@ -294,82 +299,5 @@ describe("Autosharding: Peer Management", function () { expect(dialPeerSpy.callCount).to.equal(3); }); - - it("px service nodes not subscribed to the shard should not be dialed", async function () { - this.timeout(100_000); - const contentTopicInfoToDial: ContentTopicInfo = { - clusterId: clusterId, - contentTopics: [ContentTopic] - }; - - // this service node is not subscribed to the shard - await nwaku1.start({ - relay: true, - discv5Discovery: true, - peerExchange: true, - clusterId: 3, - shard: Shard - }); - - const enr1 = (await nwaku1.info()).enrUri; - - await nwaku2.start({ - relay: true, - discv5Discovery: true, - peerExchange: true, - discv5BootstrapNode: enr1, - clusterId: clusterId, - shard: Shard, - contentTopic: [ContentTopic] - }); - - const enr2 = (await nwaku2.info()).enrUri; - - await nwaku3.start({ - relay: true, - discv5Discovery: true, - peerExchange: true, - discv5BootstrapNode: enr2, - clusterId: clusterId, - shard: Shard, - contentTopic: [ContentTopic] - }); - const nwaku3Ma = await nwaku3.getMultiaddrWithId(); - - waku = await createLightNode({ - networkConfig: contentTopicInfoToDial, - libp2p: { - peerDiscovery: [ - bootstrap({ list: [nwaku3Ma.toString()] }), - wakuPeerExchangeDiscovery() - ] - } - }); - - dialPeerSpy = Sinon.spy((waku as any).libp2p, "dial"); - - await waku.start(); - - const pxPeersDiscovered = new Set(); - - await new Promise((resolve) => { - waku.libp2p.addEventListener("peer:discovery", (evt) => { - return void (async () => { - const peerId = evt.detail.id; - const peer = await waku.libp2p.peerStore.get(peerId); - const tags = Array.from(peer.tags.keys()); - if (tags.includes(Tags.PEER_EXCHANGE)) { - pxPeersDiscovered.add(peerId); - if (pxPeersDiscovered.size === 1) { - resolve(); - } - } - })(); - }); - }); - - await delay(1000); - expect(dialPeerSpy.callCount).to.equal(2); - }); }); }); diff --git a/packages/tests/tests/sharding/static_sharding.spec.ts b/packages/tests/tests/sharding/static_sharding.spec.ts index aa55abac45..9ca84ed25d 100644 --- a/packages/tests/tests/sharding/static_sharding.spec.ts +++ b/packages/tests/tests/sharding/static_sharding.spec.ts @@ -1,15 +1,10 @@ -import { LightNode, SingleShardInfo } from "@waku/interfaces"; +import { LightNode, StaticSharding } from "@waku/interfaces"; import { createEncoder, utf8ToBytes } from "@waku/sdk"; -import { - shardInfoToPubsubTopics, - singleShardInfosToShardInfo, - singleShardInfoToPubsubTopic -} from "@waku/utils"; +import { createRoutingInfo } from "@waku/utils"; import { expect } from "chai"; import { afterEachCustom, - beforeEachCustom, runMultipleNodes, ServiceNodesFleet, teardownNodesWithRedundancy @@ -30,13 +25,15 @@ describe("Static Sharding: Running Nodes", function () { } }); - it("shard 0", async function () { - const singleShardInfo = { clusterId: 0, shard: 0 }; - const shardInfo = singleShardInfosToShardInfo([singleShardInfo]); + it("Cluster id 0, shard 0", async function () { + const clusterId = 0; + const shardId = 0; + const networkConfig: StaticSharding = { clusterId }; + const routingInfo = createRoutingInfo(networkConfig, { shardId }); [serviceNodes, waku] = await runMultipleNodes( this.ctx, - shardInfo, + routingInfo, { lightpush: true, filter: true }, false, numServiceNodes, @@ -45,32 +42,27 @@ describe("Static Sharding: Running Nodes", function () { const encoder = createEncoder({ contentTopic: ContentTopic, - pubsubTopicShardInfo: singleShardInfo + routingInfo }); - expect(encoder.pubsubTopic).to.eq( - singleShardInfoToPubsubTopic(singleShardInfo) - ); const request = await waku.lightPush.send(encoder, { payload: utf8ToBytes("Hello World") }); expect(request.successes.length).to.eq(numServiceNodes); - expect( - await serviceNodes.messageCollector.waitForMessages(1, { - pubsubTopic: encoder.pubsubTopic - }) - ).to.eq(true); + expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq(true); }); // dedicated test for Default Cluster ID 0 - it("Cluster ID 0 - Default/Global Cluster", async function () { - const singleShardInfo = { clusterId: 0, shard: 1 }; - const shardInfo = singleShardInfosToShardInfo([singleShardInfo]); + it("Cluster ID 0, shard 1", async function () { + const clusterId = 0; + const shardId = 1; + const networkConfig: StaticSharding = { clusterId }; + const routingInfo = createRoutingInfo(networkConfig, { shardId }); [serviceNodes, waku] = await runMultipleNodes( this.ctx, - shardInfo, + routingInfo, { lightpush: true, filter: true }, false, numServiceNodes, @@ -79,7 +71,7 @@ describe("Static Sharding: Running Nodes", function () { const encoder = createEncoder({ contentTopic: ContentTopic, - pubsubTopicShardInfo: singleShardInfo + routingInfo }); const request = await waku.lightPush.send(encoder, { @@ -87,11 +79,7 @@ describe("Static Sharding: Running Nodes", function () { }); expect(request.successes.length).to.eq(numServiceNodes); - expect( - await serviceNodes.messageCollector.waitForMessages(1, { - pubsubTopic: shardInfoToPubsubTopics(shardInfo)[0] - }) - ).to.eq(true); + expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq(true); }); const numTest = 10; @@ -102,15 +90,15 @@ describe("Static Sharding: Running Nodes", function () { // Random shardId between 1 and 1000 const shardId = Math.floor(Math.random() * 1000) + 1; + const networkConfig: StaticSharding = { clusterId }; + const routingInfo = createRoutingInfo(networkConfig, { shardId }); + it(`random static sharding ${ i + 1 } - Cluster ID: ${clusterId}, Shard ID: ${shardId}`, async function () { - const singleShardInfo = { clusterId: clusterId, shard: shardId }; - const shardInfo = singleShardInfosToShardInfo([singleShardInfo]); - [serviceNodes, waku] = await runMultipleNodes( this.ctx, - shardInfo, + routingInfo, { lightpush: true, filter: true }, false, numServiceNodes, @@ -119,7 +107,7 @@ describe("Static Sharding: Running Nodes", function () { const encoder = createEncoder({ contentTopic: ContentTopic, - pubsubTopicShardInfo: singleShardInfo + routingInfo }); const request = await waku.lightPush.send(encoder, { @@ -127,75 +115,9 @@ describe("Static Sharding: Running Nodes", function () { }); expect(request.successes.length).to.eq(numServiceNodes); - expect( - await serviceNodes.messageCollector.waitForMessages(1, { - pubsubTopic: shardInfoToPubsubTopics(shardInfo)[0] - }) - ).to.eq(true); - }); - } - - describe("Others", function () { - const clusterId = 2; - - const singleShardInfo1: SingleShardInfo = { - clusterId: clusterId, - shard: 2 - }; - const singleShardInfo2: SingleShardInfo = { - clusterId: clusterId, - shard: 3 - }; - - beforeEachCustom(this, async () => { - [serviceNodes, waku] = await runMultipleNodes( - this.ctx, - { clusterId, shards: [2, 3] }, - { lightpush: true, filter: true }, - false, - numServiceNodes, + expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq( true ); }); - - afterEachCustom(this, async () => { - if (serviceNodes) { - await teardownNodesWithRedundancy(serviceNodes, waku ?? []); - } - }); - - it("configure the node with multiple pubsub topics", async function () { - const encoder1 = createEncoder({ - contentTopic: ContentTopic, - pubsubTopicShardInfo: singleShardInfo1 - }); - - const encoder2 = createEncoder({ - contentTopic: ContentTopic, - pubsubTopicShardInfo: singleShardInfo2 - }); - - const request1 = await waku?.lightPush.send(encoder1, { - payload: utf8ToBytes("Hello World2") - }); - - expect(request1?.successes.length).to.eq(numServiceNodes); - expect( - await serviceNodes?.messageCollector.waitForMessages(1, { - pubsubTopic: encoder1.pubsubTopic - }) - ).to.eq(true); - - const request2 = await waku?.lightPush.send(encoder2, { - payload: utf8ToBytes("Hello World3") - }); - - expect(request2?.successes.length).to.eq(numServiceNodes); - expect( - await serviceNodes?.messageCollector.waitForMessages(1, { - pubsubTopic: encoder2.pubsubTopic - }) - ).to.eq(true); - }); - }); + } }); diff --git a/packages/tests/tests/store/cursor.node.spec.ts b/packages/tests/tests/store/cursor.node.spec.ts index cd3316a65d..ebac2d1a3a 100644 --- a/packages/tests/tests/store/cursor.node.spec.ts +++ b/packages/tests/tests/store/cursor.node.spec.ts @@ -14,9 +14,11 @@ import { runStoreNodes, sendMessages, startAndConnectLightNode, + TestContentTopic, TestDecoder, TestDecoder2, - TestShardInfo, + TestNetworkConfig, + TestRoutingInfo, totalMsgs } from "./utils.js"; @@ -27,7 +29,12 @@ describe("Waku Store, cursor", function () { let nwaku: ServiceNode; beforeEachCustom(this, async () => { - [nwaku, waku] = await runStoreNodes(this.ctx, TestShardInfo); + [nwaku, waku] = await runStoreNodes( + this.ctx, + TestNetworkConfig, + [], + [TestContentTopic] + ); }); afterEachCustom(this, async () => { @@ -43,11 +50,12 @@ describe("Waku Store, cursor", function () { [110, 120] ].forEach(([cursorIndex, messageCount]) => { it(`Passing a valid cursor at ${cursorIndex} index when there are ${messageCount} messages`, async function () { + console.log(nwaku); await sendMessages( nwaku, messageCount, TestDecoder.contentTopic, - TestDecoder.pubsubTopic + TestRoutingInfo ); // messages in reversed order (first message at last index) @@ -95,9 +103,9 @@ describe("Waku Store, cursor", function () { nwaku, totalMsgs, TestDecoder.contentTopic, - TestDecoder.pubsubTopic + TestRoutingInfo ); - waku2 = await startAndConnectLightNode(nwaku, TestShardInfo); + waku2 = await startAndConnectLightNode(nwaku, TestNetworkConfig); // messages in reversed order (first message at last index) const messages: DecodedMessage[] = []; @@ -137,12 +145,7 @@ describe("Waku Store, cursor", function () { this.skip(); } - await sendMessages( - nwaku, - totalMsgs, - TestDecoder.contentTopic, - TestDecoder.pubsubTopic - ); + await sendMessages(nwaku, totalMsgs, TestContentTopic, TestRoutingInfo); const messages: DecodedMessage[] = []; for await (const page of waku.store.queryGenerator([TestDecoder])) { @@ -170,7 +173,7 @@ describe("Waku Store, cursor", function () { if ( !(err instanceof Error) || !err.message.includes( - "Store query failed with status code: 300, description: BAD_RESPONSE: archive error: DRIVER_ERROR: cursor not found" + "Store query failed with status code: 300, description: BAD_RESPONSE: archive error: DIRVER_ERROR: cursor not found" ) ) { throw err; @@ -187,7 +190,7 @@ describe("Waku Store, cursor", function () { nwaku, totalMsgs, TestDecoder.contentTopic, - TestDecoder.pubsubTopic + TestRoutingInfo ); const messages: DecodedMessage[] = []; @@ -196,7 +199,7 @@ describe("Waku Store, cursor", function () { messages.push(msg as DecodedMessage); } } - messages[5].pubsubTopic = TestDecoder2.pubsubTopic; + messages[5].pubsubTopic = TestDecoder2.routingInfo.pubsubTopic; const cursor = waku.store.createCursor(messages[5]); try { @@ -210,7 +213,7 @@ describe("Waku Store, cursor", function () { if ( !(err instanceof Error) || !err.message.includes( - "Store query failed with status code: 300, description: BAD_RESPONSE: archive error: DRIVER_ERROR: cursor not found" + "Store query failed with status code: 300, description: BAD_RESPONSE: archive error: DIRVER_ERROR: cursor not found" ) ) { throw err; diff --git a/packages/tests/tests/store/different_static_shards.spec.ts b/packages/tests/tests/store/different_static_shards.spec.ts new file mode 100644 index 0000000000..bb71b4a2d4 --- /dev/null +++ b/packages/tests/tests/store/different_static_shards.spec.ts @@ -0,0 +1,190 @@ +import { createDecoder } from "@waku/core"; +import { IMessage, LightNode, ShardId, StaticSharding } from "@waku/interfaces"; +import { Protocols } from "@waku/sdk"; +import { createRoutingInfo } from "@waku/utils"; +import { expect } from "chai"; + +import { + afterEachCustom, + beforeEachCustom, + makeLogFileName, + ServiceNode, + tearDownNodes +} from "../../src/index.js"; + +import { + processQueriedMessages, + runStoreNodes, + sendMessages, + totalMsgs +} from "./utils.js"; + +const StaticTestClusterId = 2; +const StaticTestRelayShards = [1, 2]; +const StaticTestNetworkConfig: StaticSharding = { + clusterId: StaticTestClusterId +}; + +const TestShardOne: ShardId = 1; +const TestContentTopicOne = "/test/0/one/proto"; +const TestRoutingInfoOne = createRoutingInfo(StaticTestNetworkConfig, { + shardId: TestShardOne +}); + +const TestDecoderShardOne = createDecoder( + TestContentTopicOne, + TestRoutingInfoOne +); + +const TestShardTwo: ShardId = 2; +const TestContentTopicTwo = "/test/0/two/proto"; +const TestRoutingInfoTwo = createRoutingInfo(StaticTestNetworkConfig, { + shardId: TestShardTwo +}); + +const TestDecoderShardTwo = createDecoder( + TestContentTopicTwo, + TestRoutingInfoTwo +); + +// TODO: Same tests but with auto-sharding +describe("Waku Store, different static shards", function () { + this.timeout(15000); + let waku: LightNode; + let nwaku: ServiceNode; + let nwaku2: ServiceNode; + + beforeEachCustom(this, async () => { + [nwaku, waku] = await runStoreNodes( + this.ctx, + StaticTestNetworkConfig, + StaticTestRelayShards + ); + }); + + afterEachCustom(this, async () => { + await tearDownNodes([nwaku, nwaku2], waku); + }); + + it("Generator, one shard", async function () { + await sendMessages( + nwaku, + totalMsgs, + TestContentTopicOne, + TestRoutingInfoOne + ); + + const messages = await processQueriedMessages( + waku, + [TestDecoderShardOne], + TestDecoderShardOne.routingInfo.pubsubTopic + ); + + expect(messages?.length).eq(totalMsgs); + const result = messages?.findIndex((msg) => { + return msg.payload![0]! === 0; + }); + expect(result).to.not.eq(-1); + }); + + it("Generator, 2 different shards", async function () { + this.timeout(10000); + + const totalMsgs = 10; + await sendMessages( + nwaku, + totalMsgs, + TestContentTopicOne, + TestRoutingInfoOne + ); + await sendMessages( + nwaku, + totalMsgs, + TestContentTopicTwo, + TestRoutingInfoTwo + ); + + const customMessages = await processQueriedMessages( + waku, + [TestDecoderShardOne], + TestDecoderShardOne.routingInfo.pubsubTopic + ); + expect(customMessages?.length).eq(totalMsgs); + const result1 = customMessages?.findIndex((msg) => { + return msg.payload![0]! === 0; + }); + expect(result1).to.not.eq(-1); + + const testMessages = await processQueriedMessages( + waku, + [TestDecoderShardTwo], + TestDecoderShardTwo.routingInfo.pubsubTopic + ); + expect(testMessages?.length).eq(totalMsgs); + const result2 = testMessages?.findIndex((msg) => { + return msg.payload![0]! === 0; + }); + expect(result2).to.not.eq(-1); + }); + + it("Generator, 2 nwaku nodes each with different shards", async function () { + this.timeout(10000); + + await tearDownNodes([nwaku], []); + + // make sure each nwaku node operates on dedicated shard only + nwaku = new ServiceNode(makeLogFileName(this) + "1"); + await nwaku.start({ + store: true, + clusterId: StaticTestClusterId, + shard: [1], + relay: true + }); + + // Set up and start a new nwaku node with Default Pubsubtopic + nwaku2 = new ServiceNode(makeLogFileName(this) + "2"); + await nwaku2.start({ + store: true, + clusterId: StaticTestClusterId, + shard: [2], + relay: true + }); + + const totalMsgs = 10; + await sendMessages( + nwaku, + totalMsgs, + TestDecoderShardOne.contentTopic, + TestDecoderShardOne.routingInfo + ); + await sendMessages( + nwaku2, + totalMsgs, + TestDecoderShardTwo.contentTopic, + TestDecoderShardTwo.routingInfo + ); + + await waku.dial(await nwaku.getMultiaddrWithId()); + await waku.dial(await nwaku2.getMultiaddrWithId()); + await waku.waitForPeers([Protocols.Store]); + + let customMessages: IMessage[] = []; + let testMessages: IMessage[] = []; + + while ( + customMessages.length != totalMsgs || + testMessages.length != totalMsgs + ) { + customMessages = await processQueriedMessages( + waku, + [TestDecoderShardOne], + TestDecoderShardOne.routingInfo.pubsubTopic + ); + testMessages = await processQueriedMessages( + waku, + [TestDecoderShardTwo], + TestDecoderShardTwo.routingInfo.pubsubTopic + ); + } + }); +}); diff --git a/packages/tests/tests/store/error_handling.node.spec.ts b/packages/tests/tests/store/error_handling.node.spec.ts index 89f80d5cdf..ebb554fd6f 100644 --- a/packages/tests/tests/store/error_handling.node.spec.ts +++ b/packages/tests/tests/store/error_handling.node.spec.ts @@ -1,5 +1,5 @@ import { IMessage, type LightNode } from "@waku/interfaces"; -import { determinePubsubTopic } from "@waku/utils"; +import { formatPubsubTopic } from "@waku/utils"; import { expect } from "chai"; import { @@ -14,7 +14,7 @@ import { runStoreNodes, TestDecoder, TestDecoder2, - TestShardInfo + TestNetworkConfig } from "./utils.js"; describe("Waku Store, error handling", function () { @@ -23,7 +23,7 @@ describe("Waku Store, error handling", function () { let nwaku: ServiceNode; beforeEachCustom(this, async () => { - [nwaku, waku] = await runStoreNodes(this.ctx, TestShardInfo); + [nwaku, waku] = await runStoreNodes(this.ctx, TestNetworkConfig); }); afterEachCustom(this, async () => { @@ -68,7 +68,7 @@ describe("Waku Store, error handling", function () { }); it("Query Generator, No message returned", async function () { - const WrongTestPubsubTopic = determinePubsubTopic("/test/1/wrong/utf8"); + const WrongTestPubsubTopic = formatPubsubTopic(43, 53); const messages = await processQueriedMessages( waku, [TestDecoder], diff --git a/packages/tests/tests/store/index.node.spec.ts b/packages/tests/tests/store/index.node.spec.ts index c95acffc86..4d6d23470e 100644 --- a/packages/tests/tests/store/index.node.spec.ts +++ b/packages/tests/tests/store/index.node.spec.ts @@ -14,6 +14,7 @@ import { createDecoder as createSymDecoder, createEncoder as createSymEncoder } from "@waku/message-encryption/symmetric"; +import { createRoutingInfo } from "@waku/utils"; import { bytesToUtf8, utf8ToBytes } from "@waku/utils/bytes"; import { expect } from "chai"; import { equals } from "uint8arrays/equals"; @@ -35,12 +36,11 @@ import { runStoreNodes, sendMessages, startAndConnectLightNode, - TestContentTopic1, + TestContentTopic, TestDecoder, - TestDecoder2, TestEncoder, - TestPubsubTopic1, - TestShardInfo, + TestNetworkConfig, + TestRoutingInfo, totalMsgs } from "./utils.js"; @@ -51,7 +51,7 @@ describe("Waku Store, general", function () { let nwaku: ServiceNode; beforeEachCustom(this, async () => { - [nwaku, waku] = await runStoreNodes(this.ctx, TestShardInfo); + [nwaku, waku] = await runStoreNodes(this.ctx, TestNetworkConfig); }); afterEachCustom(this, async () => { @@ -63,13 +63,13 @@ describe("Waku Store, general", function () { nwaku, totalMsgs, TestDecoder.contentTopic, - TestDecoder.pubsubTopic + TestRoutingInfo ); const messages = await processQueriedMessages( waku, [TestDecoder], - TestDecoder.pubsubTopic + TestRoutingInfo.pubsubTopic ); expect(messages?.length).eq(totalMsgs); @@ -89,7 +89,7 @@ describe("Waku Store, general", function () { payload: utf8ToBytes(testItem["value"]), contentTopic: TestDecoder.contentTopic }), - TestDecoder.pubsubTopic + TestRoutingInfo ) ).to.eq(true); await delay(1); // to ensure each timestamp is unique. @@ -99,7 +99,7 @@ describe("Waku Store, general", function () { messageCollector.list = await processQueriedMessages( waku, [TestDecoder], - TestDecoder.pubsubTopic + TestRoutingInfo.pubsubTopic ); // checking that all message sent were retrieved @@ -111,57 +111,69 @@ describe("Waku Store, general", function () { }); it("Query generator for multiple messages with multiple decoders", async function () { - const SecondDecoder = createDecoder( - TestDecoder2.contentTopic, - TestDecoder.pubsubTopic - ); + const secondContentTopic = "/test/1/waku-store-two/utf8"; + const secondRoutingInfo = createRoutingInfo(TestNetworkConfig, { + contentTopic: secondContentTopic + }); + const secondDecoder = createDecoder(secondContentTopic, secondRoutingInfo); await nwaku.sendMessage( ServiceNode.toMessageRpcQuery({ payload: utf8ToBytes("M1"), - contentTopic: TestDecoder.contentTopic + contentTopic: TestContentTopic }), - TestDecoder.pubsubTopic + TestRoutingInfo ); await nwaku.sendMessage( ServiceNode.toMessageRpcQuery({ payload: utf8ToBytes("M2"), - contentTopic: SecondDecoder.contentTopic + contentTopic: secondContentTopic }), - SecondDecoder.pubsubTopic + secondRoutingInfo ); const messageCollector = new MessageCollector(nwaku); messageCollector.list = await processQueriedMessages( waku, - [TestDecoder, SecondDecoder], - TestDecoder.pubsubTopic + [TestDecoder, secondDecoder], + TestRoutingInfo.pubsubTopic ); expect(messageCollector.hasMessage(TestDecoder.contentTopic, "M1")).to.eq( true ); - expect(messageCollector.hasMessage(SecondDecoder.contentTopic, "M2")).to.eq( - true - ); + expect(messageCollector.hasMessage(secondContentTopic, "M2")).to.eq(true); }); it("Query generator for multiple messages with different content topic format", async function () { for (const testItem of TEST_STRING) { + if (testItem.invalidContentTopic) continue; + + const contentTopic = `/test/1/${testItem.value}/proto`; + const routingInfo = createRoutingInfo(TestNetworkConfig, { + contentTopic + }); expect( await nwaku.sendMessage( ServiceNode.toMessageRpcQuery({ payload: utf8ToBytes(messageText), - contentTopic: testItem["value"] + contentTopic }), - TestDecoder.pubsubTopic + routingInfo ) ).to.eq(true); await delay(1); // to ensure each timestamp is unique. } for (const testItem of TEST_STRING) { + if (testItem.invalidContentTopic) continue; + + const contentTopic = `/test/1/${testItem.value}/proto`; + const routingInfo = createRoutingInfo(TestNetworkConfig, { + contentTopic + }); + for await (const query of waku.store.queryGenerator([ - createDecoder(testItem["value"], TestDecoder.pubsubTopic) + createDecoder(contentTopic, routingInfo) ])) { for await (const msg of query) { expect(equals(msg!.payload, utf8ToBytes(messageText))).to.eq(true); @@ -175,7 +187,7 @@ describe("Waku Store, general", function () { nwaku, totalMsgs, TestDecoder.contentTopic, - TestDecoder.pubsubTopic + TestRoutingInfo ); const messages: IMessage[] = []; @@ -201,7 +213,7 @@ describe("Waku Store, general", function () { nwaku, totalMsgs, TestDecoder.contentTopic, - TestDecoder.pubsubTopic + TestRoutingInfo ); const desiredMsgs = 14; @@ -254,32 +266,28 @@ describe("Waku Store, general", function () { const eciesEncoder = createEciesEncoder({ contentTopic: asymTopic, publicKey, - pubsubTopic: TestPubsubTopic1 + routingInfo: TestRoutingInfo }); const symEncoder = createSymEncoder({ contentTopic: symTopic, symKey, - pubsubTopic: TestPubsubTopic1 + routingInfo: TestRoutingInfo }); const otherEncoder = createEciesEncoder({ - contentTopic: TestContentTopic1, - pubsubTopic: TestPubsubTopic1, + contentTopic: TestContentTopic, + routingInfo: TestRoutingInfo, publicKey: getPublicKey(generatePrivateKey()) }); const eciesDecoder = createEciesDecoder( asymTopic, - privateKey, - TestDecoder.pubsubTopic - ); - const symDecoder = createSymDecoder( - symTopic, - symKey, - TestDecoder.pubsubTopic + TestRoutingInfo, + privateKey ); + const symDecoder = createSymDecoder(symTopic, TestRoutingInfo, symKey); - waku2 = await startAndConnectLightNode(nwaku, TestShardInfo); + waku2 = await startAndConnectLightNode(nwaku, TestNetworkConfig); const nimWakuMultiaddr = await nwaku.getMultiaddrWithId(); await waku2.dial(nimWakuMultiaddr); @@ -320,7 +328,7 @@ describe("Waku Store, general", function () { nwaku, totalMsgs, TestDecoder.contentTopic, - TestDecoder.pubsubTopic + TestRoutingInfo ); const desiredMsgs = 14; @@ -339,17 +347,12 @@ describe("Waku Store, general", function () { it("Query generator for 2000 messages", async function () { this.timeout(40000); - await sendMessages( - nwaku, - 2000, - TestDecoder.contentTopic, - TestDecoder.pubsubTopic - ); + await sendMessages(nwaku, 2000, TestDecoder.contentTopic, TestRoutingInfo); const messages = await processQueriedMessages( waku, [TestDecoder], - TestDecoder.pubsubTopic + TestRoutingInfo.pubsubTopic ); expect(messages?.length).eq(2000); diff --git a/packages/tests/tests/store/message_hash.spec.ts b/packages/tests/tests/store/message_hash.spec.ts index bba98ae109..d97077e747 100644 --- a/packages/tests/tests/store/message_hash.spec.ts +++ b/packages/tests/tests/store/message_hash.spec.ts @@ -13,7 +13,8 @@ import { runStoreNodes, sendMessages, TestDecoder, - TestShardInfo, + TestNetworkConfig, + TestRoutingInfo, totalMsgs } from "./utils.js"; @@ -23,7 +24,7 @@ describe("Waku Store, message hash query", function () { let nwaku: ServiceNode; beforeEachCustom(this, async () => { - [nwaku, waku] = await runStoreNodes(this.ctx, TestShardInfo); + [nwaku, waku] = await runStoreNodes(this.ctx, TestNetworkConfig); }); afterEachCustom(this, async () => { @@ -35,7 +36,7 @@ describe("Waku Store, message hash query", function () { nwaku, totalMsgs, TestDecoder.contentTopic, - TestDecoder.pubsubTopic, + TestDecoder.routingInfo, true ); @@ -54,11 +55,11 @@ describe("Waku Store, message hash query", function () { nwaku, totalMsgs, TestDecoder.contentTopic, - TestDecoder.pubsubTopic, + TestRoutingInfo, true ); const messageHashes = sentMessages.map((msg) => - messageHash(TestDecoder.pubsubTopic, { + messageHash(TestRoutingInfo.pubsubTopic, { payload: Buffer.from(msg.payload, "base64"), contentTopic: msg.contentTopic || TestDecoder.contentTopic, timestamp: msg.timestamp || undefined, @@ -72,7 +73,7 @@ describe("Waku Store, message hash query", function () { const messages: IDecodedMessage[] = []; for await (const page of waku.store.queryGenerator([TestDecoder], { messageHashes, - pubsubTopic: TestDecoder.pubsubTopic + routingInfo: TestRoutingInfo })) { for await (const msg of page) { messages.push(msg as IDecodedMessage); diff --git a/packages/tests/tests/store/multiple_pubsub.spec.ts b/packages/tests/tests/store/multiple_pubsub.spec.ts deleted file mode 100644 index caf4204765..0000000000 --- a/packages/tests/tests/store/multiple_pubsub.spec.ts +++ /dev/null @@ -1,438 +0,0 @@ -import { createDecoder } from "@waku/core"; -import type { ContentTopicInfo, IMessage, LightNode } from "@waku/interfaces"; -import { createLightNode, Protocols } from "@waku/sdk"; -import { - contentTopicToPubsubTopic, - pubsubTopicToSingleShardInfo -} from "@waku/utils"; -import { expect } from "chai"; - -import { - afterEachCustom, - beforeEachCustom, - makeLogFileName, - NOISE_KEY_1, - ServiceNode, - tearDownNodes -} from "../../src/index.js"; - -import { - processQueriedMessages, - runStoreNodes, - sendMessages, - sendMessagesAutosharding, - TestDecoder, - TestDecoder2, - TestShardInfo, - totalMsgs -} from "./utils.js"; - -describe("Waku Store, custom pubsub topic", function () { - this.timeout(15000); - let waku: LightNode; - let nwaku: ServiceNode; - let nwaku2: ServiceNode; - - beforeEachCustom(this, async () => { - [nwaku, waku] = await runStoreNodes(this.ctx, TestShardInfo); - }); - - afterEachCustom(this, async () => { - await tearDownNodes([nwaku, nwaku2], waku); - }); - - it("Generator, custom pubsub topic", async function () { - await sendMessages( - nwaku, - totalMsgs, - TestDecoder.contentTopic, - TestDecoder.pubsubTopic - ); - - const messages = await processQueriedMessages( - waku, - [TestDecoder], - TestDecoder.pubsubTopic - ); - - expect(messages?.length).eq(totalMsgs); - const result = messages?.findIndex((msg) => { - return msg.payload![0]! === 0; - }); - expect(result).to.not.eq(-1); - }); - - it("Generator, 2 different pubsubtopics", async function () { - this.timeout(10000); - - const totalMsgs = 10; - await sendMessages( - nwaku, - totalMsgs, - TestDecoder.contentTopic, - TestDecoder.pubsubTopic - ); - await sendMessages( - nwaku, - totalMsgs, - TestDecoder2.contentTopic, - TestDecoder2.pubsubTopic - ); - - const customMessages = await processQueriedMessages( - waku, - [TestDecoder], - TestDecoder.pubsubTopic - ); - expect(customMessages?.length).eq(totalMsgs); - const result1 = customMessages?.findIndex((msg) => { - return msg.payload![0]! === 0; - }); - expect(result1).to.not.eq(-1); - - const testMessages = await processQueriedMessages( - waku, - [TestDecoder2], - TestDecoder2.pubsubTopic - ); - expect(testMessages?.length).eq(totalMsgs); - const result2 = testMessages?.findIndex((msg) => { - return msg.payload![0]! === 0; - }); - expect(result2).to.not.eq(-1); - }); - - it("Generator, 2 nwaku nodes each with different pubsubtopics", async function () { - this.timeout(10000); - - await tearDownNodes([nwaku], []); - - // make sure each nwaku node operates on dedicated shard only - nwaku = new ServiceNode(makeLogFileName(this) + "1"); - await nwaku.start({ - store: true, - clusterId: TestShardInfo.clusterId, - shard: [TestShardInfo.shards[0]], - relay: true - }); - - // Set up and start a new nwaku node with Default Pubsubtopic - nwaku2 = new ServiceNode(makeLogFileName(this) + "2"); - await nwaku2.start({ - store: true, - clusterId: TestShardInfo.clusterId, - shard: [TestShardInfo.shards[1]], - relay: true - }); - - const totalMsgs = 10; - await sendMessages( - nwaku, - totalMsgs, - TestDecoder.contentTopic, - TestDecoder.pubsubTopic - ); - await sendMessages( - nwaku2, - totalMsgs, - TestDecoder2.contentTopic, - TestDecoder2.pubsubTopic - ); - - await waku.dial(await nwaku.getMultiaddrWithId()); - await waku.dial(await nwaku2.getMultiaddrWithId()); - await waku.waitForPeers([Protocols.Store]); - - let customMessages: IMessage[] = []; - let testMessages: IMessage[] = []; - - while ( - customMessages.length != totalMsgs || - testMessages.length != totalMsgs - ) { - customMessages = await processQueriedMessages( - waku, - [TestDecoder], - TestDecoder.pubsubTopic - ); - testMessages = await processQueriedMessages( - waku, - [TestDecoder2], - TestDecoder2.pubsubTopic - ); - } - }); -}); - -// TODO: blocked by https://github.com/waku-org/nwaku/issues/3362 -describe.skip("Waku Store (Autosharding), custom pubsub topic", function () { - this.timeout(15000); - let waku: LightNode; - let nwaku: ServiceNode; - let nwaku2: ServiceNode; - - const customContentTopic1 = "/waku/2/content/utf8"; - const customContentTopic2 = "/myapp/1/latest/proto"; - const clusterId = 5; - const Shard2 = [1]; - const autoshardingPubsubTopic1 = contentTopicToPubsubTopic( - customContentTopic1, - clusterId - ); - const autoshardingPubsubTopic2 = contentTopicToPubsubTopic( - customContentTopic2, - clusterId - ); - const customDecoder1 = createDecoder( - customContentTopic1, - pubsubTopicToSingleShardInfo(autoshardingPubsubTopic1) - ); - const customDecoder2 = createDecoder( - customContentTopic2, - pubsubTopicToSingleShardInfo(autoshardingPubsubTopic2) - ); - const contentTopicInfoBothShards: ContentTopicInfo = { - clusterId, - contentTopics: [customContentTopic1, customContentTopic2] - }; - - beforeEachCustom(this, async () => { - [nwaku, waku] = await runStoreNodes(this.ctx, contentTopicInfoBothShards); - }); - - afterEachCustom(this, async () => { - await tearDownNodes([nwaku, nwaku2], waku); - }); - - it("Generator, custom pubsub topic", async function () { - await sendMessagesAutosharding(nwaku, totalMsgs, customContentTopic1); - - const messages = await processQueriedMessages( - waku, - [customDecoder1], - autoshardingPubsubTopic1 - ); - - expect(messages?.length).eq(totalMsgs); - const result = messages?.findIndex((msg) => { - return msg.payload![0]! === 0; - }); - expect(result).to.not.eq(-1); - }); - - it("Generator, 2 different pubsubtopics", async function () { - this.timeout(10000); - - const totalMsgs = 10; - await sendMessagesAutosharding(nwaku, totalMsgs, customContentTopic1); - await sendMessagesAutosharding(nwaku, totalMsgs, customContentTopic2); - - const customMessages = await processQueriedMessages( - waku, - [customDecoder1], - autoshardingPubsubTopic1 - ); - expect(customMessages?.length).eq(totalMsgs); - const result1 = customMessages?.findIndex((msg) => { - return msg.payload![0]! === 0; - }); - expect(result1).to.not.eq(-1); - - const testMessages = await processQueriedMessages( - waku, - [customDecoder2], - autoshardingPubsubTopic2 - ); - expect(testMessages?.length).eq(totalMsgs); - const result2 = testMessages?.findIndex((msg) => { - return msg.payload![0]! === 0; - }); - expect(result2).to.not.eq(-1); - }); - - it("Generator, 2 nwaku nodes each with different pubsubtopics", async function () { - this.timeout(10000); - - // Set up and start a new nwaku node with Default Pubsubtopic - nwaku2 = new ServiceNode(makeLogFileName(this) + "2"); - await nwaku2.start({ - store: true, - contentTopic: [customContentTopic2], - relay: true, - clusterId, - shard: Shard2 - }); - await nwaku2.ensureSubscriptionsAutosharding([customContentTopic2]); - - const totalMsgs = 10; - await sendMessagesAutosharding(nwaku, totalMsgs, customContentTopic1); - await sendMessagesAutosharding(nwaku2, totalMsgs, customContentTopic2); - - waku = await createLightNode({ - staticNoiseKey: NOISE_KEY_1, - networkConfig: contentTopicInfoBothShards - }); - await waku.start(); - - await waku.dial(await nwaku.getMultiaddrWithId()); - await waku.dial(await nwaku2.getMultiaddrWithId()); - await waku.waitForPeers([Protocols.Store]); - - let customMessages: IMessage[] = []; - let testMessages: IMessage[] = []; - - while ( - customMessages.length != totalMsgs || - testMessages.length != totalMsgs - ) { - customMessages = await processQueriedMessages( - waku, - [customDecoder1], - autoshardingPubsubTopic1 - ); - testMessages = await processQueriedMessages( - waku, - [customDecoder2], - autoshardingPubsubTopic2 - ); - } - }); -}); - -describe("Waku Store (named sharding), custom pubsub topic", function () { - this.timeout(15000); - let waku: LightNode; - let nwaku: ServiceNode; - let nwaku2: ServiceNode; - - beforeEachCustom(this, async () => { - [nwaku, waku] = await runStoreNodes(this.ctx, TestShardInfo); - }); - - afterEachCustom(this, async () => { - await tearDownNodes([nwaku, nwaku2], waku); - }); - - it("Generator, custom pubsub topic", async function () { - await sendMessages( - nwaku, - totalMsgs, - TestDecoder.contentTopic, - TestDecoder.pubsubTopic - ); - - const messages = await processQueriedMessages( - waku, - [TestDecoder], - TestDecoder.pubsubTopic - ); - - expect(messages?.length).eq(totalMsgs); - const result = messages?.findIndex((msg) => { - return msg.payload![0]! === 0; - }); - expect(result).to.not.eq(-1); - }); - - it("Generator, 2 different pubsubtopics", async function () { - this.timeout(10000); - - const totalMsgs = 10; - await sendMessages( - nwaku, - totalMsgs, - TestDecoder.contentTopic, - TestDecoder.pubsubTopic - ); - await sendMessages( - nwaku, - totalMsgs, - TestDecoder2.contentTopic, - TestDecoder2.pubsubTopic - ); - - const customMessages = await processQueriedMessages( - waku, - [TestDecoder], - TestDecoder.pubsubTopic - ); - expect(customMessages?.length).eq(totalMsgs); - const result1 = customMessages?.findIndex((msg) => { - return msg.payload![0]! === 0; - }); - expect(result1).to.not.eq(-1); - - const testMessages = await processQueriedMessages( - waku, - [TestDecoder2], - TestDecoder2.pubsubTopic - ); - expect(testMessages?.length).eq(totalMsgs); - const result2 = testMessages?.findIndex((msg) => { - return msg.payload![0]! === 0; - }); - expect(result2).to.not.eq(-1); - }); - - it("Generator, 2 nwaku nodes each with different pubsubtopics", async function () { - this.timeout(10000); - - await tearDownNodes([nwaku], []); - - // make sure each nwaku node operates on dedicated shard only - nwaku = new ServiceNode(makeLogFileName(this) + "1"); - await nwaku.start({ - store: true, - clusterId: TestShardInfo.clusterId, - shard: [TestShardInfo.shards[0]], - relay: true - }); - - // Set up and start a new nwaku node with Default Pubsubtopic - nwaku2 = new ServiceNode(makeLogFileName(this) + "2"); - await nwaku2.start({ - store: true, - relay: true, - clusterId: TestShardInfo.clusterId, - shard: TestShardInfo.shards - }); - await nwaku2.ensureSubscriptions([TestDecoder2.pubsubTopic]); - - const totalMsgs = 10; - await sendMessages( - nwaku, - totalMsgs, - TestDecoder.contentTopic, - TestDecoder.pubsubTopic - ); - await sendMessages( - nwaku2, - totalMsgs, - TestDecoder2.contentTopic, - TestDecoder2.pubsubTopic - ); - - await waku.dial(await nwaku.getMultiaddrWithId()); - await waku.dial(await nwaku2.getMultiaddrWithId()); - await waku.waitForPeers([Protocols.Store]); - - let customMessages: IMessage[] = []; - let testMessages: IMessage[] = []; - - while ( - customMessages.length != totalMsgs || - testMessages.length != totalMsgs - ) { - customMessages = await processQueriedMessages( - waku, - [TestDecoder], - TestDecoder.pubsubTopic - ); - testMessages = await processQueriedMessages( - waku, - [TestDecoder2], - TestDecoder2.pubsubTopic - ); - } - }); -}); diff --git a/packages/tests/tests/store/order.node.spec.ts b/packages/tests/tests/store/order.node.spec.ts index 3de1f30005..bddea7a4ad 100644 --- a/packages/tests/tests/store/order.node.spec.ts +++ b/packages/tests/tests/store/order.node.spec.ts @@ -13,7 +13,8 @@ import { runStoreNodes, sendMessages, TestDecoder, - TestShardInfo, + TestNetworkConfig, + TestRoutingInfo, totalMsgs } from "./utils.js"; @@ -23,7 +24,7 @@ describe("Waku Store, order", function () { let nwaku: ServiceNode; beforeEachCustom(this, async () => { - [nwaku, waku] = await runStoreNodes(this.ctx, TestShardInfo); + [nwaku, waku] = await runStoreNodes(this.ctx, TestNetworkConfig); }); afterEachCustom(this, async () => { @@ -36,7 +37,7 @@ describe("Waku Store, order", function () { nwaku, totalMsgs, TestDecoder.contentTopic, - TestDecoder.pubsubTopic + TestRoutingInfo ); const messages: IMessage[] = []; @@ -64,7 +65,7 @@ describe("Waku Store, order", function () { nwaku, totalMsgs, TestDecoder.contentTopic, - TestDecoder.pubsubTopic + TestRoutingInfo ); const messages: IMessage[] = []; @@ -95,7 +96,7 @@ describe("Waku Store, order", function () { nwaku, totalMsgs, TestDecoder.contentTopic, - TestDecoder.pubsubTopic + TestRoutingInfo ); const messages: IMessage[] = []; diff --git a/packages/tests/tests/store/page_size.node.spec.ts b/packages/tests/tests/store/page_size.node.spec.ts index 019b58bd51..66ce158334 100644 --- a/packages/tests/tests/store/page_size.node.spec.ts +++ b/packages/tests/tests/store/page_size.node.spec.ts @@ -12,7 +12,8 @@ import { runStoreNodes, sendMessages, TestDecoder, - TestShardInfo + TestNetworkConfig, + TestRoutingInfo } from "./utils.js"; describe("Waku Store, page size", function () { @@ -21,7 +22,7 @@ describe("Waku Store, page size", function () { let nwaku: ServiceNode; beforeEachCustom(this, async () => { - [nwaku, waku] = await runStoreNodes(this.ctx, TestShardInfo); + [nwaku, waku] = await runStoreNodes(this.ctx, TestNetworkConfig); }); afterEachCustom(this, async () => { @@ -42,7 +43,7 @@ describe("Waku Store, page size", function () { nwaku, messageCount, TestDecoder.contentTopic, - TestDecoder.pubsubTopic + TestRoutingInfo ); // Determine effectivePageSize for test expectations @@ -77,12 +78,7 @@ describe("Waku Store, page size", function () { // Possible issue here because pageSize differs across implementations it("Default pageSize", async function () { - await sendMessages( - nwaku, - 20, - TestDecoder.contentTopic, - TestDecoder.pubsubTopic - ); + await sendMessages(nwaku, 20, TestDecoder.contentTopic, TestRoutingInfo); let messagesRetrieved = 0; for await (const query of waku.store.queryGenerator([TestDecoder])) { diff --git a/packages/tests/tests/store/sorting.node.spec.ts b/packages/tests/tests/store/sorting.node.spec.ts index 63bd8e4591..46b45f0133 100644 --- a/packages/tests/tests/store/sorting.node.spec.ts +++ b/packages/tests/tests/store/sorting.node.spec.ts @@ -12,7 +12,8 @@ import { runStoreNodes, sendMessages, TestDecoder, - TestShardInfo, + TestNetworkConfig, + TestRoutingInfo, totalMsgs } from "./utils.js"; @@ -22,7 +23,7 @@ describe("Waku Store, sorting", function () { let nwaku: ServiceNode; beforeEachCustom(this, async () => { - [nwaku, waku] = await runStoreNodes(this.ctx, TestShardInfo); + [nwaku, waku] = await runStoreNodes(this.ctx, TestNetworkConfig); }); afterEachCustom(this, async () => { @@ -35,7 +36,7 @@ describe("Waku Store, sorting", function () { nwaku, totalMsgs, TestDecoder.contentTopic, - TestDecoder.pubsubTopic + TestRoutingInfo ); const pages: IMessage[][] = []; @@ -96,7 +97,7 @@ describe("Waku Store, sorting", function () { nwaku, totalMsgs, TestDecoder.contentTopic, - TestDecoder.pubsubTopic + TestRoutingInfo ); const messages: IMessage[] = []; diff --git a/packages/tests/tests/store/time_filter.node.spec.ts b/packages/tests/tests/store/time_filter.node.spec.ts index e149a38614..dc7c407858 100644 --- a/packages/tests/tests/store/time_filter.node.spec.ts +++ b/packages/tests/tests/store/time_filter.node.spec.ts @@ -12,7 +12,8 @@ import { adjustDate, runStoreNodes, TestDecoder, - TestShardInfo + TestNetworkConfig, + TestRoutingInfo } from "./utils.js"; describe("Waku Store, time filter", function () { @@ -21,7 +22,7 @@ describe("Waku Store, time filter", function () { let nwaku: ServiceNode; beforeEachCustom(this, async () => { - [nwaku, waku] = await runStoreNodes(this.ctx, TestShardInfo); + [nwaku, waku] = await runStoreNodes(this.ctx, TestNetworkConfig); }); afterEachCustom(this, async () => { @@ -49,7 +50,8 @@ describe("Waku Store, time filter", function () { payload: new Uint8Array([0]), contentTopic: TestDecoder.contentTopic, timestamp: msgTimestamp - }) + }), + TestRoutingInfo ) ).to.eq(true); @@ -90,7 +92,8 @@ describe("Waku Store, time filter", function () { payload: new Uint8Array([0]), contentTopic: TestDecoder.contentTopic, timestamp: msgTimestamp - }) + }), + TestRoutingInfo ) ).to.eq(true); diff --git a/packages/tests/tests/store/utils.ts b/packages/tests/tests/store/utils.ts index 727149f240..7d25243a9a 100644 --- a/packages/tests/tests/store/utils.ts +++ b/packages/tests/tests/store/utils.ts @@ -5,14 +5,16 @@ import { Decoder } from "@waku/core"; import { + type AutoSharding, + ContentTopic, LightNode, - NetworkConfig, + type NetworkConfig, Protocols, - ShardInfo, - type SingleShardInfo + RelayShards, + ShardId } from "@waku/interfaces"; import { createLightNode } from "@waku/sdk"; -import { Logger, singleShardInfoToPubsubTopic } from "@waku/utils"; +import { createRoutingInfo, Logger, RoutingInfo } from "@waku/utils"; import { expect } from "chai"; import { Context } from "mocha"; @@ -21,27 +23,34 @@ import { MessageRpcQuery } from "../../src/types.js"; export const log = new Logger("test:store"); -export const TestClusterId = 3; -export const TestShardInfo: ShardInfo = { +export const TestClusterId = 5; +export const TestNetworkConfig: AutoSharding = { clusterId: TestClusterId, - shards: [1, 2] + numShardsInCluster: 8 }; -export const TestShardInfo1: SingleShardInfo = { clusterId: 3, shard: 1 }; -export const TestPubsubTopic1 = singleShardInfoToPubsubTopic(TestShardInfo1); - -export const TestShardInfo2: SingleShardInfo = { clusterId: 3, shard: 2 }; -export const TestPubsubTopic2 = singleShardInfoToPubsubTopic(TestShardInfo2); - -export const TestContentTopic1 = "/test/1/waku-store/utf8"; -export const TestEncoder = createEncoder({ - contentTopic: TestContentTopic1, - pubsubTopicShardInfo: TestShardInfo1 +export const TestContentTopic = "/test/1/waku-store/utf8"; +export const TestRoutingInfo = createRoutingInfo(TestNetworkConfig, { + contentTopic: TestContentTopic }); -export const TestDecoder = createDecoder(TestContentTopic1, TestPubsubTopic1); -export const TestContentTopic2 = "/test/3/waku-store/utf8"; -export const TestDecoder2 = createDecoder(TestContentTopic2, TestPubsubTopic2); +export const TestRelayShards: RelayShards = { + clusterId: TestClusterId, + shards: [TestRoutingInfo.shardId] +}; + +export const TestEncoder = createEncoder({ + contentTopic: TestContentTopic, + routingInfo: TestRoutingInfo +}); +export const TestDecoder = createDecoder(TestContentTopic, TestRoutingInfo); + +export const TestContentTopic2 = "/test/12/waku-store/utf8"; +export const TestRoutingInfo2 = createRoutingInfo(TestNetworkConfig, { + contentTopic: TestContentTopic2 +}); + +export const TestDecoder2 = createDecoder(TestContentTopic2, TestRoutingInfo2); export const totalMsgs = 20; export const messageText = "Store Push works!"; @@ -50,7 +59,7 @@ export async function sendMessages( instance: ServiceNode, numMessages: number, contentTopic: string, - pubsubTopic: string, + routingInfo: RoutingInfo, timestamp: boolean = false ): Promise { const messages: MessageRpcQuery[] = new Array(numMessages); @@ -60,30 +69,12 @@ export async function sendMessages( contentTopic: contentTopic, timestamp: timestamp ? new Date() : undefined }); - expect(await instance.sendMessage(messages[i], pubsubTopic)).to.eq(true); + expect(await instance.sendMessage(messages[i], routingInfo)).to.eq(true); await delay(1); // to ensure each timestamp is unique. } return messages; } -export async function sendMessagesAutosharding( - instance: ServiceNode, - numMessages: number, - contentTopic: string -): Promise { - for (let i = 0; i < numMessages; i++) { - expect( - await instance.sendMessageAutosharding( - ServiceNode.toMessageRpcQuery({ - payload: new Uint8Array([i]), - contentTopic: contentTopic - }) - ) - ).to.eq(true); - await delay(1); // to ensure each timestamp is unique. - } -} - export async function processQueriedMessages( instance: LightNode, decoders: Array, @@ -126,17 +117,6 @@ export async function startAndConnectLightNode( return waku; } -export function chunkAndReverseArray( - arr: number[], - chunkSize: number -): number[] { - const result: number[] = []; - for (let i = 0; i < arr.length; i += chunkSize) { - result.push(...arr.slice(i, i + chunkSize).reverse()); - } - return result.reverse(); -} - export const adjustDate = (baseDate: Date, adjustMs: number): Date => { const adjusted = new Date(baseDate); adjusted.setTime(adjusted.getTime() + adjustMs); @@ -145,11 +125,15 @@ export const adjustDate = (baseDate: Date, adjustMs: number): Date => { export const runStoreNodes = ( context: Context, - networkConfig: NetworkConfig + networkConfig: NetworkConfig, + shardIds?: ShardId[], + contentTopics?: ContentTopic[] ): Promise<[ServiceNode, LightNode]> => runNodes({ context, networkConfig, createNode: createLightNode, + relayShards: shardIds, + contentTopics, protocols: [Protocols.Store] }); diff --git a/packages/tests/tests/wait_for_remote_peer.node.spec.ts b/packages/tests/tests/wait_for_remote_peer.node.spec.ts index e68cbe347b..0811fc069c 100644 --- a/packages/tests/tests/wait_for_remote_peer.node.spec.ts +++ b/packages/tests/tests/wait_for_remote_peer.node.spec.ts @@ -6,8 +6,11 @@ import { expect } from "chai"; import { afterEachCustom, - DefaultTestPubsubTopic, - DefaultTestShardInfo, + DefaultTestClusterId, + DefaultTestContentTopic, + DefaultTestNetworkConfig, + DefaultTestNumShardsInCluster, + DefaultTestRoutingInfo, delay, makeLogFileName, NOISE_KEY_1, @@ -15,11 +18,7 @@ import { tearDownNodes } from "../src/index.js"; -import { - runRelayNodes, - TestPubsubTopic, - TestShardInfo -} from "./relay/utils.js"; +import { runRelayNodes } from "./relay/utils.js"; describe("Wait for remote peer", function () { let waku1: RelayNode; @@ -32,10 +31,15 @@ describe("Wait for remote peer", function () { it("Relay - dialed first", async function () { this.timeout(20_000); - [nwaku, waku1] = await runRelayNodes(this, TestShardInfo); + [nwaku, waku1] = await runRelayNodes( + this, + DefaultTestNetworkConfig, + undefined, + [DefaultTestContentTopic] + ); const multiAddrWithId = await nwaku.getMultiaddrWithId(); - const peers = waku1.relay.getMeshPeers(TestPubsubTopic); + const peers = waku1.relay.getMeshPeers(DefaultTestRoutingInfo.pubsubTopic); const nimPeerId = multiAddrWithId.getPeerId(); expect(nimPeerId).to.not.be.undefined; @@ -50,14 +54,16 @@ describe("Wait for remote peer", function () { store: false, filter: false, lightpush: false, - clusterId: DefaultTestShardInfo.clusterId, - shard: DefaultTestShardInfo.shards + clusterId: DefaultTestClusterId, + numShardsInNetwork: DefaultTestNumShardsInCluster, + contentTopic: [DefaultTestContentTopic] }); const multiAddrWithId = await nwaku.getMultiaddrWithId(); waku1 = await createRelayNode({ staticNoiseKey: NOISE_KEY_1, - networkConfig: DefaultTestShardInfo + networkConfig: DefaultTestNetworkConfig, + routingInfos: [DefaultTestRoutingInfo] }); await waku1.start(); @@ -66,7 +72,7 @@ describe("Wait for remote peer", function () { await waku1.dial(multiAddrWithId); await waitPromise; - const peers = waku1.relay.getMeshPeers(DefaultTestPubsubTopic); + const peers = waku1.relay.getMeshPeers(DefaultTestRoutingInfo.pubsubTopic); const nimPeerId = multiAddrWithId.getPeerId(); expect(nimPeerId).to.not.be.undefined; @@ -77,7 +83,8 @@ describe("Wait for remote peer", function () { this.timeout(5000); createRelayNode({ staticNoiseKey: NOISE_KEY_1, - networkConfig: DefaultTestShardInfo + networkConfig: DefaultTestNetworkConfig, + routingInfos: [DefaultTestRoutingInfo] }) .then((waku1) => waku1.start().then(() => waku1)) .then((waku1) => { @@ -109,7 +116,7 @@ describe("Wait for remote peer", function () { waku2 = await createLightNode({ staticNoiseKey: NOISE_KEY_1, - networkConfig: DefaultTestShardInfo + networkConfig: DefaultTestNetworkConfig }); await waku2.start(); await waku2.dial(multiAddrWithId); @@ -138,7 +145,7 @@ describe("Wait for remote peer", function () { waku2 = await createLightNode({ staticNoiseKey: NOISE_KEY_1, - networkConfig: DefaultTestShardInfo + networkConfig: DefaultTestNetworkConfig }); await waku2.start(); const waitPromise = waku2.waitForPeers([Protocols.Store], 2000); @@ -169,7 +176,7 @@ describe("Wait for remote peer", function () { waku2 = await createLightNode({ staticNoiseKey: NOISE_KEY_1, - networkConfig: DefaultTestShardInfo + networkConfig: DefaultTestNetworkConfig }); await waku2.start(); await waku2.dial(multiAddrWithId); @@ -198,7 +205,7 @@ describe("Wait for remote peer", function () { waku2 = await createLightNode({ staticNoiseKey: NOISE_KEY_1, - networkConfig: DefaultTestShardInfo + networkConfig: DefaultTestNetworkConfig }); await waku2.start(); await waku2.dial(multiAddrWithId); @@ -228,7 +235,7 @@ describe("Wait for remote peer", function () { waku2 = await createLightNode({ staticNoiseKey: NOISE_KEY_1, - networkConfig: DefaultTestShardInfo + networkConfig: DefaultTestNetworkConfig }); await waku2.start(); await waku2.dial(multiAddrWithId); @@ -250,10 +257,10 @@ describe("Wait for remote peer", function () { it("Privacy Node - default protocol", async function () { this.timeout(20_000); - [nwaku, waku1] = await runRelayNodes(this, TestShardInfo); + [nwaku, waku1] = await runRelayNodes(this, DefaultTestNetworkConfig); const multiAddrWithId = await nwaku.getMultiaddrWithId(); - const peers = waku1.relay.getMeshPeers(TestPubsubTopic); + const peers = waku1.relay.getMeshPeers(DefaultTestRoutingInfo.pubsubTopic); const nimPeerId = multiAddrWithId.getPeerId(); diff --git a/packages/tests/tests/waku.node.spec.ts b/packages/tests/tests/waku.node.spec.ts index d97a473a89..9483bc8ba6 100644 --- a/packages/tests/tests/waku.node.spec.ts +++ b/packages/tests/tests/waku.node.spec.ts @@ -17,14 +17,15 @@ import { createLightNode, createEncoder as createPlainEncoder } from "@waku/sdk"; +import { createRoutingInfo } from "@waku/utils"; import { bytesToUtf8, utf8ToBytes } from "@waku/utils/bytes"; import { expect } from "chai"; import { afterEachCustom, beforeEachCustom, - DefaultTestShardInfo, - DefaultTestSingleShardInfo, + DefaultTestNetworkConfig, + DefaultTestRoutingInfo, makeLogFileName, NOISE_KEY_1, NOISE_KEY_2, @@ -33,8 +34,13 @@ import { } from "../src/index.js"; const TestContentTopic = "/test/1/waku/utf8"; - -const TestEncoder = createPlainEncoder({ contentTopic: TestContentTopic }); +const TestRoutingInfo = createRoutingInfo(DefaultTestNetworkConfig, { + contentTopic: TestContentTopic +}); +const TestEncoder = createPlainEncoder({ + contentTopic: TestContentTopic, + routingInfo: TestRoutingInfo +}); describe("Waku Dial [node only]", function () { describe("Interop: ServiceNode", function () { @@ -57,7 +63,7 @@ describe("Waku Dial [node only]", function () { waku = await createLightNode({ staticNoiseKey: NOISE_KEY_1, - networkConfig: DefaultTestShardInfo + networkConfig: DefaultTestNetworkConfig }); await waku.start(); await waku.dial(multiAddrWithId); @@ -91,7 +97,7 @@ describe("Waku Dial [node only]", function () { waku = await createLightNode({ staticNoiseKey: NOISE_KEY_1, - networkConfig: DefaultTestShardInfo + networkConfig: DefaultTestNetworkConfig }); await waku.start(); await waku.dial(multiAddrWithId); @@ -119,7 +125,7 @@ describe("Waku Dial [node only]", function () { const multiAddrWithId = await nwaku.getMultiaddrWithId(); waku = await createLightNode({ staticNoiseKey: NOISE_KEY_1, - networkConfig: DefaultTestShardInfo, + networkConfig: DefaultTestNetworkConfig, libp2p: { peerDiscovery: [bootstrap({ list: [multiAddrWithId.toString()] })] } @@ -145,7 +151,7 @@ describe("Waku Dial [node only]", function () { waku = await createLightNode({ staticNoiseKey: NOISE_KEY_1, - networkConfig: DefaultTestShardInfo, + networkConfig: DefaultTestNetworkConfig, libp2p: { peerDiscovery: [bootstrap({ list: [nwakuMa.toString()] })] } @@ -177,11 +183,13 @@ describe("Decryption Keys", function () { [waku1, waku2] = await Promise.all([ createRelayNode({ staticNoiseKey: NOISE_KEY_1, - networkConfig: DefaultTestShardInfo + networkConfig: DefaultTestNetworkConfig, + routingInfos: [DefaultTestRoutingInfo] }).then((waku) => waku.start().then(() => waku)), createRelayNode({ staticNoiseKey: NOISE_KEY_2, - networkConfig: DefaultTestShardInfo, + networkConfig: DefaultTestNetworkConfig, + routingInfos: [DefaultTestRoutingInfo], libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } } }).then((waku) => waku.start().then(() => waku)) ]); @@ -205,15 +213,11 @@ describe("Decryption Keys", function () { this.timeout(10000); const symKey = generateSymmetricKey(); - const decoder = createDecoder( - TestContentTopic, - symKey, - DefaultTestSingleShardInfo - ); + const decoder = createDecoder(TestContentTopic, TestRoutingInfo, symKey); const encoder = createEncoder({ contentTopic: TestContentTopic, - pubsubTopicShardInfo: DefaultTestSingleShardInfo, + routingInfo: TestRoutingInfo, symKey }); @@ -257,11 +261,13 @@ describe("User Agent", function () { createRelayNode({ staticNoiseKey: NOISE_KEY_1, userAgent: waku1UserAgent, - networkConfig: DefaultTestShardInfo + networkConfig: DefaultTestNetworkConfig, + routingInfos: [DefaultTestRoutingInfo] }).then((waku) => waku.start().then(() => waku)), createRelayNode({ staticNoiseKey: NOISE_KEY_2, - networkConfig: DefaultTestShardInfo, + networkConfig: DefaultTestNetworkConfig, + routingInfos: [DefaultTestRoutingInfo], libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } } }).then((waku) => waku.start().then(() => waku)) ]); diff --git a/packages/utils/src/common/relay_shard_codec.ts b/packages/utils/src/common/relay_shard_codec.ts index 91dea7b4ea..334673187f 100644 --- a/packages/utils/src/common/relay_shard_codec.ts +++ b/packages/utils/src/common/relay_shard_codec.ts @@ -1,6 +1,6 @@ -import type { ShardInfo } from "@waku/interfaces"; +import type { RelayShards } from "@waku/interfaces"; -export const decodeRelayShard = (bytes: Uint8Array): ShardInfo => { +export const decodeRelayShard = (bytes: Uint8Array): RelayShards => { // explicitly converting to Uint8Array to avoid Buffer // https://github.com/libp2p/js-libp2p/issues/2146 bytes = new Uint8Array(bytes); @@ -33,8 +33,8 @@ export const decodeRelayShard = (bytes: Uint8Array): ShardInfo => { return { clusterId, shards }; }; -export const encodeRelayShard = (shardInfo: ShardInfo): Uint8Array => { - const { clusterId, shards } = shardInfo; +export const encodeRelayShard = (relayShards: RelayShards): Uint8Array => { + const { clusterId, shards } = relayShards; const totalLength = shards.length >= 64 ? 130 : 3 + 2 * shards.length; const buffer = new ArrayBuffer(totalLength); const view = new DataView(buffer); diff --git a/packages/utils/src/common/sharding/index.spec.ts b/packages/utils/src/common/sharding/index.spec.ts index 4c8f854875..7e5056dbe5 100644 --- a/packages/utils/src/common/sharding/index.spec.ts +++ b/packages/utils/src/common/sharding/index.spec.ts @@ -1,17 +1,12 @@ -import { DEFAULT_CLUSTER_ID, NetworkConfig } from "@waku/interfaces"; +import { DEFAULT_CLUSTER_ID } from "@waku/interfaces"; import { expect } from "chai"; import { contentTopicsByPubsubTopic, contentTopicToPubsubTopic, contentTopicToShardIndex, - determinePubsubTopic, - ensureShardingConfigured, ensureValidContentTopic, - pubsubTopicToSingleShardInfo, - shardInfoToPubsubTopics, - singleShardInfosToShardInfo, - singleShardInfoToPubsubTopic + pubsubTopicToSingleShardInfo } from "./index.js"; const testInvalidCases = ( @@ -154,7 +149,7 @@ describe("contentTopicsByPubsubTopic", () => { const contentTopics = ["/toychat/2/huilong/proto", "/myapp/1/latest/proto"]; const grouped = contentTopicsByPubsubTopic(contentTopics); for (const contentTopic of contentTopics) { - const pubsubTopic = contentTopicToPubsubTopic(contentTopic); + const pubsubTopic = contentTopicToPubsubTopic(contentTopic, 0, 8); expect(grouped.get(pubsubTopic)?.includes(contentTopic)).to.be.true; } }); @@ -166,23 +161,25 @@ describe("contentTopicsByPubsubTopic", () => { ]; const grouped = contentTopicsByPubsubTopic(contentTopics); expect(grouped.size).to.eq(1); // Only one pubsub topic expected - const pubsubTopic = contentTopicToPubsubTopic(contentTopics[0]); + const pubsubTopic = contentTopicToPubsubTopic(contentTopics[0], 0, 8); expect(grouped.get(pubsubTopic)?.length).to.eq(2); // Both topics should be grouped under the same pubsub topic }); it("handles different clusterIds correctly", () => { const contentTopics = ["/app/22/sometopic/someencoding"]; - const clusterId1 = 1; + const clusterId1 = 3; const clusterId2 = 2; const grouped1 = contentTopicsByPubsubTopic(contentTopics, clusterId1); const grouped2 = contentTopicsByPubsubTopic(contentTopics, clusterId2); const pubsubTopic1 = contentTopicToPubsubTopic( contentTopics[0], - clusterId1 + clusterId1, + 8 ); const pubsubTopic2 = contentTopicToPubsubTopic( contentTopics[0], - clusterId2 + clusterId2, + 8 ); expect(pubsubTopic1).not.to.equal(pubsubTopic2); expect(grouped1.has(pubsubTopic1)).to.be.true; @@ -228,95 +225,6 @@ describe("contentTopicsByPubsubTopic", () => { }); }); -describe("singleShardInfoToPubsubTopic", () => { - it("should convert a SingleShardInfo object to the correct PubsubTopic", () => { - const singleShardInfo = { clusterId: 2, shard: 2 }; - const expectedTopic = "/waku/2/rs/2/2"; - expect(singleShardInfoToPubsubTopic(singleShardInfo)).to.equal( - expectedTopic - ); - }); -}); - -describe("singleShardInfosToShardInfo", () => { - it("should aggregate SingleShardInfos into a ShardInfo", () => { - const singleShardInfos = [ - { clusterId: 1, shard: 2 }, - { clusterId: 1, shard: 3 }, - { clusterId: 1, shard: 5 } - ]; - const expectedShardInfo = { clusterId: 1, shards: [2, 3, 5] }; - expect(singleShardInfosToShardInfo(singleShardInfos)).to.deep.equal( - expectedShardInfo - ); - }); - - it("should throw an error for empty SingleShardInfos array", () => { - expect(() => singleShardInfosToShardInfo([])).to.throw("Invalid shard"); - }); - - it("should throw an error for SingleShardInfos with different clusterIds", () => { - const invalidShardInfos = [ - { clusterId: 1, shard: 2 }, - { clusterId: 2, shard: 3 } - ]; - expect(() => singleShardInfosToShardInfo(invalidShardInfos)).to.throw( - "Passed shard infos have different clusterIds" - ); - }); -}); - -describe("shardInfoToPubsubTopics", () => { - it("should convert content topics to PubsubTopics for autosharding", () => { - const shardInfo = { - contentTopics: ["/app/v1/topic1/proto", "/app/v1/topic2/proto"] - }; - const topics = shardInfoToPubsubTopics(shardInfo); - expect(topics).to.be.an("array").that.includes("/waku/2/rs/1/4"); - expect(topics.length).to.equal(1); - }); - - it("should return unique PubsubTopics for static sharding", () => { - const shardInfo = { clusterId: 1, shards: [0, 1, 0] }; // Duplicate shard to test uniqueness - const topics = shardInfoToPubsubTopics(shardInfo); - expect(topics).to.have.members(["/waku/2/rs/1/0", "/waku/2/rs/1/1"]); - expect(topics.length).to.equal(2); - }); - - [0, 1, 6].forEach((clusterId) => { - it(`should handle clusterId, application and version for autosharding with cluster iD ${clusterId}`, () => { - const shardInfo = { - clusterId: clusterId, - application: "app", - version: "v1" - }; - const topics = shardInfoToPubsubTopics(shardInfo); - expect(topics) - .to.be.an("array") - .that.includes(`/waku/2/rs/${clusterId}/4`); - expect(topics.length).to.equal(1); - }); - }); - - it("should return empty list for no shard", () => { - const shardInfo = { clusterId: 1, shards: [] }; - const topics = shardInfoToPubsubTopics(shardInfo); - expect(topics.length).to.equal(0); - }); - - it("should throw an error if shards are undefined for static sharding", () => { - const shardInfo = { clusterId: 1, shards: undefined }; - expect(() => shardInfoToPubsubTopics(shardInfo)).to.throw("Invalid shard"); - }); - - it("should throw an error for missing required configuration", () => { - const shardInfo = {}; - expect(() => shardInfoToPubsubTopics(shardInfo)).to.throw( - "Missing required configuration in shard parameters" - ); - }); -}); - describe("pubsubTopicToSingleShardInfo with various invalid formats", () => { const invalidTopics = [ "/waku/1/rs/1/2", // Invalid Waku version @@ -327,8 +235,8 @@ describe("pubsubTopicToSingleShardInfo with various invalid formats", () => { ]; it("should extract SingleShardInfo from a valid PubsubTopic", () => { - const topic = "/waku/2/rs/1/2"; - const expectedInfo = { clusterId: 1, shard: 2 }; + const topic = "/waku/2/rs/2/2"; + const expectedInfo = { clusterId: 2, shard: 2 }; expect(pubsubTopicToSingleShardInfo(topic)).to.deep.equal(expectedInfo); }); @@ -356,114 +264,77 @@ describe("pubsubTopicToSingleShardInfo with various invalid formats", () => { }); }); -describe("determinePubsubTopic", () => { - const contentTopic = "/app/46/sometopic/someencoding"; - it("should return the pubsub topic directly if a string is provided", () => { - const topic = "/waku/2/rs/1/3"; - expect(determinePubsubTopic(contentTopic, topic)).to.equal(topic); - }); - - it("should return a calculated topic if SingleShardInfo is provided", () => { - const info = { clusterId: 1, shard: 2 }; - const expectedTopic = "/waku/2/rs/1/2"; - expect(determinePubsubTopic(contentTopic, info)).to.equal(expectedTopic); - }); - - it("should fall back to default pubsub topic when pubsubTopicShardInfo is not provided", () => { - expect(determinePubsubTopic(contentTopic)).to.equal("/waku/2/rs/1/6"); - }); - - it("should process correctly when SingleShardInfo has no clusterId but has a shard", () => { - const info = { shard: 0 }; - const expectedTopic = `/waku/2/rs/${DEFAULT_CLUSTER_ID}/0`; - expect(determinePubsubTopic(contentTopic, info as any)).to.equal( - expectedTopic - ); - }); - - it("should derive a pubsub topic using contentTopic when SingleShardInfo only contains clusterId", () => { - const info = { clusterId: 2 }; - const expectedTopic = contentTopicToPubsubTopic( - contentTopic, - info.clusterId - ); - expect(determinePubsubTopic(contentTopic, info as any)).to.equal( - expectedTopic - ); - }); -}); - -describe("ensureShardingConfigured", () => { - it("should return valid sharding parameters for static sharding", () => { - const shardInfo = { clusterId: 1, shards: [0, 1] }; - const result = ensureShardingConfigured(shardInfo); - expect(result.shardInfo).to.deep.include({ - clusterId: 1, - shards: [0, 1] - }); - expect(result.shardInfo).to.deep.include({ clusterId: 1, shards: [0, 1] }); - expect(result.pubsubTopics).to.have.members([ - "/waku/2/rs/1/0", - "/waku/2/rs/1/1" - ]); - }); - - it("should return valid sharding parameters for content topics autosharding", () => { - const contentTopicInfo = { contentTopics: ["/app/v1/topic1/proto"] }; - const result = ensureShardingConfigured(contentTopicInfo); - const expectedPubsubTopic = contentTopicToPubsubTopic( - "/app/v1/topic1/proto", - DEFAULT_CLUSTER_ID - ); - expect(result.shardInfo.shards).to.include( - contentTopicToShardIndex("/app/v1/topic1/proto") - ); - expect(result.pubsubTopics).to.include(expectedPubsubTopic); - }); - - it("should throw an error for missing sharding configuration", () => { - const shardInfo = {} as any as NetworkConfig; - expect(() => ensureShardingConfigured(shardInfo)).to.throw(); - }); - - it("handles empty shards array correctly", () => { - const shardInfo = { clusterId: 1, shards: [] }; - expect(() => ensureShardingConfigured(shardInfo)).to.throw(); - }); - - it("handles empty contentTopics array correctly", () => { - const shardInfo = { contentTopics: [] }; - expect(() => ensureShardingConfigured(shardInfo)).to.throw(); - }); -}); - -describe("contentTopicToPubsubTopic", () => { - it("should correctly map a content topic to a pubsub topic", () => { - const contentTopic = "/app/v1/topic1/proto"; - expect(contentTopicToPubsubTopic(contentTopic)).to.equal("/waku/2/rs/1/4"); - }); - - it("should map different content topics to different pubsub topics based on shard index", () => { - const contentTopic1 = "/app/v1/topic1/proto"; - const contentTopic2 = "/app/v2/topic2/proto"; - const pubsubTopic1 = contentTopicToPubsubTopic(contentTopic1); - const pubsubTopic2 = contentTopicToPubsubTopic(contentTopic2); - expect(pubsubTopic1).not.to.equal(pubsubTopic2); - }); - - it("should use the provided clusterId for the pubsub topic", () => { - const contentTopic = "/app/v1/topic1/proto"; - const clusterId = 2; - expect(contentTopicToPubsubTopic(contentTopic, clusterId)).to.equal( - "/waku/2/rs/2/4" - ); - }); - - it("should correctly map a content topic to a pubsub topic for different network shard sizes", () => { - const contentTopic = "/app/v1/topic1/proto"; - const networkShards = 16; - expect(contentTopicToPubsubTopic(contentTopic, 1, networkShards)).to.equal( - "/waku/2/rs/1/4" - ); - }); -}); +// describe("ensureShardingConfigured", () => { +// it("should return valid sharding parameters for static sharding", () => { +// const shardInfo = { clusterId: 1, shards: [0, 1] }; +// const result = ensureShardingConfigured(shardInfo); +// expect(result.shardInfo).to.deep.include({ +// clusterId: 1, +// shards: [0, 1] +// }); +// expect(result.shardInfo).to.deep.include({ clusterId: 1, shards: [0, 1] }); +// expect(result.pubsubTopics).to.have.members([ +// "/waku/2/rs/1/0", +// "/waku/2/rs/1/1" +// ]); +// }); +// +// it("should return valid sharding parameters for content topics autosharding", () => { +// const contentTopicInfo = { contentTopics: ["/app/v1/topic1/proto"] }; +// const result = ensureShardingConfigured(contentTopicInfo); +// const expectedPubsubTopic = contentTopicToPubsubTopic( +// "/app/v1/topic1/proto", +// DEFAULT_CLUSTER_ID +// ); +// expect(result.shardInfo.shards).to.include( +// contentTopicToShardIndex("/app/v1/topic1/proto") +// ); +// expect(result.pubsubTopics).to.include(expectedPubsubTopic); +// }); +// +// it("should throw an error for missing sharding configuration", () => { +// const shardInfo = {} as any as NetworkConfig; +// expect(() => ensureShardingConfigured(shardInfo)).to.throw(); +// }); +// +// it("handles empty shards array correctly", () => { +// const shardInfo = { clusterId: 1, shards: [] }; +// expect(() => ensureShardingConfigured(shardInfo)).to.throw(); +// }); +// +// it("handles empty contentTopics array correctly", () => { +// const shardInfo = { contentTopics: [] }; +// expect(() => ensureShardingConfigured(shardInfo)).to.throw(); +// }); +// }); +// +// describe("contentTopicToPubsubTopic", () => { +// it("should correctly map a content topic to a pubsub topic", () => { +// const contentTopic = "/app/v1/topic1/proto"; +// expect(contentTopicToPubsubTopic(contentTopic)).to.equal("/waku/2/rs/1/4"); +// }); +// +// it("should map different content topics to different pubsub topics based on shard index", () => { +// const contentTopic1 = "/app/v1/topic1/proto"; +// const contentTopic2 = "/app/v2/topic2/proto"; +// const pubsubTopic1 = contentTopicToPubsubTopic(contentTopic1); +// const pubsubTopic2 = contentTopicToPubsubTopic(contentTopic2); +// expect(pubsubTopic1).not.to.equal(pubsubTopic2); +// }); +// +// it("should use the provided clusterId for the pubsub topic", () => { +// const contentTopic = "/app/v1/topic1/proto"; +// const clusterId = 2; +// expect(contentTopicToPubsubTopic(contentTopic, clusterId)).to.equal( +// "/waku/2/rs/2/4" +// ); +// }); +// +// it("should correctly map a content topic to a pubsub topic for different network shard sizes", () => { +// const contentTopic = "/app/v1/topic1/proto"; +// const networkShards = 16; +// expect(contentTopicToPubsubTopic(contentTopic, 1, networkShards)).to.equal( +// "/waku/2/rs/1/4" +// ); +// }); +// }); diff --git a/packages/utils/src/common/sharding/index.ts b/packages/utils/src/common/sharding/index.ts index f70db904bf..e48522ac22 100644 --- a/packages/utils/src/common/sharding/index.ts +++ b/packages/utils/src/common/sharding/index.ts @@ -1,109 +1,22 @@ import { sha256 } from "@noble/hashes/sha256"; import { + type ClusterId, + ContentTopic, DEFAULT_CLUSTER_ID, - NetworkConfig, PubsubTopic, - ShardInfo, - SingleShardInfo + type ShardId } from "@waku/interfaces"; import { concat, utf8ToBytes } from "../../bytes/index.js"; -import { isAutoSharding, isStaticSharding } from "./type_guards.js"; - export * from "./type_guards.js"; +export * from "./routing_info.js"; -export function derivePubsubTopicsFromNetworkConfig( - networkConfig: NetworkConfig -): PubsubTopic[] { - if (isStaticSharding(networkConfig)) { - if (networkConfig.shards.length === 0) { - throw new Error( - "Invalid shards configuration: please provide at least one shard" - ); - } - return shardInfoToPubsubTopics(networkConfig); - } else if (isAutoSharding(networkConfig)) { - if (networkConfig.contentTopics.length === 0) { - throw new Error( - "Invalid content topics configuration: please provide at least one content topic" - ); - } - return networkConfig.contentTopics.map((contentTopic) => - contentTopicToPubsubTopic(contentTopic, networkConfig.clusterId) - ); - } else { - throw new Error( - "Unknown shard config. Please use ShardInfo or ContentTopicInfo" - ); - } -} - -export const singleShardInfoToPubsubTopic = ( - shardInfo: SingleShardInfo +export const formatPubsubTopic = ( + clusterId: ClusterId, + shard: ShardId ): PubsubTopic => { - if (shardInfo.shard === undefined) throw new Error("Invalid shard"); - - return `/waku/2/rs/${shardInfo.clusterId ?? DEFAULT_CLUSTER_ID}/${shardInfo.shard}`; -}; - -export const singleShardInfosToShardInfo = ( - singleShardInfos: SingleShardInfo[] -): ShardInfo => { - if (singleShardInfos.length === 0) throw new Error("Invalid shard"); - - const clusterIds = singleShardInfos.map((shardInfo) => shardInfo.clusterId); - if (new Set(clusterIds).size !== 1) { - throw new Error("Passed shard infos have different clusterIds"); - } - - const shards = singleShardInfos - .map((shardInfo) => shardInfo.shard) - .filter((shard): shard is number => shard !== undefined); - - return { - clusterId: singleShardInfos[0].clusterId, - shards - }; -}; - -/** - * @deprecated will be removed, use cluster and shard comparison directly - */ -export const shardInfoToPubsubTopics = ( - shardInfo: Partial -): PubsubTopic[] => { - if ("contentTopics" in shardInfo && shardInfo.contentTopics) { - // Autosharding: explicitly defined content topics - return Array.from( - new Set( - shardInfo.contentTopics.map((contentTopic) => - contentTopicToPubsubTopic(contentTopic, shardInfo.clusterId) - ) - ) - ); - } else if ("shards" in shardInfo) { - // Static sharding - if (shardInfo.shards === undefined) throw new Error("Invalid shard"); - return Array.from( - new Set( - shardInfo.shards.map( - (index) => - `/waku/2/rs/${shardInfo.clusterId ?? DEFAULT_CLUSTER_ID}/${index}` - ) - ) - ); - } else if ("application" in shardInfo && "version" in shardInfo) { - // Autosharding: single shard from application and version - return [ - contentTopicToPubsubTopic( - `/${shardInfo.application}/${shardInfo.version}/default/default`, - shardInfo.clusterId - ) - ]; - } else { - throw new Error("Missing required configuration in shard parameters"); - } + return `/waku/2/rs/${clusterId}/${shard}`; }; /** @@ -111,7 +24,7 @@ export const shardInfoToPubsubTopics = ( */ export const pubsubTopicToSingleShardInfo = ( pubsubTopics: PubsubTopic -): SingleShardInfo => { +): { clusterId: ClusterId; shard: ShardId } => { const parts = pubsubTopics.split("/"); if ( @@ -134,40 +47,7 @@ export const pubsubTopicToSingleShardInfo = ( }; }; -export const pubsubTopicsToShardInfo = ( - pubsubTopics: PubsubTopic[] -): ShardInfo => { - const shardInfoSet = new Set(); - const clusterIds = new Set(); - - for (const topic of pubsubTopics) { - const { clusterId, shard } = pubsubTopicToSingleShardInfo(topic); - shardInfoSet.add(`${clusterId}:${shard}`); - clusterIds.add(clusterId); - } - - if (shardInfoSet.size === 0) { - throw new Error("No valid pubsub topics provided"); - } - - if (clusterIds.size > 1) { - throw new Error( - "Pubsub topics from multiple cluster IDs are not supported" - ); - } - - const clusterId = clusterIds.values().next().value!; - const shards = Array.from(shardInfoSet).map((info) => - parseInt(info.split(":")[1]) - ); - - return { - clusterId, - shards - }; -}; - -interface ContentTopic { +interface ParsedContentTopic { generation: number; application: string; version: string; @@ -180,39 +60,45 @@ interface ContentTopic { * @param contentTopic String to validate * @returns Object with each content topic field as an attribute */ -export function ensureValidContentTopic(contentTopic: string): ContentTopic { - const parts = contentTopic.split("/"); +export function ensureValidContentTopic( + contentTopic: ContentTopic +): ParsedContentTopic { + const parts = (contentTopic as string).split("/"); if (parts.length < 5 || parts.length > 6) { - throw Error("Content topic format is invalid"); + throw Error(`Content topic format is invalid: ${contentTopic}`); } // Validate generation field if present let generation = 0; if (parts.length == 6) { generation = parseInt(parts[1]); if (isNaN(generation)) { - throw new Error("Invalid generation field in content topic"); + throw new Error( + `Invalid generation field in content topic: ${contentTopic}` + ); } if (generation > 0) { - throw new Error("Generation greater than 0 is not supported"); + throw new Error( + `Generation greater than 0 is not supported: ${contentTopic}` + ); } } // Validate remaining fields const fields = parts.splice(-4); // Validate application field if (fields[0].length == 0) { - throw new Error("Application field cannot be empty"); + throw new Error(`Application field cannot be empty: ${contentTopic}`); } // Validate version field if (fields[1].length == 0) { - throw new Error("Version field cannot be empty"); + throw new Error(`Version field cannot be empty: ${contentTopic}`); } // Validate topic name field if (fields[2].length == 0) { - throw new Error("Topic name field cannot be empty"); + throw new Error(`Topic name field cannot be empty: ${contentTopic}`); } // Validate encoding field if (fields[3].length == 0) { - throw new Error("Encoding field cannot be empty"); + throw new Error(`Encoding field cannot be empty: ${contentTopic}`); } return { @@ -229,27 +115,27 @@ export function ensureValidContentTopic(contentTopic: string): ContentTopic { * Based on the algorithm described in the RFC: https://rfc.vac.dev/spec/51//#algorithm */ export function contentTopicToShardIndex( - contentTopic: string, - networkShards: number = 8 + contentTopic: ContentTopic, + numShardsInCluster: number = 8 ): number { const { application, version } = ensureValidContentTopic(contentTopic); const digest = sha256( concat([utf8ToBytes(application), utf8ToBytes(version)]) ); const dataview = new DataView(digest.buffer.slice(-8)); - return Number(dataview.getBigUint64(0, false) % BigInt(networkShards)); + return Number(dataview.getBigUint64(0, false) % BigInt(numShardsInCluster)); } export function contentTopicToPubsubTopic( - contentTopic: string, - clusterId: number = DEFAULT_CLUSTER_ID, - networkShards: number = 8 + contentTopic: ContentTopic, + clusterId: number, + numShardsInCluster: number ): string { if (!contentTopic) { throw Error("Content topic must be specified"); } - const shardIndex = contentTopicToShardIndex(contentTopic, networkShards); + const shardIndex = contentTopicToShardIndex(contentTopic, numShardsInCluster); return `/waku/2/rs/${clusterId}/${shardIndex}`; } @@ -258,7 +144,7 @@ export function contentTopicToPubsubTopic( * If any of the content topics are not properly formatted, the function will throw an error. */ export function contentTopicsByPubsubTopic( - contentTopics: string[], + contentTopics: ContentTopic[], clusterId: number = DEFAULT_CLUSTER_ID, networkShards: number = 8 ): Map> { @@ -278,70 +164,3 @@ export function contentTopicsByPubsubTopic( } return groupedContentTopics; } - -/** - * Used when creating encoders/decoders to determine which pubsub topic to use - */ -export function determinePubsubTopic( - contentTopic: string, - // TODO: make it accept ShardInfo https://github.com/waku-org/js-waku/issues/2086 - pubsubTopicShardInfo?: SingleShardInfo | PubsubTopic -): string { - if (typeof pubsubTopicShardInfo == "string") { - return pubsubTopicShardInfo; - } - - return pubsubTopicShardInfo?.shard !== undefined - ? singleShardInfoToPubsubTopic(pubsubTopicShardInfo) - : contentTopicToPubsubTopic( - contentTopic, - pubsubTopicShardInfo?.clusterId ?? DEFAULT_CLUSTER_ID - ); -} - -/** - * Validates sharding configuration and sets defaults where possible. - * @returns Validated sharding parameters, with any missing values set to defaults - */ -export const ensureShardingConfigured = ( - networkConfig: NetworkConfig -): { - shardInfo: ShardInfo; - pubsubTopics: PubsubTopic[]; -} => { - const clusterId = networkConfig.clusterId ?? DEFAULT_CLUSTER_ID; - const shards = "shards" in networkConfig ? networkConfig.shards : []; - const contentTopics = - "contentTopics" in networkConfig ? networkConfig.contentTopics : []; - - const isShardsConfigured = shards && shards.length > 0; - const isContentTopicsConfigured = contentTopics && contentTopics.length > 0; - - if (isShardsConfigured) { - return { - shardInfo: { clusterId, shards }, - pubsubTopics: shardInfoToPubsubTopics({ clusterId, shards }) - }; - } - - if (isContentTopicsConfigured) { - const pubsubTopics = Array.from( - new Set( - contentTopics.map((topic) => - contentTopicToPubsubTopic(topic, clusterId) - ) - ) - ); - const shards = Array.from( - new Set(contentTopics.map((topic) => contentTopicToShardIndex(topic))) - ); - return { - shardInfo: { clusterId, shards }, - pubsubTopics - }; - } - - throw new Error( - "Missing minimum required configuration options for static sharding or autosharding." - ); -}; diff --git a/packages/utils/src/common/sharding/routing_info.ts b/packages/utils/src/common/sharding/routing_info.ts new file mode 100644 index 0000000000..a9ee49c4dd --- /dev/null +++ b/packages/utils/src/common/sharding/routing_info.ts @@ -0,0 +1,183 @@ +import type { + AutoSharding, + ContentTopic, + IRoutingInfoAutoSharding, + IRoutingInfoStaticSharding, + NetworkConfig, + PubsubTopic, + ShardId, + StaticSharding +} from "@waku/interfaces"; + +import { + contentTopicToShardIndex, + ensureValidContentTopic, + formatPubsubTopic, + isAutoSharding, + pubsubTopicToSingleShardInfo +} from "./index.js"; + +export type RoutingInfo = AutoShardingRoutingInfo | StaticShardingRoutingInfo; + +export abstract class BaseRoutingInfo { + protected constructor( + public networkConfig: NetworkConfig, + public pubsubTopic: PubsubTopic, + public shardId: ShardId + ) {} + + public abstract isAutoSharding(): boolean; + public abstract isStaticSharding(): boolean; +} + +export class AutoShardingRoutingInfo + extends BaseRoutingInfo + implements IRoutingInfoAutoSharding +{ + public static fromContentTopic( + contentTopic: ContentTopic, + networkConfig: AutoSharding + ): AutoShardingRoutingInfo { + ensureValidContentTopic(contentTopic); + + const shardId = contentTopicToShardIndex( + contentTopic, + networkConfig.numShardsInCluster + ); + const pubsubTopic = formatPubsubTopic(networkConfig.clusterId, shardId); + + return new AutoShardingRoutingInfo( + networkConfig, + pubsubTopic, + shardId, + contentTopic + ); + } + + /** + * No checks are done with this constructor, + * Be sure you check that the network config (auto vs static) + * matches other parameters. + */ + private constructor( + public networkConfig: AutoSharding, + public pubsubTopic: PubsubTopic, + public shardId: ShardId, + public contentTopic: string + ) { + super(networkConfig, pubsubTopic, shardId); + } + + public isAutoSharding(): boolean { + return true; + } + + public isStaticSharding(): boolean { + return false; + } +} + +export class StaticShardingRoutingInfo + extends BaseRoutingInfo + implements IRoutingInfoStaticSharding +{ + /** + * Create Routing Info for static sharding network, using shard + * + * @param shardId + * @param networkConfig + */ + public static fromShard( + shardId: ShardId, + networkConfig: StaticSharding + ): StaticShardingRoutingInfo { + const pubsubTopic = formatPubsubTopic(networkConfig.clusterId, shardId); + + return new StaticShardingRoutingInfo(networkConfig, pubsubTopic, shardId); + } + + /** + * Create Routing Info for static sharding network, using pubsub topic + * + * @param pubsubTopic + * @param networkConfig + * + * @throws if the pubsub topic is malformed, or does not match the network config + */ + public static fromPubsubTopic( + pubsubTopic: PubsubTopic, + networkConfig: StaticSharding + ): StaticShardingRoutingInfo { + const { clusterId, shard } = pubsubTopicToSingleShardInfo(pubsubTopic); + + if (clusterId != networkConfig.clusterId) + throw "Pubsub topic does not match network config's cluster id"; + + return new StaticShardingRoutingInfo(networkConfig, pubsubTopic, shard); + } + + /** + * No checks are done with this constructor, + * Be sure you check that the network config (auto vs static) + * matches other parameters. + */ + private constructor( + public networkConfig: StaticSharding, + public pubsubTopic: PubsubTopic, + public shardId: ShardId + ) { + super(networkConfig, pubsubTopic, shardId); + } + + public isAutoSharding(): boolean { + return false; + } + + public isStaticSharding(): boolean { + return true; + } +} + +export function isAutoShardingRoutingInfo( + routingInfo: BaseRoutingInfo +): routingInfo is AutoShardingRoutingInfo { + return routingInfo.isAutoSharding(); +} + +export function isStaticShardingRoutingInfo( + routingInfo: BaseRoutingInfo +): routingInfo is StaticShardingRoutingInfo { + return routingInfo.isStaticSharding(); +} + +export function createRoutingInfo( + networkConfig: NetworkConfig, + options: { + contentTopic?: ContentTopic; + shardId?: ShardId; + pubsubTopic?: PubsubTopic; + } +): AutoShardingRoutingInfo | StaticShardingRoutingInfo { + if (isAutoSharding(networkConfig)) { + if (options.contentTopic) { + return AutoShardingRoutingInfo.fromContentTopic( + options.contentTopic, + networkConfig + ); + } + throw new Error("AutoSharding requires contentTopic"); + } else { + if (options.shardId !== undefined) { + return StaticShardingRoutingInfo.fromShard( + options.shardId, + networkConfig + ); + } else if (options.pubsubTopic) { + return StaticShardingRoutingInfo.fromPubsubTopic( + options.pubsubTopic, + networkConfig + ); + } + throw new Error("StaticSharding requires shardId or pubsubTopic"); + } +} diff --git a/packages/utils/src/common/sharding/type_guards.ts b/packages/utils/src/common/sharding/type_guards.ts index 9ab53373aa..297b6b2b6a 100644 --- a/packages/utils/src/common/sharding/type_guards.ts +++ b/packages/utils/src/common/sharding/type_guards.ts @@ -1,5 +1,5 @@ import type { - ContentTopicInfo, + AutoSharding, CreateNodeOptions, StaticSharding } from "@waku/interfaces"; @@ -7,13 +7,11 @@ import type { export function isStaticSharding( config: NonNullable ): config is StaticSharding { - return ( - "clusterId" in config && "shards" in config && !("contentTopics" in config) - ); + return "clusterId" in config && !("numShardsInCluster" in config); } export function isAutoSharding( config: NonNullable -): config is ContentTopicInfo { - return "contentTopics" in config; +): config is AutoSharding { + return "clusterId" in config && "numShardsInCluster" in config; }