mirror of
https://github.com/logos-messaging/js-waku.git
synced 2026-01-05 23:33:08 +00:00
* feat: introduce HealthManager * feat: make health accessible on Waku object * feat: update health from protocols * chore: add access modifiers to healthmanager * feat: use a HealthManager singleton * chore: add tests for Filter, LightPush and Store * feat: add overall node health * chore: update protocol health to consider Store protocol * chore: setup generic test utils instead of using filter utils * tests: add a health status matrix check from 0-3 * chore: increase timeout for failing tests in CI tests pass locally without an increased timeout, but fail in CI * chore: move name inference to HealthManager * tests: abstract away node creation and teardown utils * fix: import
242 lines
6.7 KiB
TypeScript
242 lines
6.7 KiB
TypeScript
import type { Stream } from "@libp2p/interface";
|
|
import { isPeerId, PeerId } from "@libp2p/interface";
|
|
import { multiaddr, Multiaddr, MultiaddrInput } from "@multiformats/multiaddr";
|
|
import { ConnectionManager, getHealthManager } from "@waku/core";
|
|
import type {
|
|
IFilterSDK,
|
|
IHealthManager,
|
|
ILightPushSDK,
|
|
IRelay,
|
|
IStoreSDK,
|
|
Libp2p,
|
|
ProtocolCreateOptions,
|
|
PubsubTopic,
|
|
Waku
|
|
} from "@waku/interfaces";
|
|
import { Protocols } from "@waku/interfaces";
|
|
import { wakuRelay } from "@waku/relay";
|
|
import { Logger } from "@waku/utils";
|
|
|
|
import { wakuFilter } from "./protocols/filter.js";
|
|
import { wakuLightPush } from "./protocols/light_push.js";
|
|
import { wakuStore } from "./protocols/store.js";
|
|
|
|
export const DefaultPingKeepAliveValueSecs = 5 * 60;
|
|
export const DefaultRelayKeepAliveValueSecs = 5 * 60;
|
|
export const DefaultUserAgent = "js-waku";
|
|
export const DefaultPingMaxInboundStreams = 10;
|
|
|
|
const log = new Logger("waku");
|
|
|
|
export interface WakuOptions {
|
|
/**
|
|
* Set keep alive frequency in seconds: Waku will send a `/ipfs/ping/1.0.0`
|
|
* request to each peer after the set number of seconds. Set to 0 to disable.
|
|
*
|
|
* @default {@link @waku/core.DefaultPingKeepAliveValueSecs}
|
|
*/
|
|
pingKeepAlive?: number;
|
|
/**
|
|
* Set keep alive frequency in seconds: Waku will send a ping message over
|
|
* relay to each peer after the set number of seconds. Set to 0 to disable.
|
|
*
|
|
* @default {@link @waku/core.DefaultRelayKeepAliveValueSecs}
|
|
*/
|
|
relayKeepAlive?: number;
|
|
/**
|
|
* Set the user agent string to be used in identification of the node.
|
|
* @default {@link @waku/core.DefaultUserAgent}
|
|
*/
|
|
userAgent?: string;
|
|
pubsubTopics: PubsubTopic[];
|
|
}
|
|
|
|
export type CreateWakuNodeOptions = ProtocolCreateOptions &
|
|
Partial<WakuOptions>;
|
|
|
|
type ProtocolsEnabled = {
|
|
filter?: boolean;
|
|
lightpush?: boolean;
|
|
store?: boolean;
|
|
relay?: boolean;
|
|
};
|
|
|
|
export class WakuNode implements Waku {
|
|
public libp2p: Libp2p;
|
|
public relay?: IRelay;
|
|
public store?: IStoreSDK;
|
|
public filter?: IFilterSDK;
|
|
public lightPush?: ILightPushSDK;
|
|
public connectionManager: ConnectionManager;
|
|
public readonly pubsubTopics: PubsubTopic[];
|
|
public readonly health: IHealthManager;
|
|
|
|
public constructor(
|
|
options: WakuOptions,
|
|
libp2p: Libp2p,
|
|
protocolsEnabled: ProtocolsEnabled
|
|
) {
|
|
if (options.pubsubTopics.length == 0) {
|
|
throw new Error("At least one pubsub topic must be provided");
|
|
}
|
|
this.pubsubTopics = options.pubsubTopics;
|
|
|
|
this.libp2p = libp2p;
|
|
|
|
protocolsEnabled = {
|
|
filter: false,
|
|
lightpush: false,
|
|
store: false,
|
|
relay: false,
|
|
...protocolsEnabled
|
|
};
|
|
|
|
const pingKeepAlive =
|
|
options.pingKeepAlive || DefaultPingKeepAliveValueSecs;
|
|
const relayKeepAlive = this.relay
|
|
? options.relayKeepAlive || DefaultRelayKeepAliveValueSecs
|
|
: 0;
|
|
|
|
const peerId = this.libp2p.peerId.toString();
|
|
|
|
this.connectionManager = ConnectionManager.create(
|
|
peerId,
|
|
libp2p,
|
|
{ pingKeepAlive, relayKeepAlive },
|
|
this.pubsubTopics,
|
|
this.relay
|
|
);
|
|
|
|
this.health = getHealthManager();
|
|
|
|
if (protocolsEnabled.store) {
|
|
const store = wakuStore(this.connectionManager, options);
|
|
this.store = store(libp2p);
|
|
}
|
|
|
|
if (protocolsEnabled.lightpush) {
|
|
const lightPush = wakuLightPush(this.connectionManager, options);
|
|
this.lightPush = lightPush(libp2p);
|
|
}
|
|
|
|
if (protocolsEnabled.filter) {
|
|
const filter = wakuFilter(this.connectionManager, options);
|
|
this.filter = filter(libp2p);
|
|
}
|
|
|
|
if (protocolsEnabled.relay) {
|
|
const relay = wakuRelay(this.pubsubTopics);
|
|
this.relay = relay(libp2p);
|
|
}
|
|
|
|
log.info(
|
|
"Waku node created",
|
|
peerId,
|
|
`relay: ${!!this.relay}, store: ${!!this.store}, light push: ${!!this
|
|
.lightPush}, filter: ${!!this.filter}`
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Dials to the provided peer.
|
|
*
|
|
* @param peer The peer to dial
|
|
* @param protocols Waku protocols we expect from the peer; Defaults to mounted protocols
|
|
*/
|
|
public async dial(
|
|
peer: PeerId | MultiaddrInput,
|
|
protocols?: Protocols[]
|
|
): Promise<Stream> {
|
|
const _protocols = protocols ?? [];
|
|
const peerId = mapToPeerIdOrMultiaddr(peer);
|
|
|
|
if (typeof protocols === "undefined") {
|
|
this.relay && _protocols.push(Protocols.Relay);
|
|
this.store && _protocols.push(Protocols.Store);
|
|
this.filter && _protocols.push(Protocols.Filter);
|
|
this.lightPush && _protocols.push(Protocols.LightPush);
|
|
}
|
|
|
|
const codecs: string[] = [];
|
|
if (_protocols.includes(Protocols.Relay)) {
|
|
if (this.relay) {
|
|
this.relay.gossipSub.multicodecs.forEach((codec: string) =>
|
|
codecs.push(codec)
|
|
);
|
|
} else {
|
|
log.error(
|
|
"Relay codec not included in dial codec: protocol not mounted locally"
|
|
);
|
|
}
|
|
}
|
|
if (_protocols.includes(Protocols.Store)) {
|
|
if (this.store) {
|
|
codecs.push(this.store.protocol.multicodec);
|
|
} else {
|
|
log.error(
|
|
"Store codec not included in dial codec: protocol not mounted locally"
|
|
);
|
|
}
|
|
}
|
|
if (_protocols.includes(Protocols.LightPush)) {
|
|
if (this.lightPush) {
|
|
codecs.push(this.lightPush.protocol.multicodec);
|
|
} else {
|
|
log.error(
|
|
"Light Push codec not included in dial codec: protocol not mounted locally"
|
|
);
|
|
}
|
|
}
|
|
if (_protocols.includes(Protocols.Filter)) {
|
|
if (this.filter) {
|
|
codecs.push(this.filter.protocol.multicodec);
|
|
} else {
|
|
log.error(
|
|
"Filter codec not included in dial codec: protocol not mounted locally"
|
|
);
|
|
}
|
|
}
|
|
|
|
log.info(`Dialing to ${peerId.toString()} with protocols ${_protocols}`);
|
|
|
|
return this.libp2p.dialProtocol(peerId, codecs);
|
|
}
|
|
|
|
public async start(): Promise<void> {
|
|
await this.libp2p.start();
|
|
}
|
|
|
|
public async stop(): Promise<void> {
|
|
this.connectionManager.stop();
|
|
await this.libp2p.stop();
|
|
}
|
|
|
|
public isStarted(): boolean {
|
|
return this.libp2p.status == "started";
|
|
}
|
|
|
|
public isConnected(): boolean {
|
|
return this.connectionManager.isConnected();
|
|
}
|
|
|
|
/**
|
|
* Return the local multiaddr with peer id on which libp2p is listening.
|
|
*
|
|
* @throws if libp2p is not listening on localhost.
|
|
*/
|
|
public getLocalMultiaddrWithID(): string {
|
|
const localMultiaddr = this.libp2p
|
|
.getMultiaddrs()
|
|
.find((addr) => addr.toString().match(/127\.0\.0\.1/));
|
|
if (!localMultiaddr || localMultiaddr.toString() === "") {
|
|
throw "Not listening on localhost";
|
|
}
|
|
return localMultiaddr + "/p2p/" + this.libp2p.peerId.toString();
|
|
}
|
|
}
|
|
function mapToPeerIdOrMultiaddr(
|
|
peerId: PeerId | MultiaddrInput
|
|
): PeerId | Multiaddr {
|
|
return isPeerId(peerId) ? peerId : multiaddr(peerId);
|
|
}
|