mirror of
https://github.com/logos-messaging/js-waku.git
synced 2026-01-08 00:33:12 +00:00
feat: implement lp-v3 error codes with backwards compatibility (#2501)
* feat: implement LightPush v3 protocol support Add comprehensive LightPush v3 protocol implementation with: Core Features: - LightPush v3 protocol codec and multicodec detection - Status code-based error handling and validation - Protocol version inference and compatibility layers - Enhanced error types with detailed failure information Protocol Support: - Automatic v3/v2 protocol negotiation and fallback - Status code mapping to LightPush error types - Protocol version tracking in SDK results - Mixed protocol environment support Testing Infrastructure: - Comprehensive v3 error code handling tests - Mock functions for v3/v2 response scenarios - Protocol version detection and validation tests - Backward compatibility verification Implementation Details: - Clean separation between v2 and v3 response handling - Type-safe status code validation with isSuccess helper - Enhanced failure reporting with protocol version context - Proper error propagation through SDK layers This implementation maintains full backward compatibility with v2 while providing enhanced functionality for v3 protocol features. * feat: handle both light push protocols * fix: unsubscribe test * feat: consolidate lpv2/v3 types * feat(tests): bump nwaku to 0.36.0 * fix: remove extraneous exports * fix: add delay to tests * fix: remove protocol result types * feat: consolidate light push codec branching * fix: revert nwaku image * fix: remove multicodec * fix: remove protocolversion * feat: simplify v2/v3 branching logic to use two stream managers * fix: remove unused utils * fix: remove comments * fix: revert store test * fix: cleanup lightpush sdk * fix: remove unused util * fix: remove unused exports * fix: rename file from public to protocol_handler * fix: use proper type for sdk result * fix: update return types in filter * fix: rebase against latest master * fix: use both lightpush codecs when waiting for peer * fix: handle both lp codecs * fix: remove unused code * feat: use array for multicodec fields * fix: add timestamp if missing in v3 rpc * fix: resolve on either lp codec when waiting for peer * fix: remove unused util * fix: remove unnecessary abstraction * feat: accept nwaku docker image as arg, test lp backwards compat * fix: revert filter error * feat: add legacy flag to enable lightpushv2 only * Revert "feat: accept nwaku docker image as arg, test lp backwards compat" This reverts commit 857e12cbc73305e5c51abd057665bd34708b2737. * fix: remove unused test * feat: improve lp3 (#2597) * improve light push core * move back to singualar multicodec property, enable array prop only for light push * implement v2/v3 interop e2e test, re-add useLegacy flag, ensure e2e runs for v2 and v3 * fix v2 v3 condition * generate message package earlier * add log, fix condition --------- Co-authored-by: Sasha <118575614+weboko@users.noreply.github.com> Co-authored-by: Sasha <oleksandr@status.im>
This commit is contained in:
parent
a7f30b1211
commit
16253026c6
@ -10,7 +10,11 @@ export * as waku_filter from "./lib/filter/index.js";
|
|||||||
export { FilterCore, FilterCodecs } from "./lib/filter/index.js";
|
export { FilterCore, FilterCodecs } from "./lib/filter/index.js";
|
||||||
|
|
||||||
export * as waku_light_push from "./lib/light_push/index.js";
|
export * as waku_light_push from "./lib/light_push/index.js";
|
||||||
export { LightPushCodec, LightPushCore } from "./lib/light_push/index.js";
|
export {
|
||||||
|
LightPushCore,
|
||||||
|
LightPushCodec,
|
||||||
|
LightPushCodecV2
|
||||||
|
} from "./lib/light_push/index.js";
|
||||||
|
|
||||||
export * as waku_store from "./lib/store/index.js";
|
export * as waku_store from "./lib/store/index.js";
|
||||||
export { StoreCore, StoreCodec } from "./lib/store/index.js";
|
export { StoreCore, StoreCodec } from "./lib/store/index.js";
|
||||||
|
|||||||
@ -2,9 +2,9 @@ import type { PeerId } from "@libp2p/interface";
|
|||||||
import type { IncomingStreamData } from "@libp2p/interface-internal";
|
import type { IncomingStreamData } from "@libp2p/interface-internal";
|
||||||
import {
|
import {
|
||||||
type ContentTopic,
|
type ContentTopic,
|
||||||
type CoreProtocolResult,
|
type FilterCoreResult,
|
||||||
|
FilterError,
|
||||||
type Libp2p,
|
type Libp2p,
|
||||||
ProtocolError,
|
|
||||||
type PubsubTopic
|
type PubsubTopic
|
||||||
} from "@waku/interfaces";
|
} from "@waku/interfaces";
|
||||||
import { WakuMessage } from "@waku/proto";
|
import { WakuMessage } from "@waku/proto";
|
||||||
@ -72,14 +72,14 @@ export class FilterCore {
|
|||||||
pubsubTopic: PubsubTopic,
|
pubsubTopic: PubsubTopic,
|
||||||
peerId: PeerId,
|
peerId: PeerId,
|
||||||
contentTopics: ContentTopic[]
|
contentTopics: ContentTopic[]
|
||||||
): Promise<CoreProtocolResult> {
|
): Promise<FilterCoreResult> {
|
||||||
const stream = await this.streamManager.getStream(peerId);
|
const stream = await this.streamManager.getStream(peerId);
|
||||||
|
|
||||||
if (!stream) {
|
if (!stream) {
|
||||||
return {
|
return {
|
||||||
success: null,
|
success: null,
|
||||||
failure: {
|
failure: {
|
||||||
error: ProtocolError.NO_STREAM_AVAILABLE,
|
error: FilterError.NO_STREAM_AVAILABLE,
|
||||||
peerId: peerId
|
peerId: peerId
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -108,7 +108,7 @@ export class FilterCore {
|
|||||||
return {
|
return {
|
||||||
success: null,
|
success: null,
|
||||||
failure: {
|
failure: {
|
||||||
error: ProtocolError.GENERIC_FAIL,
|
error: FilterError.GENERIC_FAIL,
|
||||||
peerId: peerId
|
peerId: peerId
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -123,7 +123,7 @@ export class FilterCore {
|
|||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
failure: {
|
failure: {
|
||||||
error: ProtocolError.REMOTE_PEER_REJECTED,
|
error: FilterError.REMOTE_PEER_REJECTED,
|
||||||
peerId: peerId
|
peerId: peerId
|
||||||
},
|
},
|
||||||
success: null
|
success: null
|
||||||
@ -140,7 +140,7 @@ export class FilterCore {
|
|||||||
pubsubTopic: PubsubTopic,
|
pubsubTopic: PubsubTopic,
|
||||||
peerId: PeerId,
|
peerId: PeerId,
|
||||||
contentTopics: ContentTopic[]
|
contentTopics: ContentTopic[]
|
||||||
): Promise<CoreProtocolResult> {
|
): Promise<FilterCoreResult> {
|
||||||
const stream = await this.streamManager.getStream(peerId);
|
const stream = await this.streamManager.getStream(peerId);
|
||||||
|
|
||||||
if (!stream) {
|
if (!stream) {
|
||||||
@ -148,7 +148,7 @@ export class FilterCore {
|
|||||||
return {
|
return {
|
||||||
success: null,
|
success: null,
|
||||||
failure: {
|
failure: {
|
||||||
error: ProtocolError.NO_STREAM_AVAILABLE,
|
error: FilterError.NO_STREAM_AVAILABLE,
|
||||||
peerId: peerId
|
peerId: peerId
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -166,7 +166,7 @@ export class FilterCore {
|
|||||||
return {
|
return {
|
||||||
success: null,
|
success: null,
|
||||||
failure: {
|
failure: {
|
||||||
error: ProtocolError.GENERIC_FAIL,
|
error: FilterError.GENERIC_FAIL,
|
||||||
peerId: peerId
|
peerId: peerId
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -181,7 +181,7 @@ export class FilterCore {
|
|||||||
public async unsubscribeAll(
|
public async unsubscribeAll(
|
||||||
pubsubTopic: PubsubTopic,
|
pubsubTopic: PubsubTopic,
|
||||||
peerId: PeerId
|
peerId: PeerId
|
||||||
): Promise<CoreProtocolResult> {
|
): Promise<FilterCoreResult> {
|
||||||
const stream = await this.streamManager.getStream(peerId);
|
const stream = await this.streamManager.getStream(peerId);
|
||||||
|
|
||||||
if (!stream) {
|
if (!stream) {
|
||||||
@ -189,7 +189,7 @@ export class FilterCore {
|
|||||||
return {
|
return {
|
||||||
success: null,
|
success: null,
|
||||||
failure: {
|
failure: {
|
||||||
error: ProtocolError.NO_STREAM_AVAILABLE,
|
error: FilterError.NO_STREAM_AVAILABLE,
|
||||||
peerId: peerId
|
peerId: peerId
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -208,7 +208,7 @@ export class FilterCore {
|
|||||||
if (!res || !res.length) {
|
if (!res || !res.length) {
|
||||||
return {
|
return {
|
||||||
failure: {
|
failure: {
|
||||||
error: ProtocolError.NO_RESPONSE,
|
error: FilterError.NO_RESPONSE,
|
||||||
peerId: peerId
|
peerId: peerId
|
||||||
},
|
},
|
||||||
success: null
|
success: null
|
||||||
@ -224,7 +224,7 @@ export class FilterCore {
|
|||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
failure: {
|
failure: {
|
||||||
error: ProtocolError.REMOTE_PEER_REJECTED,
|
error: FilterError.REMOTE_PEER_REJECTED,
|
||||||
peerId: peerId
|
peerId: peerId
|
||||||
},
|
},
|
||||||
success: null
|
success: null
|
||||||
@ -237,7 +237,7 @@ export class FilterCore {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ping(peerId: PeerId): Promise<CoreProtocolResult> {
|
public async ping(peerId: PeerId): Promise<FilterCoreResult> {
|
||||||
const stream = await this.streamManager.getStream(peerId);
|
const stream = await this.streamManager.getStream(peerId);
|
||||||
|
|
||||||
if (!stream) {
|
if (!stream) {
|
||||||
@ -245,7 +245,7 @@ export class FilterCore {
|
|||||||
return {
|
return {
|
||||||
success: null,
|
success: null,
|
||||||
failure: {
|
failure: {
|
||||||
error: ProtocolError.NO_STREAM_AVAILABLE,
|
error: FilterError.NO_STREAM_AVAILABLE,
|
||||||
peerId: peerId
|
peerId: peerId
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -267,7 +267,7 @@ export class FilterCore {
|
|||||||
return {
|
return {
|
||||||
success: null,
|
success: null,
|
||||||
failure: {
|
failure: {
|
||||||
error: ProtocolError.GENERIC_FAIL,
|
error: FilterError.GENERIC_FAIL,
|
||||||
peerId: peerId
|
peerId: peerId
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -277,7 +277,7 @@ export class FilterCore {
|
|||||||
return {
|
return {
|
||||||
success: null,
|
success: null,
|
||||||
failure: {
|
failure: {
|
||||||
error: ProtocolError.NO_RESPONSE,
|
error: FilterError.NO_RESPONSE,
|
||||||
peerId: peerId
|
peerId: peerId
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -293,7 +293,7 @@ export class FilterCore {
|
|||||||
return {
|
return {
|
||||||
success: null,
|
success: null,
|
||||||
failure: {
|
failure: {
|
||||||
error: ProtocolError.REMOTE_PEER_REJECTED,
|
error: FilterError.REMOTE_PEER_REJECTED,
|
||||||
peerId: peerId
|
peerId: peerId
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
7
packages/core/src/lib/light_push/constants.ts
Normal file
7
packages/core/src/lib/light_push/constants.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export const CODECS = {
|
||||||
|
v2: "/vac/waku/lightpush/2.0.0-beta1",
|
||||||
|
v3: "/vac/waku/lightpush/3.0.0"
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const LightPushCodecV2 = CODECS.v2;
|
||||||
|
export const LightPushCodec = CODECS.v3;
|
||||||
@ -1 +1,2 @@
|
|||||||
export { LightPushCore, LightPushCodec, PushResponse } from "./light_push.js";
|
export { LightPushCore } from "./light_push.js";
|
||||||
|
export { LightPushCodec, LightPushCodecV2 } from "./constants.js";
|
||||||
|
|||||||
@ -1,14 +1,11 @@
|
|||||||
import type { PeerId } from "@libp2p/interface";
|
import type { PeerId, Stream } from "@libp2p/interface";
|
||||||
import {
|
import {
|
||||||
type CoreProtocolResult,
|
|
||||||
type IEncoder,
|
type IEncoder,
|
||||||
type IMessage,
|
type IMessage,
|
||||||
type Libp2p,
|
type Libp2p,
|
||||||
ProtocolError,
|
type LightPushCoreResult,
|
||||||
type ThisOrThat
|
LightPushError
|
||||||
} from "@waku/interfaces";
|
} from "@waku/interfaces";
|
||||||
import { PushResponse } from "@waku/proto";
|
|
||||||
import { isMessageSizeUnderCap } from "@waku/utils";
|
|
||||||
import { Logger } from "@waku/utils";
|
import { Logger } from "@waku/utils";
|
||||||
import all from "it-all";
|
import all from "it-all";
|
||||||
import * as lp from "it-length-prefixed";
|
import * as lp from "it-length-prefixed";
|
||||||
@ -17,92 +14,71 @@ import { Uint8ArrayList } from "uint8arraylist";
|
|||||||
|
|
||||||
import { StreamManager } from "../stream_manager/index.js";
|
import { StreamManager } from "../stream_manager/index.js";
|
||||||
|
|
||||||
import { PushRpc } from "./push_rpc.js";
|
import { CODECS } from "./constants.js";
|
||||||
import { isRLNResponseError } from "./utils.js";
|
import { ProtocolHandler } from "./protocol_handler.js";
|
||||||
|
|
||||||
const log = new Logger("light-push");
|
const log = new Logger("light-push");
|
||||||
|
|
||||||
export const LightPushCodec = "/vac/waku/lightpush/2.0.0-beta1";
|
|
||||||
export { PushResponse };
|
|
||||||
|
|
||||||
type PreparePushMessageResult = ThisOrThat<"query", PushRpc>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the [Waku v2 Light Push protocol](https://rfc.vac.dev/spec/19/).
|
* Implements the [Waku v2 Light Push protocol](https://rfc.vac.dev/spec/19/).
|
||||||
*/
|
*/
|
||||||
export class LightPushCore {
|
export class LightPushCore {
|
||||||
private readonly streamManager: StreamManager;
|
private readonly streamManager: StreamManager;
|
||||||
|
private readonly streamManagerV2: StreamManager;
|
||||||
|
|
||||||
public readonly multicodec = LightPushCodec;
|
public readonly multicodec = [CODECS.v3, CODECS.v2];
|
||||||
|
|
||||||
public constructor(libp2p: Libp2p) {
|
public constructor(private libp2p: Libp2p) {
|
||||||
this.streamManager = new StreamManager(LightPushCodec, libp2p.components);
|
this.streamManagerV2 = new StreamManager(CODECS.v2, libp2p.components);
|
||||||
}
|
this.streamManager = new StreamManager(CODECS.v3, libp2p.components);
|
||||||
|
|
||||||
private async preparePushMessage(
|
|
||||||
encoder: IEncoder,
|
|
||||||
message: IMessage
|
|
||||||
): Promise<PreparePushMessageResult> {
|
|
||||||
try {
|
|
||||||
if (!message.payload || message.payload.length === 0) {
|
|
||||||
log.error("Failed to send waku light push: payload is empty");
|
|
||||||
return { query: null, error: ProtocolError.EMPTY_PAYLOAD };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(await isMessageSizeUnderCap(encoder, message))) {
|
|
||||||
log.error("Failed to send waku light push: message is bigger than 1MB");
|
|
||||||
return { query: null, error: ProtocolError.SIZE_TOO_BIG };
|
|
||||||
}
|
|
||||||
|
|
||||||
const protoMessage = await encoder.toProtoObj(message);
|
|
||||||
if (!protoMessage) {
|
|
||||||
log.error("Failed to encode to protoMessage, aborting push");
|
|
||||||
return {
|
|
||||||
query: null,
|
|
||||||
error: ProtocolError.ENCODE_FAILED
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = PushRpc.createRequest(protoMessage, encoder.pubsubTopic);
|
|
||||||
return { query, error: null };
|
|
||||||
} catch (error) {
|
|
||||||
log.error("Failed to prepare push message", error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
query: null,
|
|
||||||
error: ProtocolError.GENERIC_FAIL
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async send(
|
public async send(
|
||||||
encoder: IEncoder,
|
encoder: IEncoder,
|
||||||
message: IMessage,
|
message: IMessage,
|
||||||
peerId: PeerId
|
peerId: PeerId,
|
||||||
): Promise<CoreProtocolResult> {
|
useLegacy: boolean = false
|
||||||
const { query, error: preparationError } = await this.preparePushMessage(
|
): Promise<LightPushCoreResult> {
|
||||||
encoder,
|
const protocol = await this.getProtocol(peerId, useLegacy);
|
||||||
message
|
|
||||||
|
log.info(
|
||||||
|
`Sending light push request to peer:${peerId.toString()}, protocol:${protocol}`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (preparationError || !query) {
|
if (!protocol) {
|
||||||
return {
|
return {
|
||||||
success: null,
|
success: null,
|
||||||
failure: {
|
failure: {
|
||||||
error: preparationError,
|
error: LightPushError.GENERIC_FAIL,
|
||||||
peerId
|
peerId
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const stream = await this.streamManager.getStream(peerId);
|
const { rpc, error: prepError } = await ProtocolHandler.preparePushMessage(
|
||||||
|
encoder,
|
||||||
|
message,
|
||||||
|
protocol
|
||||||
|
);
|
||||||
|
|
||||||
|
if (prepError) {
|
||||||
|
return {
|
||||||
|
success: null,
|
||||||
|
failure: {
|
||||||
|
error: prepError,
|
||||||
|
peerId
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream = await this.getStream(peerId, protocol);
|
||||||
|
|
||||||
if (!stream) {
|
if (!stream) {
|
||||||
log.error(`Failed to get a stream for remote peer:${peerId.toString()}`);
|
log.error(`Failed to get a stream for remote peer:${peerId.toString()}`);
|
||||||
return {
|
return {
|
||||||
success: null,
|
success: null,
|
||||||
failure: {
|
failure: {
|
||||||
error: ProtocolError.NO_STREAM_AVAILABLE,
|
error: LightPushError.NO_STREAM_AVAILABLE,
|
||||||
peerId: peerId
|
peerId: peerId
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -111,76 +87,74 @@ export class LightPushCore {
|
|||||||
let res: Uint8ArrayList[] | undefined;
|
let res: Uint8ArrayList[] | undefined;
|
||||||
try {
|
try {
|
||||||
res = await pipe(
|
res = await pipe(
|
||||||
[query.encode()],
|
[rpc.encode()],
|
||||||
lp.encode,
|
lp.encode,
|
||||||
stream,
|
stream,
|
||||||
lp.decode,
|
lp.decode,
|
||||||
async (source) => await all(source)
|
async (source) => await all(source)
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// can fail only because of `stream` abortion
|
|
||||||
log.error("Failed to send waku light push request", err);
|
log.error("Failed to send waku light push request", err);
|
||||||
return {
|
return {
|
||||||
success: null,
|
success: null,
|
||||||
failure: {
|
failure: {
|
||||||
error: ProtocolError.STREAM_ABORTED,
|
error: LightPushError.STREAM_ABORTED,
|
||||||
peerId: peerId
|
peerId: peerId
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const bytes = new Uint8ArrayList();
|
const bytes = new Uint8ArrayList();
|
||||||
res.forEach((chunk) => {
|
res.forEach((chunk) => bytes.append(chunk));
|
||||||
bytes.append(chunk);
|
|
||||||
});
|
|
||||||
|
|
||||||
let response: PushResponse | undefined;
|
if (bytes.length === 0) {
|
||||||
|
return {
|
||||||
|
success: null,
|
||||||
|
failure: {
|
||||||
|
error: LightPushError.NO_RESPONSE,
|
||||||
|
peerId: peerId
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProtocolHandler.handleResponse(bytes, protocol, peerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getProtocol(
|
||||||
|
peerId: PeerId,
|
||||||
|
useLegacy: boolean
|
||||||
|
): Promise<string | undefined> {
|
||||||
try {
|
try {
|
||||||
response = PushRpc.decode(bytes).response;
|
const peer = await this.libp2p.peerStore.get(peerId);
|
||||||
} catch (err) {
|
|
||||||
log.error("Failed to decode push reply", err);
|
if (
|
||||||
return {
|
useLegacy ||
|
||||||
success: null,
|
(!peer.protocols.includes(CODECS.v3) &&
|
||||||
failure: {
|
peer.protocols.includes(CODECS.v2))
|
||||||
error: ProtocolError.DECODE_FAILED,
|
) {
|
||||||
peerId: peerId
|
return CODECS.v2;
|
||||||
|
} else if (peer.protocols.includes(CODECS.v3)) {
|
||||||
|
return CODECS.v3;
|
||||||
|
} else {
|
||||||
|
throw new Error("No supported protocol found");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
log.error("Failed to get protocol", error);
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response) {
|
private async getStream(
|
||||||
log.error("Remote peer fault: No response in PushRPC");
|
peerId: PeerId,
|
||||||
return {
|
protocol: string
|
||||||
success: null,
|
): Promise<Stream | undefined> {
|
||||||
failure: {
|
switch (protocol) {
|
||||||
error: ProtocolError.NO_RESPONSE,
|
case CODECS.v2:
|
||||||
peerId: peerId
|
return this.streamManagerV2.getStream(peerId);
|
||||||
}
|
case CODECS.v3:
|
||||||
};
|
return this.streamManager.getStream(peerId);
|
||||||
}
|
default:
|
||||||
|
return undefined;
|
||||||
if (isRLNResponseError(response.info)) {
|
}
|
||||||
log.error("Remote peer fault: RLN generation");
|
|
||||||
return {
|
|
||||||
success: null,
|
|
||||||
failure: {
|
|
||||||
error: ProtocolError.RLN_PROOF_GENERATION,
|
|
||||||
peerId: peerId
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.isSuccess) {
|
|
||||||
log.error("Remote peer rejected the message: ", response.info);
|
|
||||||
return {
|
|
||||||
success: null,
|
|
||||||
failure: {
|
|
||||||
error: ProtocolError.REMOTE_PEER_REJECTED,
|
|
||||||
peerId: peerId
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { success: peerId, failure: null };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
191
packages/core/src/lib/light_push/protocol_handler.ts
Normal file
191
packages/core/src/lib/light_push/protocol_handler.ts
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
import type { PeerId } from "@libp2p/interface";
|
||||||
|
import type { IEncoder, IMessage, LightPushCoreResult } from "@waku/interfaces";
|
||||||
|
import { LightPushError, LightPushStatusCode } from "@waku/interfaces";
|
||||||
|
import { PushResponse, WakuMessage } from "@waku/proto";
|
||||||
|
import { isMessageSizeUnderCap, Logger } from "@waku/utils";
|
||||||
|
import { Uint8ArrayList } from "uint8arraylist";
|
||||||
|
|
||||||
|
import { CODECS } from "./constants.js";
|
||||||
|
import { PushRpcV2 } from "./push_rpc.js";
|
||||||
|
import { PushRpc } from "./push_rpc_v3.js";
|
||||||
|
import { isRLNResponseError } from "./utils.js";
|
||||||
|
|
||||||
|
type VersionedPushRpc =
|
||||||
|
| ({ version: "v2" } & PushRpcV2)
|
||||||
|
| ({ version: "v3" } & PushRpc);
|
||||||
|
|
||||||
|
type PreparePushMessageResult =
|
||||||
|
| { rpc: VersionedPushRpc; error: null }
|
||||||
|
| { rpc: null; error: LightPushError };
|
||||||
|
|
||||||
|
const log = new Logger("light-push:protocol-handler");
|
||||||
|
|
||||||
|
export class ProtocolHandler {
|
||||||
|
public static async preparePushMessage(
|
||||||
|
encoder: IEncoder,
|
||||||
|
message: IMessage,
|
||||||
|
protocol: string
|
||||||
|
): Promise<PreparePushMessageResult> {
|
||||||
|
try {
|
||||||
|
if (!message.payload || message.payload.length === 0) {
|
||||||
|
log.error("Failed to send waku light push: payload is empty");
|
||||||
|
return { rpc: null, error: LightPushError.EMPTY_PAYLOAD };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await isMessageSizeUnderCap(encoder, message))) {
|
||||||
|
log.error("Failed to send waku light push: message is bigger than 1MB");
|
||||||
|
return { rpc: null, error: LightPushError.SIZE_TOO_BIG };
|
||||||
|
}
|
||||||
|
|
||||||
|
const protoMessage = await encoder.toProtoObj(message);
|
||||||
|
if (!protoMessage) {
|
||||||
|
log.error("Failed to encode to protoMessage, aborting push");
|
||||||
|
return { rpc: null, error: LightPushError.ENCODE_FAILED };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (protocol === CODECS.v3) {
|
||||||
|
log.info("Creating v3 RPC message");
|
||||||
|
return {
|
||||||
|
rpc: ProtocolHandler.createV3Rpc(protoMessage, encoder.pubsubTopic),
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Creating v2 RPC message");
|
||||||
|
return {
|
||||||
|
rpc: ProtocolHandler.createV2Rpc(protoMessage, encoder.pubsubTopic),
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
log.error("Failed to prepare push message", err);
|
||||||
|
return { rpc: null, error: LightPushError.GENERIC_FAIL };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode and evaluate a LightPush response according to the protocol version
|
||||||
|
*/
|
||||||
|
public static handleResponse(
|
||||||
|
bytes: Uint8ArrayList,
|
||||||
|
protocol: string,
|
||||||
|
peerId: PeerId
|
||||||
|
): LightPushCoreResult {
|
||||||
|
if (protocol === CODECS.v3) {
|
||||||
|
return ProtocolHandler.handleV3Response(bytes, peerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProtocolHandler.handleV2Response(bytes, peerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static handleV3Response(
|
||||||
|
bytes: Uint8ArrayList,
|
||||||
|
peerId: PeerId
|
||||||
|
): LightPushCoreResult {
|
||||||
|
try {
|
||||||
|
const decodedRpcV3 = PushRpc.decodeResponse(bytes);
|
||||||
|
const statusCode = decodedRpcV3.statusCode;
|
||||||
|
const statusDesc = decodedRpcV3.statusDesc;
|
||||||
|
|
||||||
|
if (statusCode !== LightPushStatusCode.SUCCESS) {
|
||||||
|
const error = LightPushError.REMOTE_PEER_REJECTED;
|
||||||
|
log.error(
|
||||||
|
`Remote peer rejected with v3 status code ${statusCode}: ${statusDesc}`
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
success: null,
|
||||||
|
failure: {
|
||||||
|
error,
|
||||||
|
peerId: peerId
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decodedRpcV3.relayPeerCount !== undefined) {
|
||||||
|
log.info(`Message relayed to ${decodedRpcV3.relayPeerCount} peers`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: peerId, failure: null };
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
success: null,
|
||||||
|
failure: {
|
||||||
|
error: LightPushError.DECODE_FAILED,
|
||||||
|
peerId: peerId
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static handleV2Response(
|
||||||
|
bytes: Uint8ArrayList,
|
||||||
|
peerId: PeerId
|
||||||
|
): LightPushCoreResult {
|
||||||
|
let response: PushResponse | undefined;
|
||||||
|
try {
|
||||||
|
const decodedRpc = PushRpcV2.decode(bytes);
|
||||||
|
response = decodedRpc.response;
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
success: null,
|
||||||
|
failure: {
|
||||||
|
error: LightPushError.DECODE_FAILED,
|
||||||
|
peerId: peerId
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response) {
|
||||||
|
return {
|
||||||
|
success: null,
|
||||||
|
failure: {
|
||||||
|
error: LightPushError.NO_RESPONSE,
|
||||||
|
peerId: peerId
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRLNResponseError(response.info)) {
|
||||||
|
log.error("Remote peer fault: RLN generation");
|
||||||
|
return {
|
||||||
|
success: null,
|
||||||
|
failure: {
|
||||||
|
error: LightPushError.RLN_PROOF_GENERATION,
|
||||||
|
peerId: peerId
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.isSuccess) {
|
||||||
|
log.error("Remote peer rejected the message: ", response.info);
|
||||||
|
return {
|
||||||
|
success: null,
|
||||||
|
failure: {
|
||||||
|
error: LightPushError.REMOTE_PEER_REJECTED,
|
||||||
|
peerId: peerId
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: peerId, failure: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
private static createV2Rpc(
|
||||||
|
message: WakuMessage,
|
||||||
|
pubsubTopic: string
|
||||||
|
): VersionedPushRpc {
|
||||||
|
const v2Rpc = PushRpcV2.createRequest(message, pubsubTopic);
|
||||||
|
return Object.assign(v2Rpc, { version: "v2" as const });
|
||||||
|
}
|
||||||
|
|
||||||
|
private static createV3Rpc(
|
||||||
|
message: WakuMessage,
|
||||||
|
pubsubTopic: string
|
||||||
|
): VersionedPushRpc {
|
||||||
|
if (!message.timestamp) {
|
||||||
|
message.timestamp = BigInt(Date.now()) * BigInt(1_000_000);
|
||||||
|
}
|
||||||
|
|
||||||
|
const v3Rpc = PushRpc.createRequest(message, pubsubTopic);
|
||||||
|
return Object.assign(v3Rpc, { version: "v3" as const });
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,14 +2,14 @@ import { proto_lightpush as proto } from "@waku/proto";
|
|||||||
import type { Uint8ArrayList } from "uint8arraylist";
|
import type { Uint8ArrayList } from "uint8arraylist";
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
|
|
||||||
export class PushRpc {
|
export class PushRpcV2 {
|
||||||
public constructor(public proto: proto.PushRpc) {}
|
public constructor(public proto: proto.PushRpc) {}
|
||||||
|
|
||||||
public static createRequest(
|
public static createRequest(
|
||||||
message: proto.WakuMessage,
|
message: proto.WakuMessage,
|
||||||
pubsubTopic: string
|
pubsubTopic: string
|
||||||
): PushRpc {
|
): PushRpcV2 {
|
||||||
return new PushRpc({
|
return new PushRpcV2({
|
||||||
requestId: uuid(),
|
requestId: uuid(),
|
||||||
request: {
|
request: {
|
||||||
message: message,
|
message: message,
|
||||||
@ -19,9 +19,9 @@ export class PushRpc {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static decode(bytes: Uint8ArrayList): PushRpc {
|
public static decode(bytes: Uint8ArrayList): PushRpcV2 {
|
||||||
const res = proto.PushRpc.decode(bytes);
|
const res = proto.PushRpc.decode(bytes);
|
||||||
return new PushRpc(res);
|
return new PushRpcV2(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
public encode(): Uint8Array {
|
public encode(): Uint8Array {
|
||||||
|
|||||||
162
packages/core/src/lib/light_push/push_rpc_v3.ts
Normal file
162
packages/core/src/lib/light_push/push_rpc_v3.ts
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
import { proto_lightpush as proto } from "@waku/proto";
|
||||||
|
import type { Uint8ArrayList } from "uint8arraylist";
|
||||||
|
import { v4 as uuid } from "uuid";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LightPush v3 protocol RPC handler.
|
||||||
|
* Implements the v3 message format with correct field numbers:
|
||||||
|
* - requestId: 1
|
||||||
|
* - pubsubTopic: 20
|
||||||
|
* - message: 21
|
||||||
|
*/
|
||||||
|
export class PushRpc {
|
||||||
|
public constructor(
|
||||||
|
public proto: proto.LightPushRequestV3 | proto.LightPushResponseV3
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a v3 request message with proper field numbering
|
||||||
|
*/
|
||||||
|
public static createRequest(
|
||||||
|
message: proto.WakuMessage,
|
||||||
|
pubsubTopic: string
|
||||||
|
): PushRpc {
|
||||||
|
return new PushRpc({
|
||||||
|
requestId: uuid(),
|
||||||
|
pubsubTopic: pubsubTopic,
|
||||||
|
message: message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a v3 response message with status code handling
|
||||||
|
*/
|
||||||
|
public static createResponse(
|
||||||
|
requestId: string,
|
||||||
|
statusCode: number,
|
||||||
|
statusDesc?: string,
|
||||||
|
relayPeerCount?: number
|
||||||
|
): PushRpc {
|
||||||
|
return new PushRpc({
|
||||||
|
requestId,
|
||||||
|
statusCode,
|
||||||
|
statusDesc,
|
||||||
|
relayPeerCount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode v3 request message
|
||||||
|
*/
|
||||||
|
public static decodeRequest(bytes: Uint8ArrayList): PushRpc {
|
||||||
|
const res = proto.LightPushRequestV3.decode(bytes);
|
||||||
|
return new PushRpc(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode v3 response message
|
||||||
|
*/
|
||||||
|
public static decodeResponse(bytes: Uint8ArrayList): PushRpc {
|
||||||
|
const res = proto.LightPushResponseV3.decode(bytes);
|
||||||
|
return new PushRpc(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode message to bytes
|
||||||
|
*/
|
||||||
|
public encode(): Uint8Array {
|
||||||
|
if (this.isRequest()) {
|
||||||
|
return proto.LightPushRequestV3.encode(
|
||||||
|
this.proto as proto.LightPushRequestV3
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return proto.LightPushResponseV3.encode(
|
||||||
|
this.proto as proto.LightPushResponseV3
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get request data (if this is a request message)
|
||||||
|
*/
|
||||||
|
public get request(): proto.LightPushRequestV3 | undefined {
|
||||||
|
return this.isRequest()
|
||||||
|
? (this.proto as proto.LightPushRequestV3)
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get response data (if this is a response message)
|
||||||
|
*/
|
||||||
|
public get response(): proto.LightPushResponseV3 | undefined {
|
||||||
|
return this.isResponse()
|
||||||
|
? (this.proto as proto.LightPushResponseV3)
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the request ID
|
||||||
|
*/
|
||||||
|
public get requestId(): string {
|
||||||
|
return this.proto.requestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the pubsub topic (only available in requests)
|
||||||
|
*/
|
||||||
|
public get pubsubTopic(): string | undefined {
|
||||||
|
return this.isRequest()
|
||||||
|
? (this.proto as proto.LightPushRequestV3).pubsubTopic
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message (only available in requests)
|
||||||
|
*/
|
||||||
|
public get message(): proto.WakuMessage | undefined {
|
||||||
|
return this.isRequest()
|
||||||
|
? (this.proto as proto.LightPushRequestV3).message
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the status code (only available in responses)
|
||||||
|
*/
|
||||||
|
public get statusCode(): number | undefined {
|
||||||
|
return this.isResponse()
|
||||||
|
? (this.proto as proto.LightPushResponseV3).statusCode
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the status description (only available in responses)
|
||||||
|
*/
|
||||||
|
public get statusDesc(): string | undefined {
|
||||||
|
return this.isResponse()
|
||||||
|
? (this.proto as proto.LightPushResponseV3).statusDesc
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the relay peer count (only available in responses)
|
||||||
|
*/
|
||||||
|
public get relayPeerCount(): number | undefined {
|
||||||
|
return this.isResponse()
|
||||||
|
? (this.proto as proto.LightPushResponseV3).relayPeerCount
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this is a request message
|
||||||
|
*/
|
||||||
|
private isRequest(): boolean {
|
||||||
|
return "pubsubTopic" in this.proto && "message" in this.proto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this is a response message
|
||||||
|
*/
|
||||||
|
private isResponse(): boolean {
|
||||||
|
return "statusCode" in this.proto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,7 +13,7 @@ export class StreamManager {
|
|||||||
private streamPool: Map<string, Promise<void>> = new Map();
|
private streamPool: Map<string, Promise<void>> = new Map();
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private multicodec: string,
|
private readonly multicodec: string,
|
||||||
private readonly libp2p: Libp2pComponents
|
private readonly libp2p: Libp2pComponents
|
||||||
) {
|
) {
|
||||||
this.log = new Logger(`stream-manager:${multicodec}`);
|
this.log = new Logger(`stream-manager:${multicodec}`);
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
import type { ISender, ISendOptions } from "./sender.js";
|
import { IEncoder, IMessage } from "./message.js";
|
||||||
|
import { LightPushSDKResult } from "./protocols.js";
|
||||||
|
import type { ISendOptions } from "./sender.js";
|
||||||
|
|
||||||
export type LightPushProtocolOptions = ISendOptions & {
|
export type LightPushProtocolOptions = ISendOptions & {
|
||||||
/**
|
/**
|
||||||
@ -15,8 +17,40 @@ export type LightPushProtocolOptions = ISendOptions & {
|
|||||||
numPeersToUse?: number;
|
numPeersToUse?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ILightPush = ISender & {
|
export type ILightPush = {
|
||||||
readonly multicodec: string;
|
readonly multicodec: string[];
|
||||||
start: () => void;
|
start: () => void;
|
||||||
stop: () => void;
|
stop: () => void;
|
||||||
|
send: (
|
||||||
|
encoder: IEncoder,
|
||||||
|
message: IMessage,
|
||||||
|
options?: ISendOptions
|
||||||
|
) => Promise<LightPushSDKResult>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum LightPushStatusCode {
|
||||||
|
SUCCESS = 200,
|
||||||
|
BAD_REQUEST = 400,
|
||||||
|
PAYLOAD_TOO_LARGE = 413,
|
||||||
|
INVALID_MESSAGE = 420,
|
||||||
|
UNSUPPORTED_TOPIC = 421,
|
||||||
|
TOO_MANY_REQUESTS = 429,
|
||||||
|
INTERNAL_ERROR = 500,
|
||||||
|
UNAVAILABLE = 503,
|
||||||
|
NO_RLN_PROOF = 504,
|
||||||
|
NO_PEERS = 505
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StatusDescriptions: Record<LightPushStatusCode, string> = {
|
||||||
|
[LightPushStatusCode.SUCCESS]: "Message sent successfully",
|
||||||
|
[LightPushStatusCode.BAD_REQUEST]: "Bad request format",
|
||||||
|
[LightPushStatusCode.PAYLOAD_TOO_LARGE]:
|
||||||
|
"Message payload exceeds maximum size",
|
||||||
|
[LightPushStatusCode.INVALID_MESSAGE]: "Message validation failed",
|
||||||
|
[LightPushStatusCode.UNSUPPORTED_TOPIC]: "Unsupported pubsub topic",
|
||||||
|
[LightPushStatusCode.TOO_MANY_REQUESTS]: "Rate limit exceeded",
|
||||||
|
[LightPushStatusCode.INTERNAL_ERROR]: "Internal server error",
|
||||||
|
[LightPushStatusCode.UNAVAILABLE]: "Service temporarily unavailable",
|
||||||
|
[LightPushStatusCode.NO_RLN_PROOF]: "RLN proof generation failed",
|
||||||
|
[LightPushStatusCode.NO_PEERS]: "No relay peers available"
|
||||||
};
|
};
|
||||||
|
|||||||
@ -130,117 +130,123 @@ export type Callback<T extends IDecodedMessage> = (
|
|||||||
msg: T
|
msg: T
|
||||||
) => void | Promise<void>;
|
) => void | Promise<void>;
|
||||||
|
|
||||||
export enum ProtocolError {
|
export enum LightPushError {
|
||||||
//
|
|
||||||
// GENERAL ERRORS SECTION
|
|
||||||
//
|
|
||||||
/**
|
|
||||||
* Could not determine the origin of the fault. Best to check connectivity and try again
|
|
||||||
* */
|
|
||||||
GENERIC_FAIL = "Generic error",
|
GENERIC_FAIL = "Generic error",
|
||||||
|
|
||||||
/**
|
|
||||||
* The remote peer rejected the message. Information provided by the remote peer
|
|
||||||
* is logged. Review message validity, or mitigation for `NO_PEER_AVAILABLE`
|
|
||||||
* or `DECODE_FAILED` can be used.
|
|
||||||
*/
|
|
||||||
REMOTE_PEER_REJECTED = "Remote peer rejected",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Failure to protobuf decode the message. May be due to a remote peer issue,
|
|
||||||
* ensuring that messages are sent via several peer enable mitigation of this error.
|
|
||||||
*/
|
|
||||||
DECODE_FAILED = "Failed to decode",
|
DECODE_FAILED = "Failed to decode",
|
||||||
|
|
||||||
/**
|
|
||||||
* Failure to find a peer with suitable protocols. This may due to a connection issue.
|
|
||||||
* Mitigation can be: retrying after a given time period, display connectivity issue
|
|
||||||
* to user or listening for `peer:connected:bootstrap` or `peer:connected:peer-exchange`
|
|
||||||
* on the connection manager before retrying.
|
|
||||||
*/
|
|
||||||
NO_PEER_AVAILABLE = "No peer available",
|
NO_PEER_AVAILABLE = "No peer available",
|
||||||
|
|
||||||
/**
|
|
||||||
* Failure to find a stream to the peer. This may be because the connection with the peer is not still alive.
|
|
||||||
* Mitigation can be: retrying after a given time period, or mitigation for `NO_PEER_AVAILABLE` can be used.
|
|
||||||
*/
|
|
||||||
NO_STREAM_AVAILABLE = "No stream available",
|
NO_STREAM_AVAILABLE = "No stream available",
|
||||||
|
|
||||||
/**
|
|
||||||
* The remote peer did not behave as expected. Mitigation for `NO_PEER_AVAILABLE`
|
|
||||||
* or `DECODE_FAILED` can be used.
|
|
||||||
*/
|
|
||||||
NO_RESPONSE = "No response received",
|
NO_RESPONSE = "No response received",
|
||||||
|
|
||||||
//
|
|
||||||
// SEND ERRORS SECTION
|
|
||||||
//
|
|
||||||
/**
|
|
||||||
* Failure to protobuf encode the message. This is not recoverable and needs
|
|
||||||
* further investigation.
|
|
||||||
*/
|
|
||||||
ENCODE_FAILED = "Failed to encode",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The message payload is empty, making the message invalid. Ensure that a non-empty
|
|
||||||
* payload is set on the outgoing message.
|
|
||||||
*/
|
|
||||||
EMPTY_PAYLOAD = "Payload is empty",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The message size is above the maximum message size allowed on the Waku Network.
|
|
||||||
* Compressing the message or using an alternative strategy for large messages is recommended.
|
|
||||||
*/
|
|
||||||
SIZE_TOO_BIG = "Size is too big",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The PubsubTopic passed to the send function is not configured on the Waku node.
|
|
||||||
* Please ensure that the PubsubTopic is used when initializing the Waku node.
|
|
||||||
*/
|
|
||||||
TOPIC_NOT_CONFIGURED = "Topic not configured",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fails when
|
|
||||||
*/
|
|
||||||
STREAM_ABORTED = "Stream aborted",
|
STREAM_ABORTED = "Stream aborted",
|
||||||
|
|
||||||
/**
|
ENCODE_FAILED = "Failed to encode",
|
||||||
* General proof generation error message.
|
EMPTY_PAYLOAD = "Payload is empty",
|
||||||
* nwaku: https://github.com/waku-org/nwaku/blob/c3cb06ac6c03f0f382d3941ea53b330f6a8dd127/waku/waku_rln_relay/group_manager/group_manager_base.nim#L201C19-L201C42
|
SIZE_TOO_BIG = "Size is too big",
|
||||||
*/
|
TOPIC_NOT_CONFIGURED = "Topic not configured",
|
||||||
RLN_PROOF_GENERATION = "Proof generation failed",
|
RLN_PROOF_GENERATION = "Proof generation failed",
|
||||||
|
REMOTE_PEER_REJECTED = "Remote peer rejected",
|
||||||
|
|
||||||
//
|
BAD_REQUEST = "Bad request format",
|
||||||
// RECEIVE ERRORS SECTION
|
PAYLOAD_TOO_LARGE = "Message payload exceeds maximum size",
|
||||||
//
|
INVALID_MESSAGE = "Message validation failed",
|
||||||
/**
|
UNSUPPORTED_TOPIC = "Unsupported pubsub topic",
|
||||||
* The pubsub topic configured on the decoder does not match the pubsub topic setup on the protocol.
|
TOO_MANY_REQUESTS = "Rate limit exceeded",
|
||||||
* Ensure that the pubsub topic used for decoder creation is the same as the one used for protocol.
|
INTERNAL_ERROR = "Internal server error",
|
||||||
*/
|
UNAVAILABLE = "Service temporarily unavailable",
|
||||||
TOPIC_DECODER_MISMATCH = "Topic decoder mismatch",
|
NO_RLN_PROOF = "RLN proof generation failed",
|
||||||
|
NO_PEERS = "No relay peers available"
|
||||||
/**
|
|
||||||
* The topics passed in the decoders do not match each other, or don't exist at all.
|
|
||||||
* Ensure that all the pubsub topics used in the decoders are valid and match each other.
|
|
||||||
*/
|
|
||||||
INVALID_DECODER_TOPICS = "Invalid decoder topics"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Failure {
|
export enum FilterError {
|
||||||
error: ProtocolError;
|
// General errors
|
||||||
|
GENERIC_FAIL = "Generic error",
|
||||||
|
DECODE_FAILED = "Failed to decode",
|
||||||
|
NO_PEER_AVAILABLE = "No peer available",
|
||||||
|
NO_STREAM_AVAILABLE = "No stream available",
|
||||||
|
NO_RESPONSE = "No response received",
|
||||||
|
STREAM_ABORTED = "Stream aborted",
|
||||||
|
|
||||||
|
// Filter specific errors
|
||||||
|
REMOTE_PEER_REJECTED = "Remote peer rejected",
|
||||||
|
TOPIC_NOT_CONFIGURED = "Topic not configured",
|
||||||
|
SUBSCRIPTION_FAILED = "Subscription failed",
|
||||||
|
UNSUBSCRIBE_FAILED = "Unsubscribe failed",
|
||||||
|
PING_FAILED = "Ping failed",
|
||||||
|
TOPIC_DECODER_MISMATCH = "Topic decoder mismatch",
|
||||||
|
INVALID_DECODER_TOPICS = "Invalid decoder topics",
|
||||||
|
SUBSCRIPTION_LIMIT_EXCEEDED = "Subscription limit exceeded",
|
||||||
|
INVALID_CONTENT_TOPIC = "Invalid content topic",
|
||||||
|
PUSH_MESSAGE_FAILED = "Push message failed",
|
||||||
|
EMPTY_MESSAGE = "Empty message received",
|
||||||
|
MISSING_PUBSUB_TOPIC = "Pubsub topic missing from push message"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LightPushFailure {
|
||||||
|
error: LightPushError;
|
||||||
peerId?: PeerId;
|
peerId?: PeerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CoreProtocolResult = ThisOrThat<
|
export interface FilterFailure {
|
||||||
|
error: FilterError;
|
||||||
|
peerId?: PeerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LightPushCoreResult = ThisOrThat<
|
||||||
"success",
|
"success",
|
||||||
PeerId,
|
PeerId,
|
||||||
"failure",
|
"failure",
|
||||||
Failure
|
LightPushFailure
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export type FilterCoreResult = ThisOrThat<
|
||||||
|
"success",
|
||||||
|
PeerId,
|
||||||
|
"failure",
|
||||||
|
FilterFailure
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type LightPushSDKResult = ThisAndThat<
|
||||||
|
"successes",
|
||||||
|
PeerId[],
|
||||||
|
"failures",
|
||||||
|
LightPushFailure[]
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type FilterSDKResult = ThisAndThat<
|
||||||
|
"successes",
|
||||||
|
PeerId[],
|
||||||
|
"failures",
|
||||||
|
FilterFailure[]
|
||||||
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated replace usage by specific result types
|
||||||
|
*/
|
||||||
export type SDKProtocolResult = ThisAndThat<
|
export type SDKProtocolResult = ThisAndThat<
|
||||||
"successes",
|
"successes",
|
||||||
PeerId[],
|
PeerId[],
|
||||||
"failures",
|
"failures",
|
||||||
Failure[]
|
Array<{
|
||||||
|
error: ProtocolError;
|
||||||
|
peerId?: PeerId;
|
||||||
|
}>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated replace usage by specific result types
|
||||||
|
*/
|
||||||
|
export enum ProtocolError {
|
||||||
|
GENERIC_FAIL = "Generic error",
|
||||||
|
REMOTE_PEER_REJECTED = "Remote peer rejected",
|
||||||
|
DECODE_FAILED = "Failed to decode",
|
||||||
|
NO_PEER_AVAILABLE = "No peer available",
|
||||||
|
NO_STREAM_AVAILABLE = "No stream available",
|
||||||
|
NO_RESPONSE = "No response received",
|
||||||
|
ENCODE_FAILED = "Failed to encode",
|
||||||
|
EMPTY_PAYLOAD = "Payload is empty",
|
||||||
|
SIZE_TOO_BIG = "Size is too big",
|
||||||
|
TOPIC_NOT_CONFIGURED = "Topic not configured",
|
||||||
|
STREAM_ABORTED = "Stream aborted",
|
||||||
|
RLN_PROOF_GENERATION = "Proof generation failed",
|
||||||
|
TOPIC_DECODER_MISMATCH = "Topic decoder mismatch",
|
||||||
|
INVALID_DECODER_TOPICS = "Invalid decoder topics"
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { IEncoder, IMessage } from "./message.js";
|
import type { IEncoder, IMessage } from "./message.js";
|
||||||
import { SDKProtocolResult } from "./protocols.js";
|
import { LightPushSDKResult } from "./protocols.js";
|
||||||
|
|
||||||
export type ISendOptions = {
|
export type ISendOptions = {
|
||||||
/**
|
/**
|
||||||
@ -13,6 +13,13 @@ export type ISendOptions = {
|
|||||||
* @default 3
|
* @default 3
|
||||||
*/
|
*/
|
||||||
maxAttempts?: number;
|
maxAttempts?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use v2 of the light push protocol.
|
||||||
|
* This parameter will be removed in the future.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
useLegacy?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ISender {
|
export interface ISender {
|
||||||
@ -20,5 +27,5 @@ export interface ISender {
|
|||||||
encoder: IEncoder,
|
encoder: IEncoder,
|
||||||
message: IMessage,
|
message: IMessage,
|
||||||
sendOptions?: ISendOptions
|
sendOptions?: ISendOptions
|
||||||
) => Promise<SDKProtocolResult>;
|
) => Promise<LightPushSDKResult>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,9 +19,9 @@ import {
|
|||||||
IRelay,
|
IRelay,
|
||||||
type IRoutingInfo,
|
type IRoutingInfo,
|
||||||
Libp2p,
|
Libp2p,
|
||||||
ProtocolError,
|
LightPushError,
|
||||||
PubsubTopic,
|
LightPushSDKResult,
|
||||||
SDKProtocolResult
|
PubsubTopic
|
||||||
} from "@waku/interfaces";
|
} from "@waku/interfaces";
|
||||||
import { isWireSizeUnderCap, toAsyncIterator } from "@waku/utils";
|
import { isWireSizeUnderCap, toAsyncIterator } from "@waku/utils";
|
||||||
import { pushOrInitMapSet } from "@waku/utils";
|
import { pushOrInitMapSet } from "@waku/utils";
|
||||||
@ -127,7 +127,7 @@ export class Relay implements IRelay {
|
|||||||
public async send(
|
public async send(
|
||||||
encoder: IEncoder,
|
encoder: IEncoder,
|
||||||
message: IMessage
|
message: IMessage
|
||||||
): Promise<SDKProtocolResult> {
|
): Promise<LightPushSDKResult> {
|
||||||
const { pubsubTopic } = encoder;
|
const { pubsubTopic } = encoder;
|
||||||
if (!this.pubsubTopics.has(pubsubTopic)) {
|
if (!this.pubsubTopics.has(pubsubTopic)) {
|
||||||
log.error("Failed to send waku relay: topic not configured");
|
log.error("Failed to send waku relay: topic not configured");
|
||||||
@ -135,7 +135,7 @@ export class Relay implements IRelay {
|
|||||||
successes: [],
|
successes: [],
|
||||||
failures: [
|
failures: [
|
||||||
{
|
{
|
||||||
error: ProtocolError.TOPIC_NOT_CONFIGURED
|
error: LightPushError.TOPIC_NOT_CONFIGURED
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@ -148,7 +148,7 @@ export class Relay implements IRelay {
|
|||||||
successes: [],
|
successes: [],
|
||||||
failures: [
|
failures: [
|
||||||
{
|
{
|
||||||
error: ProtocolError.ENCODE_FAILED
|
error: LightPushError.ENCODE_FAILED
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@ -160,7 +160,7 @@ export class Relay implements IRelay {
|
|||||||
successes: [],
|
successes: [],
|
||||||
failures: [
|
failures: [
|
||||||
{
|
{
|
||||||
error: ProtocolError.SIZE_TOO_BIG
|
error: LightPushError.SIZE_TOO_BIG
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
import { Peer, PeerId } from "@libp2p/interface";
|
import { Peer, PeerId } from "@libp2p/interface";
|
||||||
import { createEncoder, Encoder, LightPushCodec } from "@waku/core";
|
import {
|
||||||
import { Libp2p, ProtocolError } from "@waku/interfaces";
|
createEncoder,
|
||||||
|
Encoder,
|
||||||
|
LightPushCodec,
|
||||||
|
LightPushCodecV2
|
||||||
|
} from "@waku/core";
|
||||||
|
import { Libp2p, LightPushError, LightPushStatusCode } from "@waku/interfaces";
|
||||||
import { createRoutingInfo } from "@waku/utils";
|
import { createRoutingInfo } from "@waku/utils";
|
||||||
import { utf8ToBytes } from "@waku/utils/bytes";
|
import { utf8ToBytes } from "@waku/utils/bytes";
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
@ -40,8 +45,8 @@ describe("LightPush SDK", () => {
|
|||||||
const failures = result.failures ?? [];
|
const failures = result.failures ?? [];
|
||||||
|
|
||||||
expect(failures.length).to.be.eq(1);
|
expect(failures.length).to.be.eq(1);
|
||||||
expect(failures.some((v) => v.error === ProtocolError.NO_PEER_AVAILABLE)).to
|
expect(failures.some((v) => v.error === LightPushError.NO_PEER_AVAILABLE))
|
||||||
.be.true;
|
.to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should send to specified number of peers of used peers", async () => {
|
it("should send to specified number of peers of used peers", async () => {
|
||||||
@ -127,6 +132,45 @@ describe("LightPush SDK", () => {
|
|||||||
expect(result.successes?.length).to.be.eq(1);
|
expect(result.successes?.length).to.be.eq(1);
|
||||||
expect(result.failures?.length).to.be.eq(1);
|
expect(result.failures?.length).to.be.eq(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("v3 protocol support", () => {
|
||||||
|
it("should work with v3 peers", async () => {
|
||||||
|
libp2p = mockLibp2p({
|
||||||
|
peers: [mockV3Peer("1"), mockV3Peer("2")]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should work with mixed v2 and v3 peers", async () => {
|
||||||
|
libp2p = mockLibp2p({
|
||||||
|
peers: [mockV2AndV3Peer("1"), mockPeer("2"), mockV3Peer("3")]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock responses for different protocol versions
|
||||||
|
const v3Response = mockV3SuccessResponse(5);
|
||||||
|
const v2Response = mockV2SuccessResponse();
|
||||||
|
const v3ErrorResponse = mockV3ErrorResponse(
|
||||||
|
LightPushStatusCode.PAYLOAD_TOO_LARGE
|
||||||
|
);
|
||||||
|
const v2ErrorResponse = mockV2ErrorResponse("Message too large");
|
||||||
|
|
||||||
|
expect(v3Response.statusCode).to.eq(LightPushStatusCode.SUCCESS);
|
||||||
|
expect(v3Response.relayPeerCount).to.eq(5);
|
||||||
|
expect(v2Response.isSuccess).to.be.true;
|
||||||
|
expect(v3ErrorResponse.statusCode).to.eq(
|
||||||
|
LightPushStatusCode.PAYLOAD_TOO_LARGE
|
||||||
|
);
|
||||||
|
expect(v2ErrorResponse.isSuccess).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle v3 RLN errors", async () => {
|
||||||
|
const v3RLNError = mockV3RLNErrorResponse();
|
||||||
|
const v2RLNError = mockV2RLNErrorResponse();
|
||||||
|
|
||||||
|
expect(v3RLNError.statusCode).to.eq(LightPushStatusCode.NO_RLN_PROOF);
|
||||||
|
expect(v3RLNError.statusDesc).to.include("RLN proof generation failed");
|
||||||
|
expect(v2RLNError.info).to.include("RLN proof generation failed");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
type MockLibp2pOptions = {
|
type MockLibp2pOptions = {
|
||||||
@ -136,7 +180,16 @@ type MockLibp2pOptions = {
|
|||||||
function mockLibp2p(options?: MockLibp2pOptions): Libp2p {
|
function mockLibp2p(options?: MockLibp2pOptions): Libp2p {
|
||||||
const peers = options?.peers || [];
|
const peers = options?.peers || [];
|
||||||
const peerStore = {
|
const peerStore = {
|
||||||
get: (id: any) => Promise.resolve(peers.find((p) => p.id === id))
|
get: (id: any) => {
|
||||||
|
const peer = peers.find((p) => p.id === id);
|
||||||
|
if (peer) {
|
||||||
|
return Promise.resolve({
|
||||||
|
...peer,
|
||||||
|
protocols: peer.protocols || [LightPushCodec]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -179,9 +232,92 @@ function mockLightPush(options: MockLightPushOptions): LightPush {
|
|||||||
return lightPush;
|
return lightPush;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mockPeer(id: string): Peer {
|
function mockPeer(id: string, protocols: string[] = [LightPushCodec]): Peer {
|
||||||
return {
|
return {
|
||||||
id,
|
id: { toString: () => id } as PeerId,
|
||||||
protocols: [LightPushCodec]
|
protocols: protocols,
|
||||||
} as unknown as Peer;
|
metadata: new Map(),
|
||||||
|
addresses: [],
|
||||||
|
tags: new Map()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// V3-specific mock functions
|
||||||
|
function mockV3Peer(id: string): Peer {
|
||||||
|
return mockPeer(id, [LightPushCodec]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockV2AndV3Peer(id: string): Peer {
|
||||||
|
return mockPeer(id, [LightPushCodec, LightPushCodecV2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockV3SuccessResponse(relayPeerCount?: number): {
|
||||||
|
statusCode: LightPushStatusCode;
|
||||||
|
statusDesc: string;
|
||||||
|
relayPeerCount?: number;
|
||||||
|
isSuccess: boolean;
|
||||||
|
} {
|
||||||
|
return {
|
||||||
|
statusCode: LightPushStatusCode.SUCCESS,
|
||||||
|
statusDesc: "Message sent successfully",
|
||||||
|
relayPeerCount,
|
||||||
|
isSuccess: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockV3ErrorResponse(
|
||||||
|
statusCode: LightPushStatusCode,
|
||||||
|
statusDesc?: string
|
||||||
|
): {
|
||||||
|
statusCode: LightPushStatusCode;
|
||||||
|
statusDesc: string;
|
||||||
|
isSuccess: boolean;
|
||||||
|
} {
|
||||||
|
return {
|
||||||
|
statusCode,
|
||||||
|
statusDesc: statusDesc || "Error occurred",
|
||||||
|
isSuccess: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockV2SuccessResponse(): {
|
||||||
|
isSuccess: boolean;
|
||||||
|
info: string;
|
||||||
|
} {
|
||||||
|
return {
|
||||||
|
isSuccess: true,
|
||||||
|
info: "Message sent successfully"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockV2ErrorResponse(info?: string): {
|
||||||
|
isSuccess: boolean;
|
||||||
|
info: string;
|
||||||
|
} {
|
||||||
|
return {
|
||||||
|
isSuccess: false,
|
||||||
|
info: info || "Error occurred"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockV3RLNErrorResponse(): {
|
||||||
|
statusCode: LightPushStatusCode;
|
||||||
|
statusDesc: string;
|
||||||
|
isSuccess: boolean;
|
||||||
|
} {
|
||||||
|
return {
|
||||||
|
statusCode: LightPushStatusCode.NO_RLN_PROOF,
|
||||||
|
statusDesc: "RLN proof generation failed",
|
||||||
|
isSuccess: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockV2RLNErrorResponse(): {
|
||||||
|
isSuccess: boolean;
|
||||||
|
info: string;
|
||||||
|
} {
|
||||||
|
return {
|
||||||
|
isSuccess: false,
|
||||||
|
info: "RLN proof generation failed"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
import type { PeerId } from "@libp2p/interface";
|
import type { PeerId } from "@libp2p/interface";
|
||||||
import { LightPushCore } from "@waku/core";
|
import { LightPushCore } from "@waku/core";
|
||||||
import {
|
import {
|
||||||
type CoreProtocolResult,
|
|
||||||
Failure,
|
|
||||||
type IEncoder,
|
type IEncoder,
|
||||||
ILightPush,
|
ILightPush,
|
||||||
type IMessage,
|
type IMessage,
|
||||||
type ISendOptions,
|
type ISendOptions,
|
||||||
type Libp2p,
|
type Libp2p,
|
||||||
|
LightPushCoreResult,
|
||||||
|
LightPushError,
|
||||||
|
LightPushFailure,
|
||||||
type LightPushProtocolOptions,
|
type LightPushProtocolOptions,
|
||||||
ProtocolError,
|
LightPushSDKResult,
|
||||||
Protocols,
|
Protocols
|
||||||
SDKProtocolResult
|
|
||||||
} from "@waku/interfaces";
|
} from "@waku/interfaces";
|
||||||
import { Logger } from "@waku/utils";
|
import { Logger } from "@waku/utils";
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ export class LightPush implements ILightPush {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public get multicodec(): string {
|
public get multicodec(): string[] {
|
||||||
return this.protocol.multicodec;
|
return this.protocol.multicodec;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,8 +71,9 @@ export class LightPush implements ILightPush {
|
|||||||
encoder: IEncoder,
|
encoder: IEncoder,
|
||||||
message: IMessage,
|
message: IMessage,
|
||||||
options: ISendOptions = {}
|
options: ISendOptions = {}
|
||||||
): Promise<SDKProtocolResult> {
|
): Promise<LightPushSDKResult> {
|
||||||
options = {
|
options = {
|
||||||
|
useLegacy: false,
|
||||||
...this.config,
|
...this.config,
|
||||||
...options
|
...options
|
||||||
};
|
};
|
||||||
@ -82,45 +83,48 @@ export class LightPush implements ILightPush {
|
|||||||
log.info("send: attempting to send a message to pubsubTopic:", pubsubTopic);
|
log.info("send: attempting to send a message to pubsubTopic:", pubsubTopic);
|
||||||
|
|
||||||
const peerIds = await this.peerManager.getPeers({
|
const peerIds = await this.peerManager.getPeers({
|
||||||
protocol: Protocols.LightPush,
|
protocol: options.useLegacy ? "light-push-v2" : Protocols.LightPush,
|
||||||
pubsubTopic: encoder.pubsubTopic
|
pubsubTopic: encoder.pubsubTopic
|
||||||
});
|
});
|
||||||
|
|
||||||
const coreResults: CoreProtocolResult[] =
|
const coreResults =
|
||||||
peerIds?.length > 0
|
peerIds?.length > 0
|
||||||
? await Promise.all(
|
? await Promise.all(
|
||||||
peerIds.map((peerId) =>
|
peerIds.map((peerId) =>
|
||||||
this.protocol.send(encoder, message, peerId).catch((_e) => ({
|
this.protocol
|
||||||
|
.send(encoder, message, peerId, options.useLegacy)
|
||||||
|
.catch((_e) => ({
|
||||||
success: null,
|
success: null,
|
||||||
failure: {
|
failure: {
|
||||||
error: ProtocolError.GENERIC_FAIL
|
error: LightPushError.GENERIC_FAIL
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const results: SDKProtocolResult = coreResults.length
|
const results: LightPushSDKResult = coreResults.length
|
||||||
? {
|
? {
|
||||||
successes: coreResults
|
successes: coreResults
|
||||||
.filter((v) => v.success)
|
.filter((v) => v.success)
|
||||||
.map((v) => v.success) as PeerId[],
|
.map((v) => v.success) as PeerId[],
|
||||||
failures: coreResults
|
failures: coreResults
|
||||||
.filter((v) => v.failure)
|
.filter((v) => v.failure)
|
||||||
.map((v) => v.failure) as Failure[]
|
.map((v) => v.failure) as LightPushFailure[]
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
successes: [],
|
successes: [],
|
||||||
failures: [
|
failures: [
|
||||||
{
|
{
|
||||||
error: ProtocolError.NO_PEER_AVAILABLE
|
error: LightPushError.NO_PEER_AVAILABLE
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
if (options.autoRetry && results.successes.length === 0) {
|
if (options.autoRetry && results.successes.length === 0) {
|
||||||
const sendCallback = (peerId: PeerId): Promise<CoreProtocolResult> =>
|
const sendCallback = (peerId: PeerId): Promise<LightPushCoreResult> =>
|
||||||
this.protocol.send(encoder, message, peerId);
|
this.protocol.send(encoder, message, peerId, options.useLegacy);
|
||||||
|
|
||||||
this.retryManager.push(
|
this.retryManager.push(
|
||||||
sendCallback.bind(this),
|
sendCallback.bind(this),
|
||||||
options.maxAttempts || DEFAULT_MAX_ATTEMPTS,
|
options.maxAttempts || DEFAULT_MAX_ATTEMPTS,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import type { PeerId } from "@libp2p/interface";
|
import type { PeerId } from "@libp2p/interface";
|
||||||
import {
|
import {
|
||||||
type CoreProtocolResult,
|
type LightPushCoreResult,
|
||||||
|
LightPushError,
|
||||||
ProtocolError,
|
ProtocolError,
|
||||||
Protocols
|
Protocols
|
||||||
} from "@waku/interfaces";
|
} from "@waku/interfaces";
|
||||||
@ -59,7 +60,7 @@ describe("RetryManager", () => {
|
|||||||
|
|
||||||
it("should process tasks in queue", async () => {
|
it("should process tasks in queue", async () => {
|
||||||
const successCallback = sinon.spy(
|
const successCallback = sinon.spy(
|
||||||
async (peerId: PeerId): Promise<CoreProtocolResult> => ({
|
async (peerId: PeerId): Promise<LightPushCoreResult> => ({
|
||||||
success: peerId,
|
success: peerId,
|
||||||
failure: null
|
failure: null
|
||||||
})
|
})
|
||||||
@ -112,9 +113,9 @@ describe("RetryManager", () => {
|
|||||||
|
|
||||||
it("should retry failed tasks", async () => {
|
it("should retry failed tasks", async () => {
|
||||||
const failingCallback = sinon.spy(
|
const failingCallback = sinon.spy(
|
||||||
async (): Promise<CoreProtocolResult> => ({
|
async (): Promise<LightPushCoreResult> => ({
|
||||||
success: null,
|
success: null,
|
||||||
failure: { error: "test error" as any }
|
failure: { error: LightPushError.GENERIC_FAIL }
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -135,7 +136,7 @@ describe("RetryManager", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should request peer renewal on specific errors", async () => {
|
it("should request peer renewal on specific errors", async () => {
|
||||||
const errorCallback = sinon.spy(async (): Promise<CoreProtocolResult> => {
|
const errorCallback = sinon.spy(async (): Promise<LightPushCoreResult> => {
|
||||||
throw new Error(ProtocolError.NO_PEER_AVAILABLE);
|
throw new Error(ProtocolError.NO_PEER_AVAILABLE);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -155,7 +156,7 @@ describe("RetryManager", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should handle task timeouts", async () => {
|
it("should handle task timeouts", async () => {
|
||||||
const slowCallback = sinon.spy(async (): Promise<CoreProtocolResult> => {
|
const slowCallback = sinon.spy(async (): Promise<LightPushCoreResult> => {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 15000));
|
await new Promise((resolve) => setTimeout(resolve, 15000));
|
||||||
return { success: mockPeerId, failure: null };
|
return { success: mockPeerId, failure: null };
|
||||||
});
|
});
|
||||||
@ -174,9 +175,11 @@ describe("RetryManager", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should not execute task if max attempts is 0", async () => {
|
it("should not execute task if max attempts is 0", async () => {
|
||||||
const failingCallback = sinon.spy(async (): Promise<CoreProtocolResult> => {
|
const failingCallback = sinon.spy(
|
||||||
|
async (): Promise<LightPushCoreResult> => {
|
||||||
throw new Error("test error" as any);
|
throw new Error("test error" as any);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const task = {
|
const task = {
|
||||||
callback: failingCallback,
|
callback: failingCallback,
|
||||||
@ -209,7 +212,7 @@ describe("RetryManager", () => {
|
|||||||
called++;
|
called++;
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
success: null,
|
success: null,
|
||||||
failure: { error: ProtocolError.GENERIC_FAIL }
|
failure: { error: LightPushError.GENERIC_FAIL }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
retryManager.push(failCallback, 2, TestRoutingInfo);
|
retryManager.push(failCallback, 2, TestRoutingInfo);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { PeerId } from "@libp2p/interface";
|
import type { PeerId } from "@libp2p/interface";
|
||||||
import {
|
import {
|
||||||
type CoreProtocolResult,
|
|
||||||
type IRoutingInfo,
|
type IRoutingInfo,
|
||||||
|
type LightPushCoreResult,
|
||||||
Protocols
|
Protocols
|
||||||
} from "@waku/interfaces";
|
} from "@waku/interfaces";
|
||||||
import { Logger } from "@waku/utils";
|
import { Logger } from "@waku/utils";
|
||||||
@ -15,7 +15,7 @@ type RetryManagerConfig = {
|
|||||||
peerManager: PeerManager;
|
peerManager: PeerManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
type AttemptCallback = (peerId: PeerId) => Promise<CoreProtocolResult>;
|
type AttemptCallback = (peerId: PeerId) => Promise<LightPushCoreResult>;
|
||||||
|
|
||||||
export type ScheduledTask = {
|
export type ScheduledTask = {
|
||||||
maxAttempts: number;
|
maxAttempts: number;
|
||||||
@ -123,7 +123,13 @@ export class RetryManager {
|
|||||||
task.callback(peerId)
|
task.callback(peerId)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (response?.failure) {
|
// If timeout resolves first, response will be void (undefined)
|
||||||
|
// In this case, we should treat it as a timeout error
|
||||||
|
if (response === undefined) {
|
||||||
|
throw new Error("Task timeout");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.failure) {
|
||||||
throw Error(response.failure.error);
|
throw Error(response.failure.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { ProtocolError } from "@waku/interfaces";
|
import { LightPushError } from "@waku/interfaces";
|
||||||
|
|
||||||
export const shouldPeerBeChanged = (
|
export const shouldPeerBeChanged = (
|
||||||
failure: string | ProtocolError
|
failure: string | LightPushError
|
||||||
): boolean => {
|
): boolean => {
|
||||||
const toBeChanged =
|
const toBeChanged =
|
||||||
failure === ProtocolError.REMOTE_PEER_REJECTED ||
|
failure === LightPushError.REMOTE_PEER_REJECTED ||
|
||||||
failure === ProtocolError.NO_RESPONSE ||
|
failure === LightPushError.NO_RESPONSE ||
|
||||||
failure === ProtocolError.RLN_PROOF_GENERATION ||
|
failure === LightPushError.RLN_PROOF_GENERATION ||
|
||||||
failure === ProtocolError.NO_PEER_AVAILABLE;
|
failure === LightPushError.NO_PEER_AVAILABLE;
|
||||||
|
|
||||||
if (toBeChanged) {
|
if (toBeChanged) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -85,7 +85,8 @@ describe("PeerManager", () => {
|
|||||||
_clusterId: ClusterId,
|
_clusterId: ClusterId,
|
||||||
_shardId: ShardId
|
_shardId: ShardId
|
||||||
) => true,
|
) => true,
|
||||||
isPeerOnTopic: async (_id: PeerId, _topic: string) => true
|
isPeerOnTopic: async (_id: PeerId, _topic: string) => true,
|
||||||
|
hasShardInfo: async (_id: PeerId) => true
|
||||||
} as unknown as IConnectionManager;
|
} as unknown as IConnectionManager;
|
||||||
peerManager = new PeerManager({
|
peerManager = new PeerManager({
|
||||||
libp2p,
|
libp2p,
|
||||||
|
|||||||
@ -4,7 +4,12 @@ import {
|
|||||||
PeerId,
|
PeerId,
|
||||||
TypedEventEmitter
|
TypedEventEmitter
|
||||||
} from "@libp2p/interface";
|
} from "@libp2p/interface";
|
||||||
import { FilterCodecs, LightPushCodec, StoreCodec } from "@waku/core";
|
import {
|
||||||
|
FilterCodecs,
|
||||||
|
LightPushCodec,
|
||||||
|
LightPushCodecV2,
|
||||||
|
StoreCodec
|
||||||
|
} from "@waku/core";
|
||||||
import {
|
import {
|
||||||
CONNECTION_LOCKED_TAG,
|
CONNECTION_LOCKED_TAG,
|
||||||
type IConnectionManager,
|
type IConnectionManager,
|
||||||
@ -28,8 +33,10 @@ type PeerManagerParams = {
|
|||||||
connectionManager: IConnectionManager;
|
connectionManager: IConnectionManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type SupportedProtocols = Protocols | "light-push-v2";
|
||||||
|
|
||||||
type GetPeersParams = {
|
type GetPeersParams = {
|
||||||
protocol: Protocols;
|
protocol: SupportedProtocols;
|
||||||
pubsubTopic: string;
|
pubsubTopic: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -119,7 +126,7 @@ export class PeerManager {
|
|||||||
|
|
||||||
for (const peer of connectedPeers) {
|
for (const peer of connectedPeers) {
|
||||||
const hasProtocol = this.hasPeerProtocol(peer, params.protocol);
|
const hasProtocol = this.hasPeerProtocol(peer, params.protocol);
|
||||||
const hasSamePubsub = await this.connectionManager.isPeerOnTopic(
|
const hasSamePubsub = await this.isPeerOnPubsub(
|
||||||
peer.id,
|
peer.id,
|
||||||
params.pubsubTopic
|
params.pubsubTopic
|
||||||
);
|
);
|
||||||
@ -204,12 +211,19 @@ export class PeerManager {
|
|||||||
|
|
||||||
private async onConnected(event: CustomEvent<IdentifyResult>): Promise<void> {
|
private async onConnected(event: CustomEvent<IdentifyResult>): Promise<void> {
|
||||||
const result = event.detail;
|
const result = event.detail;
|
||||||
if (
|
|
||||||
result.protocols.includes(this.matchProtocolToCodec(Protocols.Filter))
|
const isFilterPeer = result.protocols.includes(
|
||||||
) {
|
this.getProtocolCodecs(Protocols.Filter)
|
||||||
|
);
|
||||||
|
const isStorePeer = result.protocols.includes(
|
||||||
|
this.getProtocolCodecs(Protocols.Store)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isFilterPeer) {
|
||||||
this.dispatchFilterPeerConnect(result.peerId);
|
this.dispatchFilterPeerConnect(result.peerId);
|
||||||
}
|
}
|
||||||
if (result.protocols.includes(this.matchProtocolToCodec(Protocols.Store))) {
|
|
||||||
|
if (isStorePeer) {
|
||||||
this.dispatchStorePeerConnect(result.peerId);
|
this.dispatchStorePeerConnect(result.peerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -230,8 +244,8 @@ export class PeerManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private hasPeerProtocol(peer: Peer, protocol: Protocols): boolean {
|
private hasPeerProtocol(peer: Peer, protocol: SupportedProtocols): boolean {
|
||||||
return peer.protocols.includes(this.matchProtocolToCodec(protocol));
|
return peer.protocols.includes(this.getProtocolCodecs(protocol));
|
||||||
}
|
}
|
||||||
|
|
||||||
private lockPeer(id: PeerId): void {
|
private lockPeer(id: PeerId): void {
|
||||||
@ -289,14 +303,18 @@ export class PeerManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private matchProtocolToCodec(protocol: Protocols): string {
|
private getProtocolCodecs(protocol: SupportedProtocols): string {
|
||||||
const protocolToCodec = {
|
if (protocol === Protocols.Relay) {
|
||||||
|
throw new Error("Relay protocol is not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
const protocolToCodecs = {
|
||||||
[Protocols.Filter]: FilterCodecs.SUBSCRIBE,
|
[Protocols.Filter]: FilterCodecs.SUBSCRIBE,
|
||||||
[Protocols.LightPush]: LightPushCodec,
|
[Protocols.LightPush]: LightPushCodec,
|
||||||
[Protocols.Store]: StoreCodec,
|
[Protocols.Store]: StoreCodec,
|
||||||
[Protocols.Relay]: ""
|
"light-push-v2": LightPushCodecV2
|
||||||
};
|
};
|
||||||
|
|
||||||
return protocolToCodec[protocol];
|
return protocolToCodecs[protocol];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
import type { Connection, Peer, PeerStore } from "@libp2p/interface";
|
import type { Connection, Peer, PeerStore } from "@libp2p/interface";
|
||||||
import { FilterCodecs, LightPushCodec, StoreCodec } from "@waku/core";
|
import {
|
||||||
|
FilterCodecs,
|
||||||
|
LightPushCodec,
|
||||||
|
LightPushCodecV2,
|
||||||
|
StoreCodec
|
||||||
|
} from "@waku/core";
|
||||||
import { IRelay, Protocols } from "@waku/interfaces";
|
import { IRelay, Protocols } from "@waku/interfaces";
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
import sinon from "sinon";
|
import sinon from "sinon";
|
||||||
@ -114,7 +119,10 @@ describe("waitForRemotePeer", () => {
|
|||||||
err = e as Error;
|
err = e as Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(addEventListenerSpy.calledOnceWith("peer:identify")).to.be.true;
|
expect(addEventListenerSpy.calledTwice).to.be.true;
|
||||||
|
addEventListenerSpy
|
||||||
|
.getCalls()
|
||||||
|
.forEach((c) => expect(c.firstArg).to.equal("peer:identify"));
|
||||||
|
|
||||||
expect(err).not.to.be.undefined;
|
expect(err).not.to.be.undefined;
|
||||||
expect(err!.message).to.be.eq("Timed out waiting for a remote peer.");
|
expect(err!.message).to.be.eq("Timed out waiting for a remote peer.");
|
||||||
@ -148,9 +156,12 @@ describe("waitForRemotePeer", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should wait for LightPush peer to be connected", async () => {
|
it("should wait for LightPush peer to be connected", async () => {
|
||||||
|
let call = 0;
|
||||||
const addEventListenerSpy = sinon.spy(
|
const addEventListenerSpy = sinon.spy(
|
||||||
(_type: string, _cb: (e: any) => void) => {
|
(_type: string, _cb: (e: any) => void) => {
|
||||||
_cb({ detail: { protocols: [LightPushCodec] } });
|
const proto = call === 0 ? LightPushCodec : LightPushCodecV2;
|
||||||
|
call++;
|
||||||
|
_cb({ detail: { protocols: [proto] } });
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
eventTarget.addEventListener = addEventListenerSpy;
|
eventTarget.addEventListener = addEventListenerSpy;
|
||||||
@ -174,7 +185,10 @@ describe("waitForRemotePeer", () => {
|
|||||||
err = e as Error;
|
err = e as Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(addEventListenerSpy.calledOnceWith("peer:identify")).to.be.true;
|
expect(addEventListenerSpy.calledTwice).to.be.true;
|
||||||
|
addEventListenerSpy
|
||||||
|
.getCalls()
|
||||||
|
.forEach((c) => expect(c.firstArg).to.equal("peer:identify"));
|
||||||
expect(err).to.be.undefined;
|
expect(err).to.be.undefined;
|
||||||
|
|
||||||
// check with metadata serivice
|
// check with metadata serivice
|
||||||
@ -196,8 +210,10 @@ describe("waitForRemotePeer", () => {
|
|||||||
err = e as Error;
|
err = e as Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(addEventListenerSpy.calledTwice).to.be.true;
|
expect(addEventListenerSpy.callCount).to.equal(4);
|
||||||
expect(addEventListenerSpy.lastCall.calledWith("peer:identify")).to.be.true;
|
addEventListenerSpy
|
||||||
|
.getCalls()
|
||||||
|
.forEach((c) => expect(c.firstArg).to.equal("peer:identify"));
|
||||||
expect(err).to.be.undefined;
|
expect(err).to.be.undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
import type { IdentifyResult } from "@libp2p/interface";
|
import type { IdentifyResult } from "@libp2p/interface";
|
||||||
import { FilterCodecs, LightPushCodec, StoreCodec } from "@waku/core";
|
import {
|
||||||
|
FilterCodecs,
|
||||||
|
LightPushCodec,
|
||||||
|
LightPushCodecV2,
|
||||||
|
StoreCodec
|
||||||
|
} from "@waku/core";
|
||||||
import type { IWaku, Libp2p } from "@waku/interfaces";
|
import type { IWaku, Libp2p } from "@waku/interfaces";
|
||||||
import { Protocols } from "@waku/interfaces";
|
import { Protocols } from "@waku/interfaces";
|
||||||
import { Logger } from "@waku/utils";
|
import { Logger } from "@waku/utils";
|
||||||
@ -82,6 +87,13 @@ export async function waitForRemotePeer(
|
|||||||
|
|
||||||
type EventListener = (_: CustomEvent<IdentifyResult>) => void;
|
type EventListener = (_: CustomEvent<IdentifyResult>) => void;
|
||||||
|
|
||||||
|
function protocolToPeerPromise(
|
||||||
|
codecs: string[],
|
||||||
|
libp2p: Libp2p
|
||||||
|
): Promise<void>[] {
|
||||||
|
return codecs.map((codec) => waitForConnectedPeer(codec, libp2p));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waits for required peers to be connected.
|
* Waits for required peers to be connected.
|
||||||
*/
|
*/
|
||||||
@ -96,15 +108,21 @@ async function waitForProtocols(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (waku.store && protocols.includes(Protocols.Store)) {
|
if (waku.store && protocols.includes(Protocols.Store)) {
|
||||||
promises.push(waitForConnectedPeer(StoreCodec, waku.libp2p));
|
promises.push(...protocolToPeerPromise([StoreCodec], waku.libp2p));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (waku.lightPush && protocols.includes(Protocols.LightPush)) {
|
if (waku.lightPush && protocols.includes(Protocols.LightPush)) {
|
||||||
promises.push(waitForConnectedPeer(LightPushCodec, waku.libp2p));
|
const lpPromises = protocolToPeerPromise(
|
||||||
|
[LightPushCodec, LightPushCodecV2],
|
||||||
|
waku.libp2p
|
||||||
|
);
|
||||||
|
promises.push(Promise.any(lpPromises));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (waku.filter && protocols.includes(Protocols.Filter)) {
|
if (waku.filter && protocols.includes(Protocols.Filter)) {
|
||||||
promises.push(waitForConnectedPeer(FilterCodecs.SUBSCRIBE, waku.libp2p));
|
promises.push(
|
||||||
|
...protocolToPeerPromise([FilterCodecs.SUBSCRIBE], waku.libp2p)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
@ -246,15 +264,17 @@ function getEnabledProtocols(waku: IWaku): Protocols[] {
|
|||||||
function mapProtocolsToCodecs(protocols: Protocols[]): Map<string, boolean> {
|
function mapProtocolsToCodecs(protocols: Protocols[]): Map<string, boolean> {
|
||||||
const codecs: Map<string, boolean> = new Map();
|
const codecs: Map<string, boolean> = new Map();
|
||||||
|
|
||||||
const protocolToCodec: Record<string, string> = {
|
const protocolToCodec: Record<string, string[]> = {
|
||||||
[Protocols.Filter]: FilterCodecs.SUBSCRIBE,
|
[Protocols.Filter]: [FilterCodecs.SUBSCRIBE],
|
||||||
[Protocols.LightPush]: LightPushCodec,
|
[Protocols.LightPush]: [LightPushCodec, LightPushCodecV2],
|
||||||
[Protocols.Store]: StoreCodec
|
[Protocols.Store]: [StoreCodec]
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const protocol of protocols) {
|
for (const protocol of protocols) {
|
||||||
if (protocolToCodec[protocol]) {
|
if (protocolToCodec[protocol]) {
|
||||||
codecs.set(protocolToCodec[protocol], false);
|
protocolToCodec[protocol].forEach((codec) => {
|
||||||
|
codecs.set(codec, false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -182,7 +182,7 @@ export class WakuNode implements IWaku {
|
|||||||
}
|
}
|
||||||
if (_protocols.includes(Protocols.LightPush)) {
|
if (_protocols.includes(Protocols.LightPush)) {
|
||||||
if (this.lightPush) {
|
if (this.lightPush) {
|
||||||
codecs.push(this.lightPush.multicodec);
|
codecs.push(...this.lightPush.multicodec);
|
||||||
} else {
|
} else {
|
||||||
log.error(
|
log.error(
|
||||||
"Light Push codec not included in dial codec: protocol not mounted locally"
|
"Light Push codec not included in dial codec: protocol not mounted locally"
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { createEncoder } from "@waku/core";
|
import { createEncoder } from "@waku/core";
|
||||||
import { IRateLimitProof, LightNode, ProtocolError } from "@waku/interfaces";
|
import { IRateLimitProof, LightNode, LightPushError } from "@waku/interfaces";
|
||||||
import { utf8ToBytes } from "@waku/sdk";
|
import { utf8ToBytes } from "@waku/sdk";
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
|
||||||
@ -21,9 +21,9 @@ import {
|
|||||||
TestRoutingInfo
|
TestRoutingInfo
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
|
|
||||||
const runTests = (strictNodeCheck: boolean): void => {
|
const runTests = (strictNodeCheck: boolean, useLegacy: boolean): void => {
|
||||||
const numServiceNodes = 2;
|
const numServiceNodes = 2;
|
||||||
describe(`Waku Light Push: Multiple Nodes: Strict Check: ${strictNodeCheck}`, function () {
|
describe(`Waku Light Push (legacy=${useLegacy ? "v2" : "v3"}): Multiple Nodes: Strict Check: ${strictNodeCheck}`, function () {
|
||||||
// Set the timeout for all tests in this suite. Can be overwritten at test level
|
// Set the timeout for all tests in this suite. Can be overwritten at test level
|
||||||
this.timeout(15000);
|
this.timeout(15000);
|
||||||
let waku: LightNode;
|
let waku: LightNode;
|
||||||
@ -36,7 +36,8 @@ const runTests = (strictNodeCheck: boolean): void => {
|
|||||||
{ lightpush: true, filter: true },
|
{ lightpush: true, filter: true },
|
||||||
strictNodeCheck,
|
strictNodeCheck,
|
||||||
numServiceNodes,
|
numServiceNodes,
|
||||||
true
|
true,
|
||||||
|
{ lightPush: { useLegacy } }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -95,7 +96,7 @@ const runTests = (strictNodeCheck: boolean): void => {
|
|||||||
expect(pushResponse.successes.length).to.eq(0);
|
expect(pushResponse.successes.length).to.eq(0);
|
||||||
|
|
||||||
expect(pushResponse.failures?.map((failure) => failure.error)).to.include(
|
expect(pushResponse.failures?.map((failure) => failure.error)).to.include(
|
||||||
ProtocolError.EMPTY_PAYLOAD
|
LightPushError.EMPTY_PAYLOAD
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq(
|
expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq(
|
||||||
@ -174,7 +175,7 @@ const runTests = (strictNodeCheck: boolean): void => {
|
|||||||
|
|
||||||
expect(pushResponse.successes.length).to.eq(0);
|
expect(pushResponse.successes.length).to.eq(0);
|
||||||
expect(pushResponse.failures?.map((failure) => failure.error)).to.include(
|
expect(pushResponse.failures?.map((failure) => failure.error)).to.include(
|
||||||
ProtocolError.REMOTE_PEER_REJECTED
|
LightPushError.REMOTE_PEER_REJECTED
|
||||||
);
|
);
|
||||||
expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq(
|
expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq(
|
||||||
false
|
false
|
||||||
@ -248,7 +249,7 @@ const runTests = (strictNodeCheck: boolean): void => {
|
|||||||
});
|
});
|
||||||
expect(pushResponse.successes.length).to.eq(0);
|
expect(pushResponse.successes.length).to.eq(0);
|
||||||
expect(pushResponse.failures?.map((failure) => failure.error)).to.include(
|
expect(pushResponse.failures?.map((failure) => failure.error)).to.include(
|
||||||
ProtocolError.SIZE_TOO_BIG
|
LightPushError.SIZE_TOO_BIG
|
||||||
);
|
);
|
||||||
expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq(
|
expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq(
|
||||||
false
|
false
|
||||||
@ -257,4 +258,6 @@ const runTests = (strictNodeCheck: boolean): void => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
[true, false].map(runTests);
|
[true, false].forEach((strictNodeCheck) => {
|
||||||
|
[true, false].forEach((legacy) => runTests(strictNodeCheck, legacy));
|
||||||
|
});
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { createEncoder } from "@waku/core";
|
import { createEncoder } from "@waku/core";
|
||||||
import { LightNode, Protocols } from "@waku/interfaces";
|
import { IWaku, Protocols } from "@waku/interfaces";
|
||||||
import { createRoutingInfo } from "@waku/utils";
|
import { createRoutingInfo } from "@waku/utils";
|
||||||
import { utf8ToBytes } from "@waku/utils/bytes";
|
import { utf8ToBytes } from "@waku/utils/bytes";
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
@ -28,7 +28,7 @@ describe("Waku Light Push (Autosharding): Multiple Shards", function () {
|
|||||||
this.timeout(30000);
|
this.timeout(30000);
|
||||||
const numServiceNodes = 2;
|
const numServiceNodes = 2;
|
||||||
|
|
||||||
let waku: LightNode;
|
let waku: IWaku;
|
||||||
let serviceNodes: ServiceNodesFleet;
|
let serviceNodes: ServiceNodesFleet;
|
||||||
|
|
||||||
const customContentTopic2 = "/test/2/waku-light-push/utf8";
|
const customContentTopic2 = "/test/2/waku-light-push/utf8";
|
||||||
@ -48,6 +48,7 @@ describe("Waku Light Push (Autosharding): Multiple Shards", function () {
|
|||||||
{
|
{
|
||||||
lightpush: true,
|
lightpush: true,
|
||||||
filter: true,
|
filter: true,
|
||||||
|
relay: true,
|
||||||
contentTopic: [TestEncoder.contentTopic, customEncoder2.contentTopic]
|
contentTopic: [TestEncoder.contentTopic, customEncoder2.contentTopic]
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
@ -60,19 +61,29 @@ describe("Waku Light Push (Autosharding): Multiple Shards", function () {
|
|||||||
await teardownNodesWithRedundancy(serviceNodes, waku);
|
await teardownNodesWithRedundancy(serviceNodes, waku);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Subscribe and receive messages on 2 different pubsubtopics", async function () {
|
[true, false].forEach((useLegacy) => {
|
||||||
|
it(`Subscribe and receive messages on 2 different pubsubtopics with ${useLegacy ? "v2" : "v3"} protocol`, async function () {
|
||||||
if (customRoutingInfo2.pubsubTopic === TestEncoder.pubsubTopic)
|
if (customRoutingInfo2.pubsubTopic === TestEncoder.pubsubTopic)
|
||||||
throw "Invalid test, both encoder uses same shard";
|
throw "Invalid test, both encoder uses same shard";
|
||||||
|
|
||||||
const pushResponse1 = await waku.lightPush.send(TestEncoder, {
|
const pushResponse1 = await waku.lightPush!.send(
|
||||||
|
TestEncoder,
|
||||||
|
{
|
||||||
payload: utf8ToBytes("M1")
|
payload: utf8ToBytes("M1")
|
||||||
});
|
},
|
||||||
const pushResponse2 = await waku.lightPush.send(customEncoder2, {
|
{ useLegacy }
|
||||||
payload: utf8ToBytes("M2")
|
);
|
||||||
});
|
|
||||||
|
|
||||||
expect(pushResponse1.successes.length).to.eq(numServiceNodes);
|
const pushResponse2 = await waku.lightPush!.send(
|
||||||
expect(pushResponse2.successes.length).to.eq(numServiceNodes);
|
customEncoder2,
|
||||||
|
{
|
||||||
|
payload: utf8ToBytes("M2")
|
||||||
|
},
|
||||||
|
{ useLegacy }
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(pushResponse1?.successes.length).to.eq(numServiceNodes);
|
||||||
|
expect(pushResponse2?.successes.length).to.eq(numServiceNodes);
|
||||||
|
|
||||||
const messageCollector1 = new MessageCollector(serviceNodes.nodes[0]);
|
const messageCollector1 = new MessageCollector(serviceNodes.nodes[0]);
|
||||||
const messageCollector2 = new MessageCollector(serviceNodes.nodes[1]);
|
const messageCollector2 = new MessageCollector(serviceNodes.nodes[1]);
|
||||||
@ -101,6 +112,7 @@ describe("Waku Light Push (Autosharding): Multiple Shards", function () {
|
|||||||
expectedPubsubTopic: customEncoder2.pubsubTopic
|
expectedPubsubTopic: customEncoder2.pubsubTopic
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("Light push messages to 2 nwaku nodes each with different pubsubtopics", async function () {
|
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
|
// Set up and start a new nwaku node with Default PubsubTopic
|
||||||
@ -122,10 +134,10 @@ describe("Waku Light Push (Autosharding): Multiple Shards", function () {
|
|||||||
|
|
||||||
const messageCollector2 = new MessageCollector(nwaku2);
|
const messageCollector2 = new MessageCollector(nwaku2);
|
||||||
|
|
||||||
await waku.lightPush.send(TestEncoder, {
|
await waku.lightPush!.send(TestEncoder, {
|
||||||
payload: utf8ToBytes("M1")
|
payload: utf8ToBytes("M1")
|
||||||
});
|
});
|
||||||
await waku.lightPush.send(customEncoder2, {
|
await waku.lightPush!.send(customEncoder2, {
|
||||||
payload: utf8ToBytes("M2")
|
payload: utf8ToBytes("M2")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
83
packages/tests/tests/light-push/v2_interop.spec.ts
Normal file
83
packages/tests/tests/light-push/v2_interop.spec.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { LightNode } from "@waku/interfaces";
|
||||||
|
import { createLightNode, utf8ToBytes } from "@waku/sdk";
|
||||||
|
import { expect } from "chai";
|
||||||
|
|
||||||
|
import {
|
||||||
|
afterEachCustom,
|
||||||
|
beforeEachCustom,
|
||||||
|
NOISE_KEY_2,
|
||||||
|
runMultipleNodes,
|
||||||
|
ServiceNodesFleet,
|
||||||
|
teardownNodesWithRedundancy
|
||||||
|
} from "../../src/index.js";
|
||||||
|
import { DEFAULT_DISCOVERIES_ENABLED } from "../../src/lib/runNodes.js";
|
||||||
|
|
||||||
|
import { TestContentTopic, TestEncoder, TestRoutingInfo } from "./utils.js";
|
||||||
|
|
||||||
|
describe(`Waku Light Push V2 and V3 interop`, function () {
|
||||||
|
this.timeout(15000);
|
||||||
|
let waku: LightNode;
|
||||||
|
let waku2: LightNode;
|
||||||
|
let serviceNodes: ServiceNodesFleet;
|
||||||
|
|
||||||
|
beforeEachCustom(this, async () => {
|
||||||
|
[serviceNodes, waku] = await runMultipleNodes(
|
||||||
|
this.ctx,
|
||||||
|
TestRoutingInfo,
|
||||||
|
{ lightpush: true, filter: true, relay: true },
|
||||||
|
true,
|
||||||
|
2,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
waku2 = await createLightNode({
|
||||||
|
staticNoiseKey: NOISE_KEY_2,
|
||||||
|
libp2p: {
|
||||||
|
addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] }
|
||||||
|
},
|
||||||
|
networkConfig: TestRoutingInfo.networkConfig,
|
||||||
|
lightPush: { numPeersToUse: 1 },
|
||||||
|
discovery: DEFAULT_DISCOVERIES_ENABLED
|
||||||
|
});
|
||||||
|
|
||||||
|
await waku2.dial(await serviceNodes.nodes[1].getMultiaddrWithId());
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEachCustom(this, async () => {
|
||||||
|
await teardownNodesWithRedundancy(serviceNodes, [waku, waku2]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`Push messages througth V2 and V3 from 2 js-waku and receives`, async function () {
|
||||||
|
let pushResponse = await waku.lightPush.send(
|
||||||
|
TestEncoder,
|
||||||
|
{
|
||||||
|
payload: utf8ToBytes("v2")
|
||||||
|
},
|
||||||
|
{ useLegacy: true }
|
||||||
|
);
|
||||||
|
expect(pushResponse.successes.length).to.eq(2);
|
||||||
|
|
||||||
|
expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq(true);
|
||||||
|
serviceNodes.messageCollector.verifyReceivedMessage(0, {
|
||||||
|
expectedMessageText: "v2",
|
||||||
|
expectedContentTopic: TestContentTopic,
|
||||||
|
expectedPubsubTopic: TestRoutingInfo.pubsubTopic
|
||||||
|
});
|
||||||
|
|
||||||
|
pushResponse = await waku2.lightPush.send(
|
||||||
|
TestEncoder,
|
||||||
|
{
|
||||||
|
payload: utf8ToBytes("v3")
|
||||||
|
},
|
||||||
|
{ useLegacy: false }
|
||||||
|
);
|
||||||
|
expect(pushResponse.successes.length).to.eq(1);
|
||||||
|
|
||||||
|
expect(await serviceNodes.messageCollector.waitForMessages(1)).to.eq(true);
|
||||||
|
serviceNodes.messageCollector.verifyReceivedMessage(0, {
|
||||||
|
expectedMessageText: "v3",
|
||||||
|
expectedContentTopic: TestContentTopic,
|
||||||
|
expectedPubsubTopic: TestRoutingInfo.pubsubTopic
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user