@waku/core 0.0.36-10590da.0 → 0.0.36-3730abc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/bundle/index.js +495 -55
  2. package/bundle/lib/message/version_0.js +1 -1
  3. package/bundle/{version_0-CiYGrPc2.js → version_0-y7BmRolm.js} +919 -184
  4. package/dist/.tsbuildinfo +1 -1
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.js +2 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/lib/connection_manager/connection_manager.d.ts +1 -1
  9. package/dist/lib/connection_manager/connection_manager.js +1 -1
  10. package/dist/lib/filter/filter.d.ts +4 -2
  11. package/dist/lib/filter/filter.js +21 -3
  12. package/dist/lib/filter/filter.js.map +1 -1
  13. package/dist/lib/light_push/index.d.ts +5 -1
  14. package/dist/lib/light_push/index.js +5 -1
  15. package/dist/lib/light_push/index.js.map +1 -1
  16. package/dist/lib/light_push/light_push.d.ts +3 -4
  17. package/dist/lib/light_push/light_push.js +18 -19
  18. package/dist/lib/light_push/light_push.js.map +1 -1
  19. package/dist/lib/light_push/light_push_v3.d.ts +10 -0
  20. package/dist/lib/light_push/light_push_v3.js +172 -0
  21. package/dist/lib/light_push/light_push_v3.js.map +1 -0
  22. package/dist/lib/light_push/push_rpc.d.ts +1 -1
  23. package/dist/lib/light_push/push_rpc.js.map +1 -1
  24. package/dist/lib/light_push/push_rpc_v2.d.ts +11 -0
  25. package/dist/lib/light_push/push_rpc_v2.js +32 -0
  26. package/dist/lib/light_push/push_rpc_v2.js.map +1 -0
  27. package/dist/lib/light_push/push_rpc_v3.d.ts +11 -0
  28. package/dist/lib/light_push/push_rpc_v3.js +33 -0
  29. package/dist/lib/light_push/push_rpc_v3.js.map +1 -0
  30. package/dist/lib/light_push/status_codes.d.ts +14 -0
  31. package/dist/lib/light_push/status_codes.js +49 -0
  32. package/dist/lib/light_push/status_codes.js.map +1 -0
  33. package/dist/lib/light_push/status_codes_v3.d.ts +17 -0
  34. package/dist/lib/light_push/status_codes_v3.js +69 -0
  35. package/dist/lib/light_push/status_codes_v3.js.map +1 -0
  36. package/dist/lib/message/version_0.d.ts +2 -3
  37. package/dist/lib/message/version_0.js +1 -4
  38. package/dist/lib/message/version_0.js.map +1 -1
  39. package/dist/lib/message_hash/index.d.ts +1 -0
  40. package/dist/lib/message_hash/index.js +2 -0
  41. package/dist/lib/message_hash/index.js.map +1 -0
  42. package/dist/lib/message_hash/message_hash.d.ts +52 -0
  43. package/dist/lib/message_hash/message_hash.js +84 -0
  44. package/dist/lib/message_hash/message_hash.js.map +1 -0
  45. package/dist/lib/store/rpc.js +16 -10
  46. package/dist/lib/store/rpc.js.map +1 -1
  47. package/dist/lib/store/store.js +12 -2
  48. package/dist/lib/store/store.js.map +1 -1
  49. package/package.json +1 -1
  50. package/src/index.ts +11 -0
  51. package/src/lib/connection_manager/connection_manager.ts +1 -1
  52. package/src/lib/filter/filter.ts +33 -6
  53. package/src/lib/light_push/index.ts +24 -1
  54. package/src/lib/light_push/light_push.ts +25 -23
  55. package/src/lib/light_push/light_push_v3.ts +233 -0
  56. package/src/lib/light_push/push_rpc.ts +1 -1
  57. package/src/lib/light_push/push_rpc_v2.ts +38 -0
  58. package/src/lib/light_push/push_rpc_v3.ts +46 -0
  59. package/src/lib/light_push/status_codes.ts +71 -0
  60. package/src/lib/light_push/status_codes_v3.ts +97 -0
  61. package/src/lib/message/version_0.ts +3 -7
  62. package/src/lib/message_hash/index.ts +1 -0
  63. package/src/lib/message_hash/message_hash.ts +106 -0
  64. package/src/lib/store/rpc.ts +23 -19
  65. package/src/lib/store/store.ts +13 -1
