mirror of https://github.com/waku-org/js-waku.git
feat: track node connection state (#1719)
Co-authored-by: chair <29414216+chair28980@users.noreply.github.com> Co-authored-by: Sasha <118575614+weboko@users.noreply.github.com>
This commit is contained in:
parent
affdc265b8
commit
1d0e2ace7f
|
@ -6,8 +6,10 @@ import { CustomEvent, EventEmitter } from "@libp2p/interfaces/events";
|
||||||
import { decodeRelayShard } from "@waku/enr";
|
import { decodeRelayShard } from "@waku/enr";
|
||||||
import {
|
import {
|
||||||
ConnectionManagerOptions,
|
ConnectionManagerOptions,
|
||||||
|
EConnectionStateEvents,
|
||||||
EPeersByDiscoveryEvents,
|
EPeersByDiscoveryEvents,
|
||||||
IConnectionManager,
|
IConnectionManager,
|
||||||
|
IConnectionStateEvents,
|
||||||
IPeersByDiscoveryEvents,
|
IPeersByDiscoveryEvents,
|
||||||
IRelay,
|
IRelay,
|
||||||
KeepAliveOptions,
|
KeepAliveOptions,
|
||||||
|
@ -28,7 +30,7 @@ export const DEFAULT_MAX_DIAL_ATTEMPTS_FOR_PEER = 3;
|
||||||
export const DEFAULT_MAX_PARALLEL_DIALS = 3;
|
export const DEFAULT_MAX_PARALLEL_DIALS = 3;
|
||||||
|
|
||||||
export class ConnectionManager
|
export class ConnectionManager
|
||||||
extends EventEmitter<IPeersByDiscoveryEvents>
|
extends EventEmitter<IPeersByDiscoveryEvents & IConnectionStateEvents>
|
||||||
implements IConnectionManager
|
implements IConnectionManager
|
||||||
{
|
{
|
||||||
private static instances = new Map<string, ConnectionManager>();
|
private static instances = new Map<string, ConnectionManager>();
|
||||||
|
@ -40,6 +42,33 @@ export class ConnectionManager
|
||||||
|
|
||||||
private currentActiveParallelDialCount = 0;
|
private currentActiveParallelDialCount = 0;
|
||||||
private pendingPeerDialQueue: Array<PeerId> = [];
|
private pendingPeerDialQueue: Array<PeerId> = [];
|
||||||
|
private online: boolean = false;
|
||||||
|
|
||||||
|
public isConnected(): boolean {
|
||||||
|
return this.online;
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleOnline(): void {
|
||||||
|
if (!this.online) {
|
||||||
|
this.online = true;
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent<boolean>(EConnectionStateEvents.CONNECTION_STATUS, {
|
||||||
|
detail: this.online
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleOffline(): void {
|
||||||
|
if (this.online && this.libp2p.getConnections().length == 0) {
|
||||||
|
this.online = false;
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent<boolean>(EConnectionStateEvents.CONNECTION_STATUS, {
|
||||||
|
detail: this.online
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static create(
|
public static create(
|
||||||
peerId: string,
|
peerId: string,
|
||||||
|
@ -393,12 +422,14 @@ export class ConnectionManager
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
this.toggleOnline();
|
||||||
})();
|
})();
|
||||||
},
|
},
|
||||||
"peer:disconnect": () => {
|
"peer:disconnect": (evt: CustomEvent<PeerId>): void => {
|
||||||
return (evt: CustomEvent<PeerId>): void => {
|
void (async () => {
|
||||||
this.keepAliveManager.stop(evt.detail);
|
this.keepAliveManager.stop(evt.detail);
|
||||||
};
|
this.toggleOffline();
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -111,6 +111,12 @@ export class KeepAliveManager {
|
||||||
this.relayKeepAliveTimers.clear();
|
this.relayKeepAliveTimers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public connectionsExist(): boolean {
|
||||||
|
return (
|
||||||
|
this.pingKeepAliveTimers.size > 0 || this.relayKeepAliveTimers.size > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private scheduleRelayPings(
|
private scheduleRelayPings(
|
||||||
relay: IRelay,
|
relay: IRelay,
|
||||||
relayPeriodSecs: number,
|
relayPeriodSecs: number,
|
||||||
|
|
|
@ -178,6 +178,10 @@ export class WakuNode implements Waku {
|
||||||
return this.libp2p.isStarted();
|
return this.libp2p.isStarted();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isConnected(): boolean {
|
||||||
|
return this.connectionManager.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the local multiaddr with peer id on which libp2p is listening.
|
* Return the local multiaddr with peer id on which libp2p is listening.
|
||||||
*
|
*
|
||||||
|
|
|
@ -49,8 +49,17 @@ export interface PeersByDiscoveryResult {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum EConnectionStateEvents {
|
||||||
|
CONNECTION_STATUS = "waku:connection"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IConnectionStateEvents {
|
||||||
|
// true when online, false when offline
|
||||||
|
[EConnectionStateEvents.CONNECTION_STATUS]: CustomEvent<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IConnectionManager
|
export interface IConnectionManager
|
||||||
extends EventEmitter<IPeersByDiscoveryEvents> {
|
extends EventEmitter<IPeersByDiscoveryEvents & IConnectionStateEvents> {
|
||||||
getPeersByDiscovery(): Promise<PeersByDiscoveryResult>;
|
getPeersByDiscovery(): Promise<PeersByDiscoveryResult>;
|
||||||
stop(): void;
|
stop(): void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,8 @@ export interface Waku {
|
||||||
stop(): Promise<void>;
|
stop(): Promise<void>;
|
||||||
|
|
||||||
isStarted(): boolean;
|
isStarted(): boolean;
|
||||||
|
|
||||||
|
isConnected(): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LightNode extends Waku {
|
export interface LightNode extends Waku {
|
||||||
|
|
|
@ -2,18 +2,26 @@ import type { PeerId } from "@libp2p/interface/peer-id";
|
||||||
import type { PeerInfo } from "@libp2p/interface/peer-info";
|
import type { PeerInfo } from "@libp2p/interface/peer-info";
|
||||||
import { CustomEvent } from "@libp2p/interfaces/events";
|
import { CustomEvent } from "@libp2p/interfaces/events";
|
||||||
import { createSecp256k1PeerId } from "@libp2p/peer-id-factory";
|
import { createSecp256k1PeerId } from "@libp2p/peer-id-factory";
|
||||||
import { EPeersByDiscoveryEvents, LightNode, Tags } from "@waku/interfaces";
|
import { Multiaddr } from "@multiformats/multiaddr";
|
||||||
|
import {
|
||||||
|
EConnectionStateEvents,
|
||||||
|
EPeersByDiscoveryEvents,
|
||||||
|
LightNode,
|
||||||
|
Protocols,
|
||||||
|
Tags
|
||||||
|
} from "@waku/interfaces";
|
||||||
import { createLightNode } from "@waku/sdk";
|
import { createLightNode } from "@waku/sdk";
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
import sinon, { SinonSpy, SinonStub } from "sinon";
|
import sinon, { SinonSpy, SinonStub } from "sinon";
|
||||||
|
|
||||||
import { delay } from "../dist/delay.js";
|
import { delay } from "../dist/delay.js";
|
||||||
import { tearDownNodes } from "../src/index.js";
|
import { makeLogFileName, NimGoNode, tearDownNodes } from "../src/index.js";
|
||||||
|
|
||||||
const TEST_TIMEOUT = 10_000;
|
const TEST_TIMEOUT = 10_000;
|
||||||
const DELAY_MS = 1_000;
|
const DELAY_MS = 1_000;
|
||||||
|
|
||||||
describe("ConnectionManager", function () {
|
describe("ConnectionManager", function () {
|
||||||
|
this.timeout(20_000);
|
||||||
let waku: LightNode;
|
let waku: LightNode;
|
||||||
|
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
|
@ -156,6 +164,105 @@ describe("ConnectionManager", function () {
|
||||||
expect(await peerConnectedPeerExchange).to.eq(true);
|
expect(await peerConnectedPeerExchange).to.eq(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("peer:disconnect", () => {
|
||||||
|
it("should emit `waku:offline` event when all peers disconnect", async function () {
|
||||||
|
const peerIdPx = await createSecp256k1PeerId();
|
||||||
|
const peerIdPx2 = await createSecp256k1PeerId();
|
||||||
|
|
||||||
|
await waku.libp2p.peerStore.save(peerIdPx, {
|
||||||
|
tags: {
|
||||||
|
[Tags.PEER_EXCHANGE]: {
|
||||||
|
value: 50,
|
||||||
|
ttl: 1200000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await waku.libp2p.peerStore.save(peerIdPx2, {
|
||||||
|
tags: {
|
||||||
|
[Tags.PEER_EXCHANGE]: {
|
||||||
|
value: 50,
|
||||||
|
ttl: 1200000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
waku.libp2p.dispatchEvent(
|
||||||
|
new CustomEvent<PeerId>("peer:connect", { detail: peerIdPx })
|
||||||
|
);
|
||||||
|
waku.libp2p.dispatchEvent(
|
||||||
|
new CustomEvent<PeerId>("peer:connect", { detail: peerIdPx2 })
|
||||||
|
);
|
||||||
|
|
||||||
|
await delay(100);
|
||||||
|
|
||||||
|
let eventCount = 0;
|
||||||
|
const connectionStatus = new Promise<boolean>((resolve) => {
|
||||||
|
waku.connectionManager.addEventListener(
|
||||||
|
EConnectionStateEvents.CONNECTION_STATUS,
|
||||||
|
({ detail: status }) => {
|
||||||
|
eventCount++;
|
||||||
|
resolve(status);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(waku.isConnected()).to.be.true;
|
||||||
|
|
||||||
|
waku.libp2p.dispatchEvent(
|
||||||
|
new CustomEvent<PeerId>("peer:disconnect", { detail: peerIdPx })
|
||||||
|
);
|
||||||
|
waku.libp2p.dispatchEvent(
|
||||||
|
new CustomEvent<PeerId>("peer:disconnect", { detail: peerIdPx2 })
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(await connectionStatus).to.eq(false);
|
||||||
|
expect(eventCount).to.be.eq(1);
|
||||||
|
});
|
||||||
|
it("isConnected should return false after all peers disconnect", async function () {
|
||||||
|
const peerIdPx = await createSecp256k1PeerId();
|
||||||
|
const peerIdPx2 = await createSecp256k1PeerId();
|
||||||
|
|
||||||
|
await waku.libp2p.peerStore.save(peerIdPx, {
|
||||||
|
tags: {
|
||||||
|
[Tags.PEER_EXCHANGE]: {
|
||||||
|
value: 50,
|
||||||
|
ttl: 1200000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await waku.libp2p.peerStore.save(peerIdPx2, {
|
||||||
|
tags: {
|
||||||
|
[Tags.PEER_EXCHANGE]: {
|
||||||
|
value: 50,
|
||||||
|
ttl: 1200000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
waku.libp2p.dispatchEvent(
|
||||||
|
new CustomEvent<PeerId>("peer:connect", { detail: peerIdPx })
|
||||||
|
);
|
||||||
|
waku.libp2p.dispatchEvent(
|
||||||
|
new CustomEvent<PeerId>("peer:connect", { detail: peerIdPx2 })
|
||||||
|
);
|
||||||
|
|
||||||
|
await delay(100);
|
||||||
|
|
||||||
|
expect(waku.isConnected()).to.be.true;
|
||||||
|
|
||||||
|
waku.libp2p.dispatchEvent(
|
||||||
|
new CustomEvent<PeerId>("peer:disconnect", { detail: peerIdPx })
|
||||||
|
);
|
||||||
|
waku.libp2p.dispatchEvent(
|
||||||
|
new CustomEvent<PeerId>("peer:disconnect", { detail: peerIdPx2 })
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(waku.isConnected()).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Dials", () => {
|
describe("Dials", () => {
|
||||||
|
@ -376,4 +483,111 @@ describe("ConnectionManager", function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Connection state", () => {
|
||||||
|
this.timeout(20_000);
|
||||||
|
let nwaku1: NimGoNode;
|
||||||
|
let nwaku2: NimGoNode;
|
||||||
|
let nwaku1PeerId: Multiaddr;
|
||||||
|
let nwaku2PeerId: Multiaddr;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
this.timeout(20_000);
|
||||||
|
nwaku1 = new NimGoNode(makeLogFileName(this.ctx) + "1");
|
||||||
|
nwaku2 = new NimGoNode(makeLogFileName(this.ctx) + "2");
|
||||||
|
await nwaku1.start({
|
||||||
|
filter: true
|
||||||
|
});
|
||||||
|
|
||||||
|
await nwaku2.start({
|
||||||
|
filter: true
|
||||||
|
});
|
||||||
|
|
||||||
|
nwaku1PeerId = await nwaku1.getMultiaddrWithId();
|
||||||
|
nwaku2PeerId = await nwaku2.getMultiaddrWithId();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
this.timeout(15000);
|
||||||
|
await tearDownNodes([nwaku1, nwaku2], []);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should emit `waku:online` event only when first peer is connected", async function () {
|
||||||
|
this.timeout(20_000);
|
||||||
|
|
||||||
|
let eventCount = 0;
|
||||||
|
const connectionStatus = new Promise<boolean>((resolve) => {
|
||||||
|
waku.connectionManager.addEventListener(
|
||||||
|
EConnectionStateEvents.CONNECTION_STATUS,
|
||||||
|
({ detail: status }) => {
|
||||||
|
eventCount++;
|
||||||
|
resolve(status);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// await waku.start();
|
||||||
|
await waku.dial(nwaku1PeerId, [Protocols.Filter]);
|
||||||
|
await waku.dial(nwaku2PeerId, [Protocols.Filter]);
|
||||||
|
|
||||||
|
await delay(250);
|
||||||
|
|
||||||
|
expect(await connectionStatus).to.eq(true);
|
||||||
|
expect(eventCount).to.be.eq(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("isConnected should return true after first peer connects", async function () {
|
||||||
|
this.timeout(20_000);
|
||||||
|
expect(waku.isConnected()).to.be.false;
|
||||||
|
|
||||||
|
// await waku.start();
|
||||||
|
await waku.dial(nwaku1PeerId, [Protocols.Filter]);
|
||||||
|
await waku.dial(nwaku2PeerId, [Protocols.Filter]);
|
||||||
|
|
||||||
|
await delay(250);
|
||||||
|
|
||||||
|
expect(waku.isConnected()).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should emit `waku:offline` event only when all peers disconnect", async function () {
|
||||||
|
this.timeout(20_000);
|
||||||
|
expect(waku.isConnected()).to.be.false;
|
||||||
|
|
||||||
|
await waku.dial(nwaku1PeerId, [Protocols.Filter]);
|
||||||
|
await waku.dial(nwaku2PeerId, [Protocols.Filter]);
|
||||||
|
|
||||||
|
await delay(250);
|
||||||
|
|
||||||
|
let eventCount = 0;
|
||||||
|
const connectionStatus = new Promise<boolean>((resolve) => {
|
||||||
|
waku.connectionManager.addEventListener(
|
||||||
|
EConnectionStateEvents.CONNECTION_STATUS,
|
||||||
|
({ detail: status }) => {
|
||||||
|
eventCount++;
|
||||||
|
resolve(status);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waku.libp2p.hangUp(nwaku1PeerId);
|
||||||
|
await waku.libp2p.hangUp(nwaku2PeerId);
|
||||||
|
expect(await connectionStatus).to.eq(false);
|
||||||
|
expect(eventCount).to.be.eq(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("isConnected should return false after all peers disconnect", async function () {
|
||||||
|
this.timeout(20_000);
|
||||||
|
expect(waku.isConnected()).to.be.false;
|
||||||
|
|
||||||
|
await waku.dial(nwaku1PeerId, [Protocols.Filter]);
|
||||||
|
await waku.dial(nwaku2PeerId, [Protocols.Filter]);
|
||||||
|
|
||||||
|
await delay(250);
|
||||||
|
expect(waku.isConnected()).to.be.true;
|
||||||
|
|
||||||
|
await waku.libp2p.hangUp(nwaku1PeerId);
|
||||||
|
await waku.libp2p.hangUp(nwaku2PeerId);
|
||||||
|
expect(waku.isConnected()).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue