@waku/core 0.0.36-acc9100.0 → 0.0.36-b0a2e39.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 (40) hide show
  1. package/bundle/{base_protocol-DvQrudwy.js → base_protocol-DEDdl6tx.js} +1 -1
  2. package/bundle/{index-CTo1my9M.js → index-DckUzRoN.js} +80 -1
  3. package/bundle/index.js +330 -68
  4. package/bundle/lib/base_protocol.js +2 -2
  5. package/bundle/lib/message/version_0.js +2 -2
  6. package/bundle/{version_0-CyeTW0Vr.js → version_0-DyRL7WVV.js} +370 -32
  7. package/dist/.tsbuildinfo +1 -1
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.js +1 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/lib/connection_manager/connection_manager.d.ts +1 -1
  12. package/dist/lib/connection_manager/connection_manager.js +1 -1
  13. package/dist/lib/light_push/index.d.ts +2 -1
  14. package/dist/lib/light_push/index.js +2 -1
  15. package/dist/lib/light_push/index.js.map +1 -1
  16. package/dist/lib/light_push/light_push.d.ts +2 -7
  17. package/dist/lib/light_push/light_push.js +19 -35
  18. package/dist/lib/light_push/light_push.js.map +1 -1
  19. package/dist/lib/light_push/light_push_v3.d.ts +14 -0
  20. package/dist/lib/light_push/light_push_v3.js +187 -0
  21. package/dist/lib/light_push/light_push_v3.js.map +1 -0
  22. package/dist/lib/light_push/{push_rpc.d.ts → push_rpc_v2.d.ts} +3 -3
  23. package/dist/lib/light_push/{push_rpc.js → push_rpc_v2.js} +4 -4
  24. package/dist/lib/light_push/push_rpc_v2.js.map +1 -0
  25. package/dist/lib/light_push/push_rpc_v3.d.ts +11 -0
  26. package/dist/lib/light_push/push_rpc_v3.js +32 -0
  27. package/dist/lib/light_push/push_rpc_v3.js.map +1 -0
  28. package/dist/lib/light_push/utils.d.ts +24 -0
  29. package/dist/lib/light_push/utils.js +70 -0
  30. package/dist/lib/light_push/utils.js.map +1 -1
  31. package/package.json +1 -1
  32. package/src/index.ts +5 -1
  33. package/src/lib/connection_manager/connection_manager.ts +1 -1
  34. package/src/lib/light_push/index.ts +2 -1
  35. package/src/lib/light_push/light_push.ts +26 -43
  36. package/src/lib/light_push/light_push_v3.ts +319 -0
  37. package/src/lib/light_push/{push_rpc.ts → push_rpc_v2.ts} +5 -5
  38. package/src/lib/light_push/push_rpc_v3.ts +45 -0
  39. package/src/lib/light_push/utils.ts +95 -0
  40. package/dist/lib/light_push/push_rpc.js.map +0 -1