@@ -0,0 +1,233 @@
1
+ import type { PeerId, Stream } from "@libp2p/interface";
2
+ import {
3
+ type CoreProtocolResult,
4
+ type IBaseProtocolCore,
5
+ type IEncoder,
6
+ type IMessage,
7
+ type Libp2p,
8
+ ProtocolError,
9
+ PubsubTopic,
10
+ type ThisOrThat
11
+ } from "@waku/interfaces";
12
+ import { proto_lightpush_v3, WakuMessage } from "@waku/proto";
13
+ import { isMessageSizeUnderCap } from "@waku/utils";
14
+ import { Logger } from "@waku/utils";
15
+ import all from "it-all";
16
+ import * as lp from "it-length-prefixed";
17
+ import { pipe } from "it-pipe";
18
+ import { Uint8ArrayList } from "uint8arraylist";
19
+
20
+ import { BaseProtocol } from "../base_protocol.js";
21
+
22
+ import { PushRpcV3 } from "./push_rpc_v3.js";
23
+ import {
24
+ getLightPushStatusDescriptionV3,
25
+ isSuccessStatusCodeV3,
26
+ lightPushStatusCodeToProtocolErrorV3,
27
+ LightPushStatusCodeV3
28
+ } from "./status_codes_v3.js";
29
+ import { isRLNResponseError } from "./utils.js";
30
+
31
+ const log = new Logger("light-push-v3");
32
+
33
+ export const LightPushCodecV3 = "/vac/waku/lightpush/3.0.0";
34
+
35
+ type PreparePushMessageResult = ThisOrThat<"query", PushRpcV3>;
36
+
37
+ export class LightPushCoreV3 extends BaseProtocol implements IBaseProtocolCore {
38
+ public constructor(
39
+ public readonly pubsubTopics: PubsubTopic[],
40
+ libp2p: Libp2p
41
+ ) {
42
+ super(LightPushCodecV3, libp2p.components, pubsubTopics);
43
+ }
44
+
45
+ private async preparePushMessage(
46
+ encoder: IEncoder,
47
+ message: IMessage
48
+ ): Promise<PreparePushMessageResult> {
49
+ try {
50
+ if (!message.payload || message.payload.length === 0) {
51
+ log.error("Failed to send waku light push: payload is empty");
52
+ return { query: null, error: ProtocolError.EMPTY_PAYLOAD };
53
+ }
54
+
55
+ if (!(await isMessageSizeUnderCap(encoder, message))) {
56
+ log.error("Failed to send waku light push: message is bigger than 1MB");
57
+ return { query: null, error: ProtocolError.SIZE_TOO_BIG };
58
+ }
59
+
60
+ const protoMessage = await encoder.toProtoObj(message);
61
+ if (!protoMessage) {
62
+ log.error("Failed to encode to protoMessage, aborting push");
63
+ return {
64
+ query: null,
65
+ error: ProtocolError.ENCODE_FAILED
66
+ };
67
+ }
68
+
69
+ const query = PushRpcV3.createRequest(
70
+ protoMessage as WakuMessage,
71
+ encoder.pubsubTopic
72
+ );
73
+ return { query, error: null };
74
+ } catch (error) {
75
+ log.error("Failed to prepare push message", error);
76
+
77
+ return {
78
+ query: null,
79
+ error: ProtocolError.GENERIC_FAIL
80
+ };
81
+ }
82
+ }
83
+
84
+ public async send(
85
+ encoder: IEncoder,
86
+ message: IMessage,
87
+ peerId: PeerId
88
+ ): Promise<CoreProtocolResult> {
89
+ const { query, error: preparationError } = await this.preparePushMessage(
90
+ encoder,
91
+ message
92
+ );
93
+
94
+ if (preparationError || !query) {
95
+ return {
96
+ success: null,
97
+ failure: {
98
+ error: preparationError,
99
+ peerId
100
+ }
101
+ };
102
+ }
103
+
104
+ let stream: Stream;
105
+ try {
106
+ stream = await this.getStream(peerId);
107
+ } catch (error) {
108
+ log.error("Failed to get stream", error);
109
+ return {
110
+ success: null,
111
+ failure: {
112
+ error: ProtocolError.NO_STREAM_AVAILABLE,
113
+ peerId: peerId
114
+ }
115
+ };
116
+ }
117
+
118
+ let res: Uint8ArrayList[] | undefined;
119
+ try {
120
+ res = await pipe(
121
+ [query.encode()],
122
+ lp.encode,
123
+ stream,
124
+ lp.decode,
125
+ async (source) => await all(source)
126
+ );
127
+ } catch (err) {
128
+ log.error("Failed to send waku light push request", err);
129
+ return {
130
+ success: null,
131
+ failure: {
132
+ error: ProtocolError.STREAM_ABORTED,
133
+ peerId: peerId
134
+ }
135
+ };
136
+ }
137
+
138
+ const bytes = new Uint8ArrayList();
139
+ res.forEach((chunk) => {
140
+ bytes.append(chunk);
141
+ });
142
+
143
+ let response: proto_lightpush_v3.LightpushResponse | undefined;
144
+ try {
145
+ response = proto_lightpush_v3.LightpushResponse.decode(bytes);
146
+ } catch (err) {
147
+ log.error("Failed to decode push response", err);
148
+ return {
149
+ success: null,
150
+ failure: {
151
+ error: ProtocolError.DECODE_FAILED,
152
+ peerId: peerId
153
+ }
154
+ };
155
+ }
156
+
157
+ if (!response) {
158
+ log.error("Remote peer fault: No response received");
159
+ return {
160
+ success: null,
161
+ failure: {
162
+ error: ProtocolError.NO_RESPONSE,
163
+ peerId: peerId
164
+ }
165
+ };
166
+ }
167
+
168
+ // Validate request ID matches (except for rate limiting responses)
169
+ if (response.requestId !== query.query?.requestId) {
170
+ // nwaku sends "N/A" for rate limiting responses
171
+ if (response.statusCode !== LightPushStatusCodeV3.TOO_MANY_REQUESTS) {
172
+ log.error("Request ID mismatch", {
173
+ sent: query.query?.requestId,
174
+ received: response.requestId
175
+ });
176
+ return {
177
+ success: null,
178
+ failure: {
179
+ error: ProtocolError.GENERIC_FAIL,
180
+ peerId: peerId
181
+ }
182
+ };
183
+ }
184
+ }
185
+
186
+ const statusCode = response.statusCode;
187
+ const isSuccess = isSuccessStatusCodeV3(statusCode);
188
+
189
+ // Special handling for nwaku rate limiting
190
+ if (statusCode === LightPushStatusCodeV3.TOO_MANY_REQUESTS) {
191
+ if (response.requestId === "N/A") {
192
+ log.warn("Rate limited by nwaku node", {
193
+ statusDesc:
194
+ response.statusDesc || "Request rejected due to too many requests"
195
+ });
196
+ }
197
+ }
198
+
199
+ if (response.relayPeerCount !== undefined) {
200
+ log.info(`Message relayed to ${response.relayPeerCount} peers`);
201
+ }
202
+
203
+ if (response.statusDesc && isRLNResponseError(response.statusDesc)) {
204
+ log.error("Remote peer fault: RLN generation");
205
+ return {
206
+ success: null,
207
+ failure: {
208
+ error: ProtocolError.RLN_PROOF_GENERATION,
209
+ peerId: peerId
210
+ }
211
+ };
212
+ }
213
+
214
+ if (!isSuccess) {
215
+ const errorMessage = getLightPushStatusDescriptionV3(
216
+ statusCode,
217
+ response.statusDesc
218
+ );
219
+ log.error("Remote peer rejected the message: ", errorMessage);
220
+
221
+ const protocolError = lightPushStatusCodeToProtocolErrorV3(statusCode);
222
+ return {
223
+ success: null,
224
+ failure: {
225
+ error: protocolError,
226
+ peerId: peerId
227
+ }
228
+ };
229
+ }
230
+
231
+ return { success: peerId, failure: null };
232
+ }
233
+ }
@@ -7,7 +7,7 @@ export class PushRpc {
7
7
 
8
8
  public static createRequest(
9
9
  message: proto.WakuMessage,
10
- pubsubTopic: string
10
+ pubsubTopic?: string
11
11
  ): PushRpc {
12
12
  return new PushRpc({
13
13
  requestId: uuid(),
@@ -0,0 +1,38 @@
1
+ import { proto_lightpush_v2 as proto } from "@waku/proto";
2
+ import type { Uint8ArrayList } from "uint8arraylist";
3
+ import { v4 as uuid } from "uuid";
4
+
5
+ export class PushRpcV2 {
6
+ public constructor(public proto: proto.PushRpc) {}
7
+
8
+ public static createRequest(
9
+ message: proto.WakuMessage,
10
+ pubsubTopic: string
11
+ ): PushRpcV2 {
12
+ return new PushRpcV2({
13
+ requestId: uuid(),
14
+ request: {
15
+ message: message,
16
+ pubsubTopic: pubsubTopic
17
+ },
18
+ response: undefined
19
+ });
20
+ }
21
+
22
+ public static decode(bytes: Uint8ArrayList): PushRpcV2 {
23
+ const res = proto.PushRpc.decode(bytes);
24
+ return new PushRpcV2(res);
25
+ }
26
+
27
+ public encode(): Uint8Array {
28
+ return proto.PushRpc.encode(this.proto);
29
+ }
30
+
31
+ public get query(): proto.PushRequest | undefined {
32
+ return this.proto.request;
33
+ }
34
+
35
+ public get response(): proto.PushResponse | undefined {
36
+ return this.proto.response;
37
+ }
38
+ }
@@ -0,0 +1,46 @@
1
+ import { proto_lightpush_v3, WakuMessage } from "@waku/proto";
2
+ import type { Uint8ArrayList } from "uint8arraylist";
3
+ import { v4 as uuid } from "uuid";
4
+
5
+ export class PushRpcV3 {
6
+ public request?: proto_lightpush_v3.LightpushRequest;
7
+ public response?: proto_lightpush_v3.LightpushResponse;
8
+
9
+ private constructor(
10
+ request?: proto_lightpush_v3.LightpushRequest,
11
+ response?: proto_lightpush_v3.LightpushResponse
12
+ ) {
13
+ this.request = request;
14
+ this.response = response;
15
+ }
16
+
17
+ public static createRequest(
18
+ message: WakuMessage,
19
+ pubsubTopic?: string
20
+ ): PushRpcV3 {
21
+ const request: proto_lightpush_v3.LightpushRequest = {
22
+ requestId: uuid(),
23
+ message: message,
24
+ // Only include pubsubTopic if explicitly provided (for nwaku autosharding compatibility)
25
+ ...(pubsubTopic && { pubsubTopic })
26
+ };
27
+
28
+ return new PushRpcV3(request, undefined);
29
+ }
30
+
31
+ public static decode(bytes: Uint8ArrayList | Uint8Array): PushRpcV3 {
32
+ const response = proto_lightpush_v3.LightpushResponse.decode(bytes);
33
+ return new PushRpcV3(undefined, response);
34
+ }
35
+
36
+ public encode(): Uint8Array {
37
+ if (!this.request) {
38
+ throw new Error("Cannot encode without a request");
39
+ }
40
+ return proto_lightpush_v3.LightpushRequest.encode(this.request);
41
+ }
42
+
43
+ public get query(): proto_lightpush_v3.LightpushRequest | undefined {
44
+ return this.request;
45
+ }
46
+ }
@@ -0,0 +1,71 @@
1
+ import { ProtocolError } from "@waku/interfaces";
2
+
3
+ export enum LightPushStatusCode {
4
+ SUCCESS = 200,
5
+ BAD_REQUEST = 400,
6
+ UNSUPPORTED_PUBSUB_TOPIC = 404,
7
+ REQUEST_TOO_LARGE = 413,
8
+ TOO_MANY_REQUESTS = 429,
9
+ INTERNAL_SERVER_ERROR = 500,
10
+ NO_PEERS_TO_RELAY = 503
11
+ }
12
+
13
+ export function lightPushStatusCodeToProtocolError(
14
+ statusCode: number
15
+ ): ProtocolError | null {
16
+ switch (statusCode) {
17
+ case LightPushStatusCode.SUCCESS:
18
+ return null;
19
+
20
+ case LightPushStatusCode.BAD_REQUEST:
21
+ return ProtocolError.GENERIC_FAIL;
22
+
23
+ case LightPushStatusCode.UNSUPPORTED_PUBSUB_TOPIC:
24
+ return ProtocolError.TOPIC_NOT_CONFIGURED;
25
+
26
+ case LightPushStatusCode.REQUEST_TOO_LARGE:
27
+ return ProtocolError.SIZE_TOO_BIG;
28
+
29
+ case LightPushStatusCode.TOO_MANY_REQUESTS:
30
+ return ProtocolError.GENERIC_FAIL;
31
+
32
+ case LightPushStatusCode.INTERNAL_SERVER_ERROR:
33
+ return ProtocolError.REMOTE_PEER_REJECTED;
34
+
35
+ case LightPushStatusCode.NO_PEERS_TO_RELAY:
36
+ return ProtocolError.NO_PEER_AVAILABLE;
37
+
38
+ default:
39
+ return ProtocolError.REMOTE_PEER_REJECTED;
40
+ }
41
+ }
42
+
43
+ export const lightPushStatusDescriptions: Record<number, string> = {
44
+ [LightPushStatusCode.SUCCESS]: "Message pushed successfully",
45
+ [LightPushStatusCode.BAD_REQUEST]:
46
+ "Invalid request format or missing required fields",
47
+ [LightPushStatusCode.UNSUPPORTED_PUBSUB_TOPIC]:
48
+ "The specified pubsub topic is not supported",
49
+ [LightPushStatusCode.REQUEST_TOO_LARGE]:
50
+ "Message size exceeds maximum allowed size",
51
+ [LightPushStatusCode.TOO_MANY_REQUESTS]:
52
+ "Rate limit exceeded, too many requests",
53
+ [LightPushStatusCode.INTERNAL_SERVER_ERROR]: "Internal server error occurred",
54
+ [LightPushStatusCode.NO_PEERS_TO_RELAY]:
55
+ "No relay peers available to forward the message"
56
+ };
57
+
58
+ export function isSuccessStatusCode(statusCode: number): boolean {
59
+ return statusCode === LightPushStatusCode.SUCCESS;
60
+ }
61
+
62
+ export function getLightPushStatusDescription(
63
+ statusCode: number,
64
+ statusDesc?: string
65
+ ): string {
66
+ return (
67
+ statusDesc ||
68
+ lightPushStatusDescriptions[statusCode] ||
69
+ `Unknown status code: ${statusCode}`
70
+ );
71
+ }
@@ -0,0 +1,97 @@
1
+ import { ProtocolError } from "@waku/interfaces";
2
+
3
+ export enum LightPushStatusCodeV3 {
4
+ SUCCESS = 200,
5
+ BAD_REQUEST = 400,
6
+ PAYLOAD_TOO_LARGE = 413,
7
+ INVALID_MESSAGE_ERROR = 420,
8
+ UNSUPPORTED_PUBSUB_TOPIC = 421,
9
+ TOO_MANY_REQUESTS = 429,
10
+ INTERNAL_SERVER_ERROR = 500,
11
+ SERVICE_NOT_AVAILABLE = 503,
12
+ OUT_OF_RLN_PROOF = 504,
13
+ NO_PEERS_TO_RELAY = 505
14
+ }
15
+
16
+ export const lightPushStatusDescriptionsV3: Record<
17
+ LightPushStatusCodeV3,
18
+ string
19
+ > = {
20
+ [LightPushStatusCodeV3.SUCCESS]: "Message sent successfully",
21
+ [LightPushStatusCodeV3.BAD_REQUEST]: "Bad request format",
22
+ [LightPushStatusCodeV3.PAYLOAD_TOO_LARGE]:
23
+ "Message payload exceeds maximum size",
24
+ [LightPushStatusCodeV3.INVALID_MESSAGE_ERROR]: "Message validation failed",
25
+ [LightPushStatusCodeV3.UNSUPPORTED_PUBSUB_TOPIC]: "Unsupported pubsub topic",
26
+ [LightPushStatusCodeV3.TOO_MANY_REQUESTS]: "Rate limit exceeded",
27
+ [LightPushStatusCodeV3.INTERNAL_SERVER_ERROR]: "Internal server error",
28
+ [LightPushStatusCodeV3.SERVICE_NOT_AVAILABLE]:
29
+ "Service temporarily unavailable",
30
+ [LightPushStatusCodeV3.OUT_OF_RLN_PROOF]: "RLN proof generation failed",
31
+ [LightPushStatusCodeV3.NO_PEERS_TO_RELAY]: "No relay peers available"
32
+ };
33
+
34
+ export function lightPushStatusCodeToProtocolErrorV3(
35
+ statusCode: LightPushStatusCodeV3 | number | undefined
36
+ ): ProtocolError {
37
+ if (!statusCode) {
38
+ return ProtocolError.GENERIC_FAIL;
39
+ }
40
+
41
+ switch (statusCode) {
42
+ case LightPushStatusCodeV3.SUCCESS:
43
+ return ProtocolError.GENERIC_FAIL;
44
+
45
+ case LightPushStatusCodeV3.BAD_REQUEST:
46
+ return ProtocolError.DECODE_FAILED;
47
+
48
+ case LightPushStatusCodeV3.PAYLOAD_TOO_LARGE:
49
+ return ProtocolError.SIZE_TOO_BIG;
50
+
51
+ case LightPushStatusCodeV3.INVALID_MESSAGE_ERROR:
52
+ return ProtocolError.EMPTY_PAYLOAD;
53
+
54
+ case LightPushStatusCodeV3.UNSUPPORTED_PUBSUB_TOPIC:
55
+ return ProtocolError.TOPIC_NOT_CONFIGURED;
56
+
57
+ case LightPushStatusCodeV3.TOO_MANY_REQUESTS:
58
+ return ProtocolError.REMOTE_PEER_REJECTED;
59
+
60
+ case LightPushStatusCodeV3.INTERNAL_SERVER_ERROR:
61
+ return ProtocolError.GENERIC_FAIL;
62
+
63
+ case LightPushStatusCodeV3.SERVICE_NOT_AVAILABLE:
64
+ return ProtocolError.NO_PEER_AVAILABLE;
65
+
66
+ case LightPushStatusCodeV3.OUT_OF_RLN_PROOF:
67
+ return ProtocolError.RLN_PROOF_GENERATION;
68
+
69
+ case LightPushStatusCodeV3.NO_PEERS_TO_RELAY:
70
+ return ProtocolError.NO_PEER_AVAILABLE;
71
+
72
+ default:
73
+ return ProtocolError.REMOTE_PEER_REJECTED;
74
+ }
75
+ }
76
+
77
+ export function isSuccessStatusCodeV3(statusCode: number | undefined): boolean {
78
+ return statusCode === LightPushStatusCodeV3.SUCCESS;
79
+ }
80
+
81
+ export function getLightPushStatusDescriptionV3(
82
+ statusCode: number | undefined,
83
+ customDesc?: string
84
+ ): string {
85
+ if (customDesc) {
86
+ return customDesc;
87
+ }
88
+
89
+ if (!statusCode) {
90
+ return "Unknown error";
91
+ }
92
+
93
+ return (
94
+ lightPushStatusDescriptionsV3[statusCode as LightPushStatusCodeV3] ||
95
+ `Unknown status code: ${statusCode}`
96
+ );
97
+ }
@@ -22,7 +22,7 @@ export { proto };
22
22
  export class DecodedMessage implements IDecodedMessage {
23
23
  public constructor(
24
24
  public pubsubTopic: string,
25
- protected proto: proto.WakuMessage
25
+ private proto: proto.WakuMessage
26
26
  ) {}
27
27
 
28
28
  public get ephemeral(): boolean {
@@ -37,10 +37,6 @@ export class DecodedMessage implements IDecodedMessage {
37
37
  return this.proto.contentTopic;
38
38
  }
39
39
 
40
- public get _rawTimestamp(): bigint | undefined {
41
- return this.proto.timestamp;
42
- }
43
-
44
40
  public get timestamp(): Date | undefined {
45
41
  // In the case we receive a value that is bigger than JS's max number,
46
42
  // we catch the error and return undefined.
@@ -63,7 +59,7 @@ export class DecodedMessage implements IDecodedMessage {
63
59
  public get version(): number {
64
60
  // https://rfc.vac.dev/spec/14/
65
61
  // > If omitted, the value SHOULD be interpreted as version 0.
66
- return this.proto.version ?? 0;
62
+ return this.proto.version ?? Version;
67
63
  }
68
64
 
69
65
  public get rateLimitProof(): IRateLimitProof | undefined {
@@ -160,7 +156,7 @@ export class Decoder implements IDecoder<IDecodedMessage> {
160
156
  public async fromProtoObj(
161
157
  pubsubTopic: string,
162
158
  proto: IProtoMessage
163
- ): Promise<DecodedMessage | undefined> {
159
+ ): Promise<IDecodedMessage | undefined> {
164
160
  // https://rfc.vac.dev/spec/14/
165
161
  // > If omitted, the value SHOULD be interpreted as version 0.
166
162
  if (proto.version ?? 0 !== Version) {
@@ -0,0 +1 @@
1
+ export { messageHash, messageHashStr } from "./message_hash.js";
@@ -0,0 +1,106 @@
1
+ import { sha256 } from "@noble/hashes/sha256";
2
+ import type { IDecodedMessage, IProtoMessage } from "@waku/interfaces";
3
+ import { isDefined } from "@waku/utils";
4
+ import {
5
+ bytesToHex,
6
+ concat,
7
+ numberToBytes,
8
+ utf8ToBytes
9
+ } from "@waku/utils/bytes";
10
+
11
+ /**
12
+ * Deterministic Message Hashing as defined in
13
+ * [14/WAKU2-MESSAGE](https://rfc.vac.dev/spec/14/#deterministic-message-hashing)
14
+ *
15
+ * Computes a SHA-256 hash of the concatenation of pubsub topic, payload, content topic, meta, and timestamp.
16
+ *
17
+ * @param pubsubTopic - The pubsub topic string
18
+ * @param message - The message to be hashed
19
+ * @returns A Uint8Array containing the SHA-256 hash
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * import { messageHash } from "@waku/core";
24
+ *
25
+ * const pubsubTopic = "/waku/2/default-waku/proto";
26
+ * const message = {
27
+ * payload: new Uint8Array([1, 2, 3, 4]),
28
+ * contentTopic: "/waku/2/default-content/proto",
29
+ * meta: new Uint8Array([5, 6, 7, 8]),
30
+ * timestamp: new Date()
31
+ * };
32
+ *
33
+ * const hash = messageHash(pubsubTopic, message);
34
+ * ```
35
+ */
36
+ export function messageHash(
37
+ pubsubTopic: string,
38
+ message: IProtoMessage | IDecodedMessage
39
+ ): Uint8Array {
40
+ const pubsubTopicBytes = utf8ToBytes(pubsubTopic);
41
+ const contentTopicBytes = utf8ToBytes(message.contentTopic);
42
+ const timestampBytes = tryConvertTimestampToBytes(message.timestamp);
43
+
44
+ const bytes = concat(
45
+ [
46
+ pubsubTopicBytes,
47
+ message.payload,
48
+ contentTopicBytes,
49
+ message.meta,
50
+ timestampBytes
51
+ ].filter(isDefined)
52
+ );
53
+
54
+ return sha256(bytes);
55
+ }
56
+
57
+ function tryConvertTimestampToBytes(
58
+ timestamp: Date | number | bigint | undefined
59
+ ): undefined | Uint8Array {
60
+ if (!timestamp) {
61
+ return;
62
+ }
63
+
64
+ let bigIntTimestamp: bigint;
65
+
66
+ if (typeof timestamp === "bigint") {
67
+ bigIntTimestamp = timestamp;
68
+ } else {
69
+ bigIntTimestamp = BigInt(timestamp.valueOf()) * 1000000n;
70
+ }
71
+
72
+ return numberToBytes(bigIntTimestamp);
73
+ }
74
+
75
+ /**
76
+ * Computes a deterministic message hash and returns it as a hexadecimal string.
77
+ * This is a convenience wrapper around messageHash that converts the result to a hex string.
78
+ *
79
+ * @param pubsubTopic - The pubsub topic string
80
+ * @param message - The message to be hashed
81
+ * @returns A string containing the hex representation of the SHA-256 hash
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * import { messageHashStr } from "@waku/core";
86
+ *
87
+ * const pubsubTopic = "/waku/2/default-waku/proto";
88
+ * const message = {
89
+ * payload: new Uint8Array([1, 2, 3, 4]),
90
+ * contentTopic: "/waku/2/default-content/proto",
91
+ * meta: new Uint8Array([5, 6, 7, 8]),
92
+ * timestamp: new Date()
93
+ * };
94
+ *
95
+ * const hashString = messageHashStr(pubsubTopic, message);
96
+ * console.log(hashString); // e.g. "a1b2c3d4..."
97
+ * ```
98
+ */
99
+ export function messageHashStr(
100
+ pubsubTopic: string,
101
+ message: IProtoMessage | IDecodedMessage
102
+ ): string {
103
+ const hash = messageHash(pubsubTopic, message);
104
+ const hashStr = bytesToHex(hash);
105
+ return hashStr;
106
+ }
@@ -14,6 +14,7 @@ export class StoreQueryRequest {
14
14
  public static create(params: QueryRequestParams): StoreQueryRequest {
15
15
  const request = new StoreQueryRequest({
16
16
  ...params,
17
+ contentTopics: params.contentTopics || [],
17
18
  requestId: uuid(),
18
19
  timeStart: params.timeStart
19
20
  ? BigInt(params.timeStart.getTime() * ONE_MILLION)
@@ -27,26 +28,29 @@ export class StoreQueryRequest {
27
28
  : undefined
28
29
  });
29
30
 
30
- // Validate request parameters based on RFC
31
- if (
32
- (params.pubsubTopic && !params.contentTopics) ||
33
- (!params.pubsubTopic && params.contentTopics)
34
- ) {
35
- throw new Error(
36
- "Both pubsubTopic and contentTopics must be set or unset"
37
- );
38
- }
31
+ const isHashQuery = params.messageHashes && params.messageHashes.length > 0;
32
+ const hasContentTopics =
33
+ params.contentTopics && params.contentTopics.length > 0;
34
+ const hasTimeFilter = params.timeStart || params.timeEnd;
39
35
 
40
- if (
41
- params.messageHashes &&
42
- (params.pubsubTopic ||
43
- params.contentTopics ||
44
- params.timeStart ||
45
- params.timeEnd)
46
- ) {
47
- throw new Error(
48
- "Message hash lookup queries cannot include content filter criteria"
49
- );
36
+ if (isHashQuery) {
37
+ if (hasContentTopics || hasTimeFilter) {
38
+ throw new Error(
39
+ "Message hash lookup queries cannot include content filter criteria (contentTopics, timeStart, or timeEnd)"
40
+ );
41
+ }
42
+ } else {
43
+ if (
44
+ (params.pubsubTopic &&
45
+ (!params.contentTopics || params.contentTopics.length === 0)) ||
46
+ (!params.pubsubTopic &&
47
+ params.contentTopics &&
48
+ params.contentTopics.length > 0)
49
+ ) {
50
+ throw new Error(
51
+ "Both pubsubTopic and contentTopics must be set together for content-filtered queries"
52
+ );
53
+ }
50
54
  }
51
55
 
52
56
  return request;