mirror of
https://github.com/logos-messaging/js-waku.git
synced 2026-01-03 22:33:07 +00:00
* 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>
227 lines
6.3 KiB
TypeScript
227 lines
6.3 KiB
TypeScript
import type { PeerId } from "@libp2p/interface";
|
|
import {
|
|
type LightPushCoreResult,
|
|
LightPushError,
|
|
ProtocolError,
|
|
Protocols
|
|
} from "@waku/interfaces";
|
|
import { createRoutingInfo } from "@waku/utils";
|
|
import { expect } from "chai";
|
|
import sinon from "sinon";
|
|
|
|
import { PeerManager } from "../peer_manager/index.js";
|
|
|
|
import { RetryManager, ScheduledTask } from "./retry_manager.js";
|
|
|
|
const TestRoutingInfo = createRoutingInfo(
|
|
{ clusterId: 0 },
|
|
{ pubsubTopic: "/waku/2/rs/0/0" }
|
|
);
|
|
|
|
describe("RetryManager", () => {
|
|
let retryManager: RetryManager;
|
|
let peerManager: PeerManager;
|
|
let mockPeerId: PeerId;
|
|
let clock: sinon.SinonFakeTimers;
|
|
|
|
beforeEach(() => {
|
|
clock = sinon.useFakeTimers();
|
|
|
|
mockPeerId = { toString: () => "test-peer-id" } as PeerId;
|
|
peerManager = {
|
|
getPeers: () => [mockPeerId],
|
|
renewPeer: sinon.spy(),
|
|
start: sinon.spy(),
|
|
stop: sinon.spy()
|
|
} as unknown as PeerManager;
|
|
|
|
retryManager = new RetryManager({
|
|
peerManager,
|
|
retryIntervalMs: 100
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
clock.restore();
|
|
retryManager.stop();
|
|
sinon.restore();
|
|
});
|
|
|
|
it("should start and stop interval correctly", () => {
|
|
const setIntervalSpy = sinon.spy(global, "setInterval");
|
|
const clearIntervalSpy = sinon.spy(global, "clearInterval");
|
|
|
|
retryManager.start();
|
|
expect(setIntervalSpy.calledOnce).to.be.true;
|
|
|
|
retryManager.stop();
|
|
expect(clearIntervalSpy.calledOnce).to.be.true;
|
|
});
|
|
|
|
it("should process tasks in queue", async () => {
|
|
const successCallback = sinon.spy(
|
|
async (peerId: PeerId): Promise<LightPushCoreResult> => ({
|
|
success: peerId,
|
|
failure: null
|
|
})
|
|
);
|
|
|
|
retryManager.push(successCallback, 3, TestRoutingInfo);
|
|
retryManager.start();
|
|
|
|
await clock.tickAsync(200);
|
|
retryManager.stop();
|
|
|
|
expect(successCallback.calledOnce, "called").to.be.true;
|
|
expect(successCallback.calledWith(mockPeerId), "called with peer").to.be
|
|
.true;
|
|
});
|
|
|
|
it("should requeue task if no peer is available", async () => {
|
|
(peerManager as any).getPeers = () => [];
|
|
const callback = sinon.spy();
|
|
|
|
retryManager.push(callback, 2, TestRoutingInfo);
|
|
retryManager.start();
|
|
|
|
const queue = (retryManager as any)["queue"] as ScheduledTask[];
|
|
expect(queue.length).to.equal(1);
|
|
|
|
await clock.tickAsync(200);
|
|
retryManager.stop();
|
|
|
|
expect(callback.called).to.be.false;
|
|
expect(queue.length).to.equal(1);
|
|
expect(queue[0].maxAttempts).to.equal(1);
|
|
});
|
|
|
|
it("should not requeue if maxAttempts is exhausted and no peer is available", async () => {
|
|
(peerManager as any).getPeers = () => [];
|
|
const callback = sinon.spy();
|
|
|
|
retryManager.push(callback, 1, TestRoutingInfo);
|
|
retryManager.start();
|
|
const queue = (retryManager as any)["queue"] as ScheduledTask[];
|
|
expect(queue.length).to.equal(1);
|
|
|
|
await clock.tickAsync(500);
|
|
retryManager.stop();
|
|
|
|
expect(callback.called).to.be.false;
|
|
expect(queue.length).to.equal(0);
|
|
});
|
|
|
|
it("should retry failed tasks", async () => {
|
|
const failingCallback = sinon.spy(
|
|
async (): Promise<LightPushCoreResult> => ({
|
|
success: null,
|
|
failure: { error: LightPushError.GENERIC_FAIL }
|
|
})
|
|
);
|
|
|
|
const queue = (retryManager as any)["queue"] as ScheduledTask[];
|
|
|
|
const task = {
|
|
callback: failingCallback,
|
|
maxAttempts: 2,
|
|
routingInfo: TestRoutingInfo
|
|
};
|
|
await (retryManager as any)["taskExecutor"](task);
|
|
|
|
expect(failingCallback.calledOnce, "executed callback").to.be.true;
|
|
expect(
|
|
queue.some((t) => t.maxAttempts === 1),
|
|
"task attempt decreased"
|
|
).to.be.true;
|
|
});
|
|
|
|
it("should request peer renewal on specific errors", async () => {
|
|
const errorCallback = sinon.spy(async (): Promise<LightPushCoreResult> => {
|
|
throw new Error(ProtocolError.NO_PEER_AVAILABLE);
|
|
});
|
|
|
|
await (retryManager as RetryManager)["taskExecutor"]({
|
|
callback: errorCallback,
|
|
maxAttempts: 1,
|
|
routingInfo: TestRoutingInfo
|
|
});
|
|
|
|
expect((peerManager.renewPeer as sinon.SinonSpy).calledOnce).to.be.true;
|
|
expect(
|
|
(peerManager.renewPeer as sinon.SinonSpy).calledWith(mockPeerId, {
|
|
protocol: Protocols.LightPush,
|
|
pubsubTopic: TestRoutingInfo.pubsubTopic
|
|
})
|
|
).to.be.true;
|
|
});
|
|
|
|
it("should handle task timeouts", async () => {
|
|
const slowCallback = sinon.spy(async (): Promise<LightPushCoreResult> => {
|
|
await new Promise((resolve) => setTimeout(resolve, 15000));
|
|
return { success: mockPeerId, failure: null };
|
|
});
|
|
|
|
const task = {
|
|
callback: slowCallback,
|
|
maxAttempts: 1,
|
|
routingInfo: TestRoutingInfo
|
|
};
|
|
const executionPromise = (retryManager as any)["taskExecutor"](task);
|
|
|
|
await clock.tickAsync(11000);
|
|
await executionPromise;
|
|
|
|
expect(slowCallback.calledOnce).to.be.true;
|
|
});
|
|
|
|
it("should not execute task if max attempts is 0", async () => {
|
|
const failingCallback = sinon.spy(
|
|
async (): Promise<LightPushCoreResult> => {
|
|
throw new Error("test error" as any);
|
|
}
|
|
);
|
|
|
|
const task = {
|
|
callback: failingCallback,
|
|
maxAttempts: 0,
|
|
routingInfo: TestRoutingInfo
|
|
};
|
|
await (retryManager as any)["taskExecutor"](task);
|
|
|
|
expect(failingCallback.called).to.be.false;
|
|
});
|
|
|
|
it("should not retry if at least one success", async () => {
|
|
let called = 0;
|
|
(peerManager as any).getPeers = () => [mockPeerId];
|
|
const successCallback = sinon.stub().callsFake(() => {
|
|
called++;
|
|
if (called === 1) retryManager.stop();
|
|
return Promise.resolve({ success: mockPeerId, failure: null });
|
|
});
|
|
retryManager.push(successCallback, 2, TestRoutingInfo);
|
|
retryManager.start();
|
|
await clock.tickAsync(500);
|
|
expect(called).to.equal(1);
|
|
});
|
|
|
|
it("should retry if all attempts fail", async () => {
|
|
let called = 0;
|
|
(peerManager as any).getPeers = () => [mockPeerId];
|
|
const failCallback = sinon.stub().callsFake(() => {
|
|
called++;
|
|
return Promise.resolve({
|
|
success: null,
|
|
failure: { error: LightPushError.GENERIC_FAIL }
|
|
});
|
|
});
|
|
retryManager.push(failCallback, 2, TestRoutingInfo);
|
|
retryManager.start();
|
|
await clock.tickAsync(1000);
|
|
retryManager.stop();
|
|
expect(called).to.be.greaterThan(1);
|
|
const queue = (retryManager as any)["queue"] as ScheduledTask[];
|
|
expect(queue.length).to.equal(0);
|
|
});
|
|
});
|