Skip to content

Commit 5715d7f

Browse files
authored
Merge pull request #1718 from waku-org/adklempner/autoshard-topic-hashing
feat: add function for determining shard index from content topic
2 parents 5e9c981 + 86da696 commit 5715d7f

File tree

4 files changed

+64
-3
lines changed

4 files changed

+64
-3
lines changed

package-lock.json

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

packages/utils/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"node": ">=18"
6868
},
6969
"dependencies": {
70+
"@noble/hashes": "^1.3.2",
7071
"chai": "^4.3.8",
7172
"debug": "^4.3.4",
7273
"uint8arrays": "^4.0.4"

packages/utils/src/common/sharding.spec.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { expect } from "chai";
22

3-
import { ensureValidContentTopic } from "./sharding";
3+
import { contentTopicToShardIndex, ensureValidContentTopic } from "./sharding";
44

55
const testInvalidCases = (
66
contentTopics: string[],
@@ -86,3 +86,15 @@ describe("ensureValidContentTopic", () => {
8686
testInvalidCases(["/0/myapp/1/mytopic/"], "Encoding field cannot be empty");
8787
});
8888
});
89+
90+
describe("contentTopicToShardIndex", () => {
91+
it("converts content topics to expected shard index", () => {
92+
const contentTopics: [string, number][] = [
93+
["/toychat/2/huilong/proto", 3],
94+
["/myapp/1/latest/proto", 0]
95+
];
96+
for (const [topic, shard] of contentTopics) {
97+
expect(contentTopicToShardIndex(topic)).to.eq(shard);
98+
}
99+
});
100+
});

packages/utils/src/common/sharding.ts

+48-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import { sha256 } from "@noble/hashes/sha256";
12
import type { PubsubTopic, ShardInfo } from "@waku/interfaces";
23

4+
import { concat, utf8ToBytes } from "../bytes/index.js";
5+
36
export const shardInfoToPubsubTopics = (
47
shardInfo: ShardInfo
58
): PubsubTopic[] => {
@@ -19,18 +22,28 @@ export function ensurePubsubTopicIsConfigured(
1922
}
2023
}
2124

25+
interface ContentTopic {
26+
generation: number;
27+
application: string;
28+
version: string;
29+
topicName: string;
30+
encoding: string;
31+
}
32+
2233
/**
2334
* Given a string, will throw an error if it is not formatted as a valid content topic for autosharding based on https://rfc.vac.dev/spec/51/
2435
* @param contentTopic String to validate
36+
* @returns Object with each content topic field as an attribute
2537
*/
26-
export function ensureValidContentTopic(contentTopic: string): void {
38+
export function ensureValidContentTopic(contentTopic: string): ContentTopic {
2739
const parts = contentTopic.split("/");
2840
if (parts.length < 5 || parts.length > 6) {
2941
throw Error("Content topic format is invalid");
3042
}
3143
// Validate generation field if present
44+
let generation = 0;
3245
if (parts.length == 6) {
33-
const generation = parseInt(parts[1]);
46+
generation = parseInt(parts[1]);
3447
if (isNaN(generation)) {
3548
throw new Error("Invalid generation field in content topic");
3649
}
@@ -56,4 +69,37 @@ export function ensureValidContentTopic(contentTopic: string): void {
5669
if (fields[3].length == 0) {
5770
throw new Error("Encoding field cannot be empty");
5871
}
72+
73+
return {
74+
generation,
75+
application: fields[0],
76+
version: fields[1],
77+
topicName: fields[2],
78+
encoding: fields[3]
79+
};
80+
}
81+
82+
/**
83+
* Given a string, determines which autoshard index to use for its pubsub topic.
84+
* Based on the algorithm described in the RFC: https://rfc.vac.dev/spec/51//#algorithm
85+
*/
86+
export function contentTopicToShardIndex(
87+
contentTopic: string,
88+
networkShards: number = 8
89+
): number {
90+
const { application, version } = ensureValidContentTopic(contentTopic);
91+
const digest = sha256(
92+
concat([utf8ToBytes(application), utf8ToBytes(version)])
93+
);
94+
const dataview = new DataView(digest.buffer.slice(-8));
95+
return Number(dataview.getBigUint64(0, false) % BigInt(networkShards));
96+
}
97+
98+
export function contentTopicToPubsubTopic(
99+
contentTopic: string,
100+
clusterId: number = 1,
101+
networkShards: number = 8
102+
): string {
103+
const shardIndex = contentTopicToShardIndex(contentTopic, networkShards);
104+
return `/waku/2/rs/${clusterId}/${shardIndex}`;
59105
}

0 commit comments

Comments
 (0)