292 lines
8.4 KiB
TypeScript
Raw Normal View History

2022-07-22 15:26:43 +10:00
import type { Stream } from "@libp2p/interface-connection";
2023-02-09 13:21:03 +11:00
import type { Libp2p } from "@libp2p/interface-libp2p";
import type { PeerId } from "@libp2p/interface-peer-id";
import type { PubSub } from "@libp2p/interface-pubsub";
import type { Multiaddr } from "@multiformats/multiaddr";
feat!: implement peer exchange (#1027) * wip -- yet to test * update: draft * wip * support passing flags manually to nwaku node * refactor peer-exchange test * switch response from uint8array to ENR * rm: unnecesary logs * implement clas * fix: for loop * init-wip: directories * setup: new package & fix circular deps * bind a response handler * wip: refactor & update test * test logs * wip code - debugging * address: comments * Update packages/core/src/lib/waku_peer_exchange/peer_discovery.ts Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * Update packages/core/src/lib/waku_peer_exchange/peer_discovery.ts Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * address: comments * address: comments * address: comments * address: comments * address: comments * fix: test build * refactor * fix: build * comply with API * numPeers: use number instead of bigint * fix: build * Update packages/peer-exchange/package.json Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * Update packages/peer-exchange/src/waku_peer_exchange.ts Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * Update packages/peer-exchange/src/waku_peer_exchange.ts Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * Update packages/peer-exchange/src/waku_peer_exchange.ts Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * address: comments, add eslint config * Update packages/peer-exchange/.eslintrc.cjs Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * Update packages/peer-exchange/src/index.ts Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * address comments * test works with test fleet * rm: only for px test => run all tests * fix: tests * reorder packages for build, and fix imports * remove: px test doesnt work with local nodes * chore: move proto into a separate package * fix: proto dir * fix: build * fix: ci * add: index for proto * fix: ci * Update packages/proto/package.json Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * address comments * chore: run failing test with higher timeout * chore: run failing test with higher timeout * fix: ci Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com>
2022-12-07 11:35:30 +05:30
import type {
IFilter,
ILightPush,
IRelay,
IStore,
feat!: implement peer exchange (#1027) * wip -- yet to test * update: draft * wip * support passing flags manually to nwaku node * refactor peer-exchange test * switch response from uint8array to ENR * rm: unnecesary logs * implement clas * fix: for loop * init-wip: directories * setup: new package & fix circular deps * bind a response handler * wip: refactor & update test * test logs * wip code - debugging * address: comments * Update packages/core/src/lib/waku_peer_exchange/peer_discovery.ts Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * Update packages/core/src/lib/waku_peer_exchange/peer_discovery.ts Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * address: comments * address: comments * address: comments * address: comments * address: comments * fix: test build * refactor * fix: build * comply with API * numPeers: use number instead of bigint * fix: build * Update packages/peer-exchange/package.json Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * Update packages/peer-exchange/src/waku_peer_exchange.ts Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * Update packages/peer-exchange/src/waku_peer_exchange.ts Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * Update packages/peer-exchange/src/waku_peer_exchange.ts Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * address: comments, add eslint config * Update packages/peer-exchange/.eslintrc.cjs Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * Update packages/peer-exchange/src/index.ts Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * address comments * test works with test fleet * rm: only for px test => run all tests * fix: tests * reorder packages for build, and fix imports * remove: px test doesnt work with local nodes * chore: move proto into a separate package * fix: proto dir * fix: build * fix: ci * add: index for proto * fix: ci * Update packages/proto/package.json Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * address comments * chore: run failing test with higher timeout * chore: run failing test with higher timeout * fix: ci Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com>
2022-12-07 11:35:30 +05:30
Waku,
} from "@waku/interfaces";
2022-11-01 16:30:24 +11:00
import { Protocols } from "@waku/interfaces";
2022-02-04 14:12:00 +11:00
import debug from "debug";
import { createEncoder } from "./message/version_0.js";
import * as relayConstants from "./relay/constants.js";
import { RelayPingContentTopic } from "./relay/constants.js";
2021-03-19 14:40:16 +11:00
export const DefaultPingKeepAliveValueSecs = 0;
export const DefaultRelayKeepAliveValueSecs = 5 * 60;
export const DefaultUserAgent = "js-waku";
2022-06-23 16:38:14 +10:00
const log = debug("waku: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.
*
2023-02-09 14:45:32 +11:00
* @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.
*
2023-02-09 14:45:32 +11:00
* @default {@link @waku/core.DefaultRelayKeepAliveValueSecs}
*/
relayKeepAlive?: number;
/**
* Set the user agent string to be used in identification of the node.
2023-02-09 14:45:32 +11:00
* @default {@link @waku/core.DefaultUserAgent}
*/
userAgent?: string;
}
2021-03-29 13:56:17 +11:00
2022-09-06 12:25:28 +10:00
export class WakuNode implements Waku {
public libp2p: Libp2p;
public relay?: IRelay;
public store?: IStore;
public filter?: IFilter;
public lightPush?: ILightPush;
private pingKeepAliveTimers: {
[peer: string]: ReturnType<typeof setInterval>;
};
private relayKeepAliveTimers: {
[peer: string]: ReturnType<typeof setInterval>;
};
constructor(
options: WakuOptions,
2021-05-19 11:00:43 +10:00
libp2p: Libp2p,
2023-02-09 13:21:03 +11:00
store?: (libp2p: Libp2p) => IStore,
lightPush?: (libp2p: Libp2p) => ILightPush,
filter?: (libp2p: Libp2p) => IFilter
2021-05-19 11:00:43 +10:00
) {
this.libp2p = libp2p;
if (store) {
2023-02-09 13:21:03 +11:00
this.store = store(libp2p);
}
if (filter) {
2023-02-09 13:21:03 +11:00
this.filter = filter(libp2p);
}
if (lightPush) {
2023-02-09 13:21:03 +11:00
this.lightPush = lightPush(libp2p);
}
if (isRelay(libp2p.pubsub)) {
this.relay = libp2p.pubsub;
}
2022-09-13 20:25:40 +10:00
log(
"Waku node created",
this.libp2p.peerId.toString(),
`relay: ${!!this.relay}, store: ${!!this.store}, light push: ${!!this
.lightPush}, filter: ${!!this.filter}} `
2022-09-13 20:25:40 +10:00
);
this.pingKeepAliveTimers = {};
this.relayKeepAliveTimers = {};
const pingKeepAlive =
options.pingKeepAlive || DefaultPingKeepAliveValueSecs;
const relayKeepAlive = this.relay
? options.relayKeepAlive || DefaultRelayKeepAliveValueSecs
: 0;
2023-02-09 13:21:03 +11:00
libp2p.addEventListener("peer:connect", (evt) => {
2022-06-21 13:23:42 +10:00
this.startKeepAlive(evt.detail.remotePeer, pingKeepAlive, relayKeepAlive);
});
2022-05-29 17:39:51 +02:00
/**
2022-05-29 17:46:26 +02:00
* NOTE: Event is not being emitted on closing nor losing a connection.
2022-05-29 17:39:51 +02:00
* @see https://github.com/libp2p/js-libp2p/issues/939
2022-05-29 17:41:55 +02:00
* @see https://github.com/status-im/js-waku/issues/252
2022-05-29 17:39:51 +02:00
*
* >This event will be triggered anytime we are disconnected from another peer,
* >regardless of the circumstances of that disconnection.
* >If we happen to have multiple connections to a peer,
* >this event will **only** be triggered when the last connection is closed.
* @see https://github.com/libp2p/js-libp2p/blob/bad9e8c0ff58d60a78314077720c82ae331cc55b/doc/API.md?plain=1#L2100
*/
2023-02-09 13:21:03 +11:00
libp2p.addEventListener("peer:disconnect", (evt) => {
2022-06-21 13:23:42 +10:00
this.stopKeepAlive(evt.detail.remotePeer);
});
// Trivial handling of discovered peers, to be refined.
libp2p.addEventListener("peer:discovery", (evt) => {
const peerId = evt.detail.id;
log(`Found peer ${peerId.toString()}, dialing.`);
libp2p.dial(peerId).catch((err) => {
log(`Fail to dial ${peerId}`, err);
});
});
}
2021-03-19 14:40:16 +11:00
/**
2021-04-07 11:04:30 +10:00
* Dials to the provided peer.
*
* @param peer The peer to dial
* @param protocols Waku protocols we expect from the peer; Defaults to mounted protocols
*/
2022-07-22 15:26:43 +10:00
async dial(
peer: PeerId | Multiaddr,
protocols?: Protocols[]
): Promise<Stream> {
const _protocols = protocols ?? [];
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.multicodecs.forEach((codec) => codecs.push(codec));
} else {
log(
"Relay codec not included in dial codec: protocol not mounted locally"
);
}
}
if (_protocols.includes(Protocols.Store)) {
if (this.store) {
codecs.push(this.store.multicodec);
} else {
log(
"Store codec not included in dial codec: protocol not mounted locally"
);
}
}
if (_protocols.includes(Protocols.LightPush)) {
if (this.lightPush) {
codecs.push(this.lightPush.multicodec);
} else {
log(
"Light Push codec not included in dial codec: protocol not mounted locally"
);
}
2022-05-26 14:48:33 +10:00
}
if (_protocols.includes(Protocols.Filter)) {
if (this.filter) {
codecs.push(this.filter.multicodec);
} else {
log(
"Filter codec not included in dial codec: protocol not mounted locally"
);
}
}
feat!: implement peer exchange (#1027) * wip -- yet to test * update: draft * wip * support passing flags manually to nwaku node * refactor peer-exchange test * switch response from uint8array to ENR * rm: unnecesary logs * implement clas * fix: for loop * init-wip: directories * setup: new package & fix circular deps * bind a response handler * wip: refactor & update test * test logs * wip code - debugging * address: comments * Update packages/core/src/lib/waku_peer_exchange/peer_discovery.ts Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * Update packages/core/src/lib/waku_peer_exchange/peer_discovery.ts Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * address: comments * address: comments * address: comments * address: comments * address: comments * fix: test build * refactor * fix: build * comply with API * numPeers: use number instead of bigint * fix: build * Update packages/peer-exchange/package.json Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * Update packages/peer-exchange/src/waku_peer_exchange.ts Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * Update packages/peer-exchange/src/waku_peer_exchange.ts Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * Update packages/peer-exchange/src/waku_peer_exchange.ts Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * address: comments, add eslint config * Update packages/peer-exchange/.eslintrc.cjs Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * Update packages/peer-exchange/src/index.ts Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * address comments * test works with test fleet * rm: only for px test => run all tests * fix: tests * reorder packages for build, and fix imports * remove: px test doesnt work with local nodes * chore: move proto into a separate package * fix: proto dir * fix: build * fix: ci * add: index for proto * fix: ci * Update packages/proto/package.json Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com> * address comments * chore: run failing test with higher timeout * chore: run failing test with higher timeout * fix: ci Co-authored-by: fryorcraken.eth <110212804+fryorcraken@users.noreply.github.com>
2022-12-07 11:35:30 +05:30
log(`Dialing to ${peer.toString()} with protocols ${_protocols}`);
return this.libp2p.dialProtocol(peer, codecs);
}
async start(): Promise<void> {
await this.libp2p.start();
}
async stop(): Promise<void> {
2022-05-29 17:39:51 +02:00
this.stopAllKeepAlives();
2022-05-29 13:46:12 +02:00
await this.libp2p.stop();
}
2021-04-06 11:06:10 +10:00
isStarted(): boolean {
return this.libp2p.isStarted();
}
2021-04-06 11:06:10 +10:00
/**
* Return the local multiaddr with peer id on which libp2p is listening.
2022-02-11 17:27:15 +11:00
*
* @throws if libp2p is not listening on localhost.
2021-04-06 11:06:10 +10:00
*/
getLocalMultiaddrWithID(): string {
const localMultiaddr = this.libp2p
.getMultiaddrs()
.find((addr) => addr.toString().match(/127\.0\.0\.1/));
2022-02-04 14:12:00 +11:00
if (!localMultiaddr || localMultiaddr.toString() === "") {
throw "Not listening on localhost";
2021-04-06 11:06:10 +10:00
}
return localMultiaddr + "/p2p/" + this.libp2p.peerId.toString();
2021-04-06 11:06:10 +10:00
}
private startKeepAlive(
peerId: PeerId,
pingPeriodSecs: number,
relayPeriodSecs: number
): void {
// Just in case a timer already exist for this peer
this.stopKeepAlive(peerId);
const peerIdStr = peerId.toString();
if (pingPeriodSecs !== 0) {
this.pingKeepAliveTimers[peerIdStr] = setInterval(() => {
2022-06-22 15:59:18 +10:00
this.libp2p.ping(peerId).catch((e) => {
2022-06-23 16:38:14 +10:00
log(`Ping failed (${peerIdStr})`, e);
});
}, pingPeriodSecs * 1000);
}
const relay = this.relay;
if (relay && relayPeriodSecs !== 0) {
const encoder = createEncoder({
contentTopic: RelayPingContentTopic,
ephemeral: true,
});
this.relayKeepAliveTimers[peerIdStr] = setInterval(() => {
log("Sending Waku Relay ping message");
relay
.send(encoder, { payload: new Uint8Array() })
.catch((e) => log("Failed to send relay ping", e));
}, relayPeriodSecs * 1000);
}
}
private stopKeepAlive(peerId: PeerId): void {
const peerIdStr = peerId.toString();
if (this.pingKeepAliveTimers[peerIdStr]) {
clearInterval(this.pingKeepAliveTimers[peerIdStr]);
delete this.pingKeepAliveTimers[peerIdStr];
}
if (this.relayKeepAliveTimers[peerIdStr]) {
clearInterval(this.relayKeepAliveTimers[peerIdStr]);
delete this.relayKeepAliveTimers[peerIdStr];
}
}
2022-05-29 17:39:51 +02:00
private stopAllKeepAlives(): void {
for (const timer of [
...Object.values(this.pingKeepAliveTimers),
...Object.values(this.relayKeepAliveTimers),
]) {
clearInterval(timer);
}
this.pingKeepAliveTimers = {};
this.relayKeepAliveTimers = {};
}
2021-03-19 14:40:16 +11:00
}
function isRelay(pubsub: PubSub): pubsub is IRelay {
if (pubsub) {
try {
return pubsub.multicodecs.includes(
relayConstants.RelayCodecs[relayConstants.RelayCodecs.length - 1]
);
// Exception is expected if `libp2p` was not instantiated with pubsub
// eslint-disable-next-line no-empty
} catch (e) {}
}
return false;
}