diff --git a/packages/interfaces/src/light_push.ts b/packages/interfaces/src/light_push.ts index 32b29048e6..b9350c6b6b 100644 --- a/packages/interfaces/src/light_push.ts +++ b/packages/interfaces/src/light_push.ts @@ -1,5 +1,4 @@ -import { IBaseProtocolCore, IBaseProtocolSDK } from "./protocols.js"; +import { IBaseProtocolCore } from "./protocols.js"; import type { ISender } from "./sender.js"; -export type ILightPush = ISender & - IBaseProtocolSDK & { protocol: IBaseProtocolCore }; +export type ILightPush = ISender & { protocol: IBaseProtocolCore }; diff --git a/packages/sdk/src/protocols/base_protocol.ts b/packages/sdk/src/protocols/base_protocol.ts index 062583404b..4550072b84 100644 --- a/packages/sdk/src/protocols/base_protocol.ts +++ b/packages/sdk/src/protocols/base_protocol.ts @@ -11,7 +11,7 @@ interface Options { maintainPeersInterval?: number; } -const DEFAULT_NUM_PEERS_TO_USE = 2; +export const DEFAULT_NUM_PEERS_TO_USE = 2; const DEFAULT_MAINTAIN_PEERS_INTERVAL = 30_000; export class BaseProtocolSDK implements IBaseProtocolSDK { diff --git a/packages/sdk/src/protocols/light_push/light_push.spec.ts b/packages/sdk/src/protocols/light_push/light_push.spec.ts index 9f529f181e..afc2da78e2 100644 --- a/packages/sdk/src/protocols/light_push/light_push.spec.ts +++ b/packages/sdk/src/protocols/light_push/light_push.spec.ts @@ -1,10 +1,170 @@ -// TODO: add them after decoupling `BaseProtocolSDK` from LightPush +import { Peer } from "@libp2p/interface"; +import { + ConnectionManager, + createEncoder, + Encoder, + LightPushCodec +} from "@waku/core"; +import { Libp2p, ProtocolError } from "@waku/interfaces"; +import { utf8ToBytes } from "@waku/utils/bytes"; +import { expect } from "chai"; +import sinon from "sinon"; + +import { LightPush } from "./light_push.js"; + +const PUBSUB_TOPIC = "/waku/2/rs/1/4"; +const CONTENT_TOPIC = "/test/1/waku-light-push/utf8"; + describe("LightPush SDK", () => { - it("should fail to send if pubsub topics are misconfigured"); + let libp2p: Libp2p; + let encoder: Encoder; + let lightPush: LightPush; + + beforeEach(() => { + libp2p = mockLibp2p(); + encoder = createEncoder({ contentTopic: CONTENT_TOPIC }); + lightPush = mockLightPush({ libp2p }); + }); + + it("should fail to send if pubsub topics are misconfigured", async () => { + lightPush = mockLightPush({ libp2p, pubsubTopics: ["/wrong"] }); + + const result = await lightPush.send(encoder, { + payload: utf8ToBytes("test") + }); + const failures = result.failures ?? []; + + expect(failures.length).to.be.eq(1); + expect(failures.some((v) => v.error === ProtocolError.TOPIC_NOT_CONFIGURED)) + .to.be.true; + }); + + it("should fail to send if no connected peers found", async () => { + const result = await lightPush.send(encoder, { + payload: utf8ToBytes("test") + }); + const failures = result.failures ?? []; + + expect(failures.length).to.be.eq(1); + expect(failures.some((v) => v.error === ProtocolError.NO_PEER_AVAILABLE)).to + .be.true; + }); + + it("should send to specified number of peers of used peers", async () => { + libp2p = mockLibp2p({ + peers: [mockPeer("1"), mockPeer("2"), mockPeer("3"), mockPeer("4")] + }); + + // check default value that should be 2 + lightPush = mockLightPush({ libp2p }); + let sendSpy = sinon.spy( + (_encoder: any, _message: any, peer: Peer) => + ({ success: peer.id }) as any + ); + lightPush.protocol.send = sendSpy; + + let result = await lightPush.send(encoder, { + payload: utf8ToBytes("test") + }); + + expect(sendSpy.calledTwice).to.be.true; + expect(result.successes?.length).to.be.eq(2); + + // check if setting another value works + lightPush = mockLightPush({ libp2p, numPeersToUse: 3 }); + sendSpy = sinon.spy( + (_encoder: any, _message: any, peer: Peer) => + ({ success: peer.id }) as any + ); + lightPush.protocol.send = sendSpy; - it("should fail to send if no connected peers found"); + result = await lightPush.send(encoder, { payload: utf8ToBytes("test") }); - it("should send to number of used peers"); + expect(sendSpy.calledThrice).to.be.true; + expect(result.successes?.length).to.be.eq(3); + }); - it("should retry on failure if specified"); + it("should retry on failure if specified", async () => { + libp2p = mockLibp2p({ + peers: [mockPeer("1"), mockPeer("2")] + }); + + lightPush = mockLightPush({ libp2p }); + let sendSpy = sinon.spy((_encoder: any, _message: any, peer: Peer) => { + if (peer.id.toString() === "1") { + return { success: peer.id }; + } + + return { failure: { error: "problem" } }; + }); + lightPush.protocol.send = sendSpy as any; + const attemptRetriesSpy = sinon.spy(lightPush["attemptRetries"]); + lightPush["attemptRetries"] = attemptRetriesSpy; + + const result = await lightPush.send( + encoder, + { payload: utf8ToBytes("test") }, + { autoRetry: true } + ); + + expect(attemptRetriesSpy.calledOnce).to.be.true; + expect(result.successes?.length).to.be.eq(1); + expect(result.failures?.length).to.be.eq(1); + + sendSpy = sinon.spy(() => ({ failure: { error: "problem" } })) as any; + await lightPush["attemptRetries"](sendSpy as any); + + expect(sendSpy.callCount).to.be.eq(3); + + sendSpy = sinon.spy(() => ({ failure: { error: "problem" } })) as any; + await lightPush["attemptRetries"](sendSpy as any, 2); + + expect(sendSpy.callCount).to.be.eq(2); + }); }); + +type MockLibp2pOptions = { + peers?: Peer[]; +}; + +function mockLibp2p(options?: MockLibp2pOptions): Libp2p { + const peers = options?.peers || []; + const peerStore = { + get: (id: any) => Promise.resolve(peers.find((p) => p.id === id)) + }; + + return { + peerStore, + getPeers: () => peers.map((p) => p.id), + components: { + events: new EventTarget(), + connectionManager: { + getConnections: () => [] + } as any, + peerStore + } + } as unknown as Libp2p; +} + +type MockLightPushOptions = { + libp2p: Libp2p; + pubsubTopics?: string[]; + numPeersToUse?: number; +}; + +function mockLightPush(options: MockLightPushOptions): LightPush { + return new LightPush( + { + configuredPubsubTopics: options.pubsubTopics || [PUBSUB_TOPIC] + } as ConnectionManager, + options.libp2p, + { numPeersToUse: options.numPeersToUse } + ); +} + +function mockPeer(id: string): Peer { + return { + id, + protocols: [LightPushCodec] + } as unknown as Peer; +} diff --git a/packages/sdk/src/protocols/light_push/light_push.ts b/packages/sdk/src/protocols/light_push/light_push.ts index 8da6ea31de..9764111f18 100644 --- a/packages/sdk/src/protocols/light_push/light_push.ts +++ b/packages/sdk/src/protocols/light_push/light_push.ts @@ -19,7 +19,7 @@ import { } from "@waku/interfaces"; import { ensurePubsubTopicIsConfigured, Logger } from "@waku/utils"; -import { BaseProtocolSDK } from "../base_protocol.js"; +import { DEFAULT_NUM_PEERS_TO_USE } from "../base_protocol.js"; const log = new Logger("sdk:light-push"); @@ -31,7 +31,8 @@ const DEFAULT_SEND_OPTIONS: ISenderOptions = { type RetryCallback = (peer: Peer) => Promise; -export class LightPush extends BaseProtocolSDK implements ILightPush { +export class LightPush implements ILightPush { + private numPeersToUse: number = DEFAULT_NUM_PEERS_TO_USE; public readonly protocol: LightPushCore; public constructor( @@ -39,15 +40,11 @@ export class LightPush extends BaseProtocolSDK implements ILightPush { private libp2p: Libp2p, options?: ProtocolCreateOptions ) { - super( - new LightPushCore(connectionManager.configuredPubsubTopics, libp2p), - connectionManager, - { - numPeersToUse: options?.numPeersToUse - } + this.numPeersToUse = options?.numPeersToUse ?? DEFAULT_NUM_PEERS_TO_USE; + this.protocol = new LightPushCore( + connectionManager.configuredPubsubTopics, + libp2p ); - - this.protocol = this.core as LightPushCore; } public async send(