@@ -0,0 +1,319 @@
1
+ import type { PeerId, Stream } from "@libp2p/interface";
2
+ import {
3
+ getLightPushStatusDescriptionV3,
4
+ type IBaseProtocolCore,
5
+ type IEncoder,
6
+ type IMessage,
7
+ isSuccessStatusCodeV3,
8
+ type Libp2p,
9
+ LightPushCodecV3,
10
+ type LightPushResult,
11
+ lightPushStatusCodeToProtocolErrorV3,
12
+ LightPushStatusCodeV3,
13
+ ProtocolError,
14
+ PubsubTopic
15
+ } from "@waku/interfaces";
16
+ import { proto_lightpush_v3, WakuMessage } from "@waku/proto";
17
+ import { Logger } from "@waku/utils";
18
+ import all from "it-all";
19
+ import * as lp from "it-length-prefixed";
20
+ import { pipe } from "it-pipe";
21
+ import { Uint8ArrayList } from "uint8arraylist";
22
+
23
+ import { BaseProtocol } from "../base_protocol.js";
24
+
25
+ import { PushRpcV3 } from "./push_rpc_v3.js";
26
+ import {
27
+ isRLNResponseError,
28
+ isValidRequestId,
29
+ validateMessage
30
+ } from "./utils.js";
31
+
32
+ const log = new Logger("light-push-v3");
33
+ const STREAM_TIMEOUT_MS = 30000;
34
+
35
+ class LightPushError extends Error {
36
+ public constructor(
37
+ public readonly protocol: ProtocolError,
38
+ public readonly peerId: PeerId,
39
+ public readonly response?: proto_lightpush_v3.LightpushResponse,
40
+ message?: string
41
+ ) {
42
+ super(message || protocol);
43
+ this.name = "LightPushError";
44
+ }
45
+ }
46
+
47
+ type ValidatedMessage = {
48
+ protoMessage: WakuMessage;
49
+ query: PushRpcV3;
50
+ };
51
+
52
+ type StreamResponse = {
53
+ response: proto_lightpush_v3.LightpushResponse;
54
+ query: PushRpcV3;
55
+ };
56
+
57
+ function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
58
+ return Promise.race([
59
+ promise,
60
+ new Promise<T>((_, reject) =>
61
+ setTimeout(
62
+ () => reject(new Error("Stream operation timed out")),
63
+ timeoutMs
64
+ )
65
+ )
66
+ ]);
67
+ }
68
+
69
+ function createSuccessResult(
70
+ peerId: PeerId,
71
+ response: proto_lightpush_v3.LightpushResponse
72
+ ): LightPushResult {
73
+ return {
74
+ success: peerId,
75
+ failure: null,
76
+ requestId: response.requestId,
77
+ statusCode: response.statusCode,
78
+ statusDesc: response.statusDesc,
79
+ relayPeerCount: response.relayPeerCount
80
+ };
81
+ }
82
+
83
+ function createFailureResult(error: LightPushError): LightPushResult {
84
+ const result: LightPushResult = {
85
+ success: null,
86
+ failure: {
87
+ error: error.protocol,
88
+ peerId: error.peerId
89
+ }
90
+ };
91
+
92
+ if (error.response) {
93
+ result.requestId = error.response.requestId;
94
+ result.statusCode = error.response.statusCode;
95
+ result.statusDesc = error.response.statusDesc;
96
+ result.relayPeerCount = error.response.relayPeerCount;
97
+ }
98
+
99
+ return result;
100
+ }
101
+
102
+ function validateResponseSemantics(
103
+ response: proto_lightpush_v3.LightpushResponse,
104
+ query: PushRpcV3,
105
+ peerId: PeerId
106
+ ): void {
107
+ if (!isValidRequestId(response.requestId)) {
108
+ log.error("Invalid request ID format", { received: response.requestId });
109
+ throw new LightPushError(
110
+ ProtocolError.DECODE_FAILED,
111
+ peerId,
112
+ response,
113
+ "Invalid request ID format"
114
+ );
115
+ }
116
+
117
+ if (
118
+ response.requestId !== query.query?.requestId &&
119
+ response.statusCode !== LightPushStatusCodeV3.TOO_MANY_REQUESTS
120
+ ) {
121
+ log.error("Request ID mismatch", {
122
+ sent: query.query?.requestId,
123
+ received: response.requestId
124
+ });
125
+ throw new LightPushError(
126
+ ProtocolError.GENERIC_FAIL,
127
+ peerId,
128
+ response,
129
+ "Request ID mismatch"
130
+ );
131
+ }
132
+
133
+ if (response.statusDesc && isRLNResponseError(response.statusDesc)) {
134
+ log.error("Remote peer fault: RLN generation");
135
+ throw new LightPushError(
136
+ ProtocolError.RLN_PROOF_GENERATION,
137
+ peerId,
138
+ response,
139
+ "RLN proof generation failed"
140
+ );
141
+ }
142
+ }
143
+
144
+ function ensureSuccessStatus(
145
+ response: proto_lightpush_v3.LightpushResponse,
146
+ peerId: PeerId
147
+ ): void {
148
+ if (!isSuccessStatusCodeV3(response.statusCode)) {
149
+ const errorMessage = getLightPushStatusDescriptionV3(
150
+ response.statusCode,
151
+ response.statusDesc
152
+ );
153
+ log.error("Remote peer rejected the message:", errorMessage);
154
+
155
+ const protocolError = lightPushStatusCodeToProtocolErrorV3(
156
+ response.statusCode
157
+ );
158
+ throw new LightPushError(protocolError, peerId, response, errorMessage);
159
+ }
160
+ }
161
+
162
+ function logResponseInfo(response: proto_lightpush_v3.LightpushResponse): void {
163
+ if (response.statusCode === LightPushStatusCodeV3.TOO_MANY_REQUESTS) {
164
+ if (response.requestId === "N/A") {
165
+ log.warn("Rate limited by nwaku node", {
166
+ statusDesc:
167
+ response.statusDesc || "Request rejected due to too many requests"
168
+ });
169
+ }
170
+ }
171
+
172
+ if (response.relayPeerCount !== undefined) {
173
+ log.info(`Message relayed to ${response.relayPeerCount} peers`);
174
+ }
175
+ }
176
+
177
+ export class LightPushCoreV3 extends BaseProtocol implements IBaseProtocolCore {
178
+ public constructor(
179
+ public readonly pubsubTopics: PubsubTopic[],
180
+ libp2p: Libp2p
181
+ ) {
182
+ super(LightPushCodecV3, libp2p.components, pubsubTopics);
183
+ }
184
+
185
+ public async send(
186
+ encoder: IEncoder,
187
+ message: IMessage,
188
+ peerId: PeerId
189
+ ): Promise<LightPushResult> {
190
+ try {
191
+ const validated = await this.validateAndPrepareMessage(
192
+ encoder,
193
+ message,
194
+ peerId
195
+ );
196
+ const streamResponse = await this.executeRequest(validated, peerId);
197
+ const finalResponse = this.processResponse(streamResponse, peerId);
198
+
199
+ logResponseInfo(finalResponse);
200
+ return createSuccessResult(peerId, finalResponse);
201
+ } catch (error) {
202
+ if (error instanceof LightPushError) {
203
+ return createFailureResult(error);
204
+ }
205
+
206
+ log.error("Unexpected error in send", error);
207
+ return createFailureResult(
208
+ new LightPushError(ProtocolError.GENERIC_FAIL, peerId)
209
+ );
210
+ }
211
+ }
212
+
213
+ private async validateAndPrepareMessage(
214
+ encoder: IEncoder,
215
+ message: IMessage,
216
+ peerId: PeerId
217
+ ): Promise<ValidatedMessage> {
218
+ const validationError = await validateMessage(message, encoder);
219
+ if (validationError) {
220
+ throw new LightPushError(validationError, peerId);
221
+ }
222
+
223
+ const protoMessage = await encoder.toProtoObj(message);
224
+ if (!protoMessage) {
225
+ log.error("Failed to encode to protoMessage, aborting push");
226
+ throw new LightPushError(ProtocolError.ENCODE_FAILED, peerId);
227
+ }
228
+
229
+ const query = PushRpcV3.createRequest(
230
+ protoMessage as WakuMessage,
231
+ encoder.pubsubTopic
232
+ );
233
+
234
+ return { protoMessage: protoMessage as WakuMessage, query };
235
+ }
236
+
237
+ private async executeRequest(
238
+ validated: ValidatedMessage,
239
+ peerId: PeerId
240
+ ): Promise<StreamResponse> {
241
+ const stream = await this.acquireStream(peerId);
242
+
243
+ try {
244
+ const rawResponse = await this.sendAndReceive(validated.query, stream);
245
+ const response = this.decodeResponse(rawResponse, peerId);
246
+
247
+ return { response, query: validated.query };
248
+ } finally {
249
+ void stream.close();
250
+ }
251
+ }
252
+
253
+ private processResponse(
254
+ streamResponse: StreamResponse,
255
+ peerId: PeerId
256
+ ): proto_lightpush_v3.LightpushResponse {
257
+ const { response, query } = streamResponse;
258
+
259
+ validateResponseSemantics(response, query, peerId);
260
+ ensureSuccessStatus(response, peerId);
261
+
262
+ return response;
263
+ }
264
+
265
+ private async acquireStream(peerId: PeerId): Promise<Stream> {
266
+ try {
267
+ return await this.getStream(peerId);
268
+ } catch (error) {
269
+ log.error("Failed to get stream", error);
270
+ throw new LightPushError(ProtocolError.NO_STREAM_AVAILABLE, peerId);
271
+ }
272
+ }
273
+
274
+ private async sendAndReceive(
275
+ query: PushRpcV3,
276
+ stream: Stream
277
+ ): Promise<Uint8ArrayList[]> {
278
+ try {
279
+ return await withTimeout(
280
+ pipe(
281
+ [query.encode()],
282
+ lp.encode,
283
+ stream,
284
+ lp.decode,
285
+ async (source) => await all(source)
286
+ ),
287
+ STREAM_TIMEOUT_MS
288
+ );
289
+ } catch (err) {
290
+ log.error("Failed to send waku light push request", err);
291
+ throw new Error("Stream operation failed");
292
+ }
293
+ }
294
+
295
+ private decodeResponse(
296
+ rawResponse: Uint8ArrayList[],
297
+ peerId: PeerId
298
+ ): proto_lightpush_v3.LightpushResponse {
299
+ const bytes = rawResponse.reduce((acc, chunk) => {
300
+ acc.append(chunk);
301
+ return acc;
302
+ }, new Uint8ArrayList());
303
+
304
+ try {
305
+ const response = proto_lightpush_v3.LightpushResponse.decode(bytes);
306
+ if (!response) {
307
+ log.error("Remote peer fault: No response received");
308
+ throw new LightPushError(ProtocolError.NO_RESPONSE, peerId);
309
+ }
310
+ return response;
311
+ } catch (err) {
312
+ if (err instanceof LightPushError) {
313
+ throw err;
314
+ }
315
+ log.error("Failed to decode push response", err);
316
+ throw new LightPushError(ProtocolError.DECODE_FAILED, peerId);
317
+ }
318
+ }
319
+ }
@@ -2,14 +2,14 @@ import { proto_lightpush as proto } from "@waku/proto";
2
2
  import type { Uint8ArrayList } from "uint8arraylist";
3
3
  import { v4 as uuid } from "uuid";
4
4
 
5
- export class PushRpc {
5
+ export class PushRpcV2 {
6
6
  public constructor(public proto: proto.PushRpc) {}
7
7
 
8
8
  public static createRequest(
9
9
  message: proto.WakuMessage,
10
10
  pubsubTopic: string
11
- ): PushRpc {
12
- return new PushRpc({
11
+ ): PushRpcV2 {
12
+ return new PushRpcV2({
13
13
  requestId: uuid(),
14
14
  request: {
15
15
  message: message,
@@ -19,9 +19,9 @@ export class PushRpc {
19
19
  });
20
20
  }
21
21
 
22
- public static decode(bytes: Uint8ArrayList): PushRpc {
22
+ public static decode(bytes: Uint8ArrayList): PushRpcV2 {
23
23
  const res = proto.PushRpc.decode(bytes);
24
- return new PushRpc(res);
24
+ return new PushRpcV2(res);
25
25
  }
26
26
 
27
27
  public encode(): Uint8Array {
@@ -0,0 +1,45 @@
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
+ ...(pubsubTopic && { pubsubTopic })
25
+ };
26
+
27
+ return new PushRpcV3(request, undefined);
28
+ }
29
+
30
+ public static decode(bytes: Uint8ArrayList | Uint8Array): PushRpcV3 {
31
+ const response = proto_lightpush_v3.LightpushResponse.decode(bytes);
32
+ return new PushRpcV3(undefined, response);
33
+ }
34
+
35
+ public encode(): Uint8Array {
36
+ if (!this.request) {
37
+ throw new Error("Cannot encode without a request");
38
+ }
39
+ return proto_lightpush_v3.LightpushRequest.encode(this.request);
40
+ }
41
+
42
+ public get query(): proto_lightpush_v3.LightpushRequest | undefined {
43
+ return this.request;
44
+ }
45
+ }
@@ -1,3 +1,25 @@
1
+ import type { IEncoder, IMessage } from "@waku/interfaces";
2
+ import { ProtocolError } from "@waku/interfaces";
3
+ import { isMessageSizeUnderCap, Logger } from "@waku/utils";
4
+
5
+ const log = new Logger("waku:light-push:utils");
6
+
7
+ const UUID_REGEX =
8
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
9
+
10
+ /**
11
+ * Validates a Light Push v3 request ID format.
12
+ * Request IDs should be valid UUIDs, with special handling for "N/A" when rate limited.
13
+ *
14
+ * @param requestId - The request ID to validate
15
+ * @returns true if the request ID is valid, false otherwise
16
+ */
17
+ export function isValidRequestId(requestId: string | undefined): boolean {
18
+ if (!requestId) return false;
19
+ if (requestId === "N/A") return true; // Special case for rate-limited responses
20
+ return UUID_REGEX.test(requestId);
21
+ }
22
+
1
23
  // should match nwaku
2
24
  // https://github.com/waku-org/nwaku/blob/c3cb06ac6c03f0f382d3941ea53b330f6a8dd127/waku/waku_rln_relay/rln_relay.nim#L309
3
25
  // https://github.com/waku-org/nwaku/blob/c3cb06ac6c03f0f382d3941ea53b330f6a8dd127/tests/waku_rln_relay/rln/waku_rln_relay_utils.nim#L20
@@ -21,3 +43,76 @@ export const isRLNResponseError = (info?: string): boolean => {
21
43
  info.includes(RLN_REMOTE_VALIDATION)
22
44
  );
23
45
  };
46
+
47
+ export function mapInfoToProtocolError(info?: string): ProtocolError {
48
+ if (!info) {
49
+ return ProtocolError.REMOTE_PEER_REJECTED;
50
+ }
51
+
52
+ const lowerInfo = info.toLowerCase();
53
+
54
+ if (isRLNResponseError(info)) {
55
+ return ProtocolError.RLN_PROOF_GENERATION;
56
+ }
57
+
58
+ if (
59
+ lowerInfo.includes("rate limit") ||
60
+ lowerInfo.includes("too many requests")
61
+ ) {
62
+ return ProtocolError.REMOTE_PEER_REJECTED;
63
+ }
64
+
65
+ if (
66
+ lowerInfo.includes("topic") &&
67
+ (lowerInfo.includes("not found") || lowerInfo.includes("not configured"))
68
+ ) {
69
+ return ProtocolError.TOPIC_NOT_CONFIGURED;
70
+ }
71
+
72
+ if (lowerInfo.includes("too large") || lowerInfo.includes("size")) {
73
+ return ProtocolError.SIZE_TOO_BIG;
74
+ }
75
+
76
+ if (
77
+ lowerInfo.includes("decode") ||
78
+ lowerInfo.includes("invalid") ||
79
+ lowerInfo.includes("malformed")
80
+ ) {
81
+ return ProtocolError.DECODE_FAILED;
82
+ }
83
+
84
+ if (lowerInfo.includes("empty") && lowerInfo.includes("payload")) {
85
+ return ProtocolError.EMPTY_PAYLOAD;
86
+ }
87
+
88
+ return ProtocolError.REMOTE_PEER_REJECTED;
89
+ }
90
+
91
+ /**
92
+ * Validates that a message conforms to Light Push protocol requirements.
93
+ *
94
+ * @param message - The message to validate
95
+ * @param encoder - The encoder to use for message size validation
96
+ * @returns A ProtocolError if validation fails, null if the message is valid
97
+ *
98
+ * @remarks
99
+ * This function performs the following validations:
100
+ * - Checks if the message payload exists and is not empty
101
+ * - Verifies the encoded message size is under the network cap (1MB)
102
+ */
103
+ export async function validateMessage(
104
+ message: IMessage,
105
+ encoder: IEncoder
106
+ ): Promise<ProtocolError | null> {
107
+ if (!message.payload?.length) {
108
+ log.error("Failed to send waku light push: payload is empty");
109
+ return ProtocolError.EMPTY_PAYLOAD;
110
+ }
111
+
112
+ if (!(await isMessageSizeUnderCap(encoder, message))) {
113
+ log.error("Failed to send waku light push: message is bigger than 1MB");
114
+ return ProtocolError.SIZE_TOO_BIG;
115
+ }
116
+
117
+ return null;
118
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"push_rpc.js","sourceRoot":"","sources":["../../../src/lib/light_push/push_rpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,IAAI,KAAK,EAAE,MAAM,aAAa,CAAC;AAEvD,OAAO,EAAE,EAAE,IAAI,IAAI,EAAE,MAAM,MAAM,CAAC;AAElC,MAAM,OAAO,OAAO;IACQ;IAA1B,YAA0B,KAAoB;QAApB,UAAK,GAAL,KAAK,CAAe;IAAG,CAAC;IAE3C,MAAM,CAAC,aAAa,CACzB,OAA0B,EAC1B,WAAmB;QAEnB,OAAO,IAAI,OAAO,CAAC;YACjB,SAAS,EAAE,IAAI,EAAE;YACjB,OAAO,EAAE;gBACP,OAAO,EAAE,OAAO;gBAChB,WAAW,EAAE,WAAW;aACzB;YACD,QAAQ,EAAE,SAAS;SACpB,CAAC,CAAC;IACL,CAAC;IAEM,MAAM,CAAC,MAAM,CAAC,KAAqB;QACxC,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACxC,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IAEM,MAAM;QACX,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1C,CAAC;IAED,IAAW,KAAK;QACd,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;IAC5B,CAAC;IAED,IAAW,QAAQ;QACjB,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;IAC7B,CAAC;CACF"}