Merge pull request #931 from status-im/optional-protocols

This commit is contained in:
fryorcraken.eth 2022-09-08 19:30:00 +10:00 committed by GitHub
commit 4d29698128
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 443 additions and 135 deletions

View File

@ -2,13 +2,13 @@ module.exports = [
{
name: "Waku core",
path: "bundle/index.js",
import: "{ Waku }",
import: "{ WakuNode }",
},
{
name: "Waku default setup",
path: ["bundle/index.js", "bundle/lib/create_waku.js"],
import: {
"./bundle/lib/create_waku.js": "{ createWaku }",
"./bundle/lib/create_waku.js": "{ createLightNode }",
"./bundle/lib/wait_for_remote_peer.js": "{ waitForRemotePeer }",
},
},

View File

@ -12,11 +12,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Simple connection management that selects the most recent connection for store, light push and filter requests.
- `createLightNode` to create a Waku node for resource restricted environment with Light Push, Filter and Relay.
- `createPrivacyNode` to create a Waku node for privacy preserving usage with Relay only.
- `createFullNode` to create a Waku node for with all protocols, for **testing purposes only**.
### Changed
- **breaking**: `DecryptionParams` may be passed when using `queryHistory` instead of just keys.
- Examples have been moved to https://github.com/waku-org/js-waku-examples.
- `Waku` is now defined as an interface with `WakuNode` an implementation of it.
- `createWaku` is deprecated in favour of `createLightNode` and `createPrivacyNode`.
- `waitForRemotePeer` can throw, default behaviour has changed.
### Fixed

1
package-lock.json generated
View File

@ -18,6 +18,7 @@
"@libp2p/interface-peer-id": "^1.0.2",
"@libp2p/interface-peer-info": "^1.0.1",
"@libp2p/interface-peer-store": "^1.0.0",
"@libp2p/interface-pubsub": "^2.0.1",
"@libp2p/interfaces": "^3.0.2",
"@libp2p/mplex": "^5.1.1",
"@libp2p/peer-id": "^1.1.10",

View File

@ -13,6 +13,9 @@
"types": "./dist/lib/create_waku.d.ts",
"import": "./dist/lib/create_waku.js"
},
"./lib/interfaces": {
"types": "./dist/lib/interfaces.d.ts"
},
"./lib/peer_discovery_dns": {
"types": "./dist/lib/peer_discovery_dns/index.d.ts",
"import": "./dist/lib/peer_discovery_dns/index.js"
@ -60,7 +63,7 @@
"pretest": "run-s pretest:*",
"pretest:1-init-git-submodules": "[ -f './nwaku/build/wakunode2' ] || git submodule update --init --recursive",
"pretest:2-build-nwaku": "[ -f './nwaku/build/wakunode2' ] || run-s nwaku:build",
"nwaku:build": "(cd nwaku; NIMFLAGS=\"-d:chronicles_colors=off -d:chronicles_sinks=textlines -d:chronicles_log_level=TRACE\" make -j$(nproc --all 2>/dev/null || echo 2) wakunode2)",
"nwaku:build": "(cd nwaku; make update; NIMFLAGS=\"-d:chronicles_colors=off -d:chronicles_sinks=textlines -d:chronicles_log_level=TRACE\" make -j$(nproc --all 2>/dev/null || echo 2) wakunode2)",
"nwaku:force-build": "(cd nwaku && rm -rf ./build/ ./vendor && make -j$(nproc --all 2>/dev/null || echo 2) update) && run-s nwaku:build",
"test": "run-s test:*",
"test:lint": "eslint src --ext .ts",
@ -95,6 +98,7 @@
"@libp2p/interface-peer-id": "^1.0.2",
"@libp2p/interface-peer-info": "^1.0.1",
"@libp2p/interface-peer-store": "^1.0.0",
"@libp2p/interface-pubsub": "^2.0.1",
"@libp2p/interfaces": "^3.0.2",
"@libp2p/mplex": "^5.1.1",
"@libp2p/peer-id": "^1.1.10",

View File

@ -13,7 +13,7 @@ export * as utils from "./lib/utils";
export * as proto_message from "./proto/message";
export * as waku from "./lib/waku";
export { Waku, Protocols } from "./lib/waku";
export { WakuNode, Protocols } from "./lib/waku";
export * as waku_message from "./lib/waku_message";
export { WakuMessage } from "./lib/waku_message";

View File

@ -6,9 +6,10 @@ import { all as filterAll } from "@libp2p/websockets/filters";
import { createLibp2p, Libp2pOptions } from "libp2p";
import type { Libp2p } from "libp2p";
import type { Waku, WakuFull, WakuLight, WakuPrivacy } from "./interfaces";
import { PeerDiscoveryStaticPeers } from "./peer_discovery_static_list";
import { getPredefinedBootstrapNodes } from "./predefined_bootstrap_nodes";
import { Waku, WakuOptions } from "./waku";
import { WakuNode, WakuOptions } from "./waku";
import { WakuFilter } from "./waku_filter";
import { WakuLightPush } from "./waku_light_push";
import { WakuRelay } from "./waku_relay";
@ -30,11 +31,11 @@ export interface CreateOptions {
*/
pubSubTopic?: string;
/**
* You can pass options to the `Libp2p` instance used by {@link index.waku.Waku} using the {@link CreateOptions.libp2p} property.
* You can pass options to the `Libp2p` instance used by {@link index.waku.WakuNode} using the {@link CreateOptions.libp2p} property.
* This property is the same type than the one passed to [`Libp2p.create`](https://github.com/libp2p/js-libp2p/blob/master/doc/API.md#create)
* apart that we made the `modules` property optional and partial,
* allowing its omission and letting Waku set good defaults.
* Notes that some values are overridden by {@link index.waku.Waku} to ensure it implements the Waku protocol.
* Notes that some values are overridden by {@link index.waku.WakuNode} to ensure it implements the Waku protocol.
*/
libp2p?: Partial<Libp2pOptions>;
/**
@ -49,6 +50,99 @@ export interface CreateOptions {
defaultBootstrap?: boolean;
}
/**
* Create a Waku node that uses Waku Light Push, Filter and Store to send and
* receive messages, enabling low resource consumption.
* **Note: This is NOT compatible with nwaku v0.11**
*
* @see https://github.com/status-im/nwaku/issues/1085
*/
export async function createLightNode(
options?: CreateOptions & WakuOptions
): Promise<WakuLight> {
const libp2pOptions = options?.libp2p ?? {};
const peerDiscovery = libp2pOptions.peerDiscovery ?? [];
if (options?.defaultBootstrap) {
peerDiscovery.push(defaultPeerDiscovery());
Object.assign(libp2pOptions, { peerDiscovery });
}
const libp2p = await defaultLibp2p(undefined, libp2pOptions);
const wakuStore = new WakuStore(libp2p, options);
const wakuLightPush = new WakuLightPush(libp2p, options);
const wakuFilter = new WakuFilter(libp2p, options);
return new WakuNode(
options ?? {},
libp2p,
wakuStore,
wakuLightPush,
wakuFilter
) as WakuLight;
}
/**
* Create a Waku node that uses Waku Relay to send and receive messages,
* enabling some privacy preserving properties.
*/
export async function createPrivacyNode(
options?: CreateOptions & WakuOptions
): Promise<WakuPrivacy> {
const libp2pOptions = options?.libp2p ?? {};
const peerDiscovery = libp2pOptions.peerDiscovery ?? [];
if (options?.defaultBootstrap) {
peerDiscovery.push(defaultPeerDiscovery());
Object.assign(libp2pOptions, { peerDiscovery });
}
const libp2p = await defaultLibp2p(new WakuRelay(options), libp2pOptions);
return new WakuNode(options ?? {}, libp2p) as WakuPrivacy;
}
/**
* Create a Waku node that uses all Waku protocols.
*
* This helper is not recommended except if:
* - you are interfacing with nwaku v0.11 or below
* - you are doing some form of testing
*
* If you are building a full node, it is recommended to use
* [nwaku](github.com/status-im/nwaku) and its JSON RPC API or wip REST API.
*
* @see https://github.com/status-im/nwaku/issues/1085
* @internal
*/
export async function createFullNode(
options?: CreateOptions & WakuOptions
): Promise<WakuFull> {
const libp2pOptions = options?.libp2p ?? {};
const peerDiscovery = libp2pOptions.peerDiscovery ?? [];
if (options?.defaultBootstrap) {
peerDiscovery.push(defaultPeerDiscovery());
Object.assign(libp2pOptions, { peerDiscovery });
}
const libp2p = await defaultLibp2p(new WakuRelay(options), libp2pOptions);
const wakuStore = new WakuStore(libp2p, options);
const wakuLightPush = new WakuLightPush(libp2p, options);
const wakuFilter = new WakuFilter(libp2p, options);
return new WakuNode(
options ?? {},
libp2p,
wakuStore,
wakuLightPush,
wakuFilter
) as WakuFull;
}
/**
* @deprecated use { @link createLightNode }, { @link createPrivacyNode } or
* { @link index.waku.WakuNode.constructor } instead.
*/
export async function createWaku(
options?: CreateOptions & WakuOptions
): Promise<Waku> {
@ -65,7 +159,13 @@ export async function createWaku(
const wakuLightPush = new WakuLightPush(libp2p, options);
const wakuFilter = new WakuFilter(libp2p, options);
return new Waku(options ?? {}, libp2p, wakuStore, wakuLightPush, wakuFilter);
return new WakuNode(
options ?? {},
libp2p,
wakuStore,
wakuLightPush,
wakuFilter
);
}
export function defaultPeerDiscovery(): PeerDiscovery {
@ -73,7 +173,7 @@ export function defaultPeerDiscovery(): PeerDiscovery {
}
export async function defaultLibp2p(
wakuRelay: WakuRelay,
wakuRelay?: WakuRelay,
options?: Partial<Libp2pOptions>
): Promise<Libp2p> {
const libp2pOpts = Object.assign(
@ -82,7 +182,7 @@ export async function defaultLibp2p(
streamMuxers: [new Mplex()],
connectionEncryption: [new Noise()],
},
{ pubsub: wakuRelay },
wakuRelay ? { pubsub: wakuRelay } : {},
options ?? {}
);

View File

@ -2,8 +2,9 @@ import { expect } from "chai";
import { makeLogFileName, NOISE_KEY_1, Nwaku } from "../../test_utils";
import { createWaku } from "../create_waku";
import type { Waku } from "../interfaces";
import { waitForRemotePeer } from "../wait_for_remote_peer";
import { Protocols, Waku } from "../waku";
import { Protocols } from "../waku";
import { ENR } from "./enr";

60
src/lib/interfaces.ts Normal file
View File

@ -0,0 +1,60 @@
import type { Stream } from "@libp2p/interface-connection";
import type { PeerId } from "@libp2p/interface-peer-id";
import type { Multiaddr } from "@multiformats/multiaddr";
import type { Libp2p } from "libp2p";
import type { Protocols } from "./waku";
import type { WakuFilter } from "./waku_filter";
import type { WakuLightPush } from "./waku_light_push";
import type { DecryptionMethod } from "./waku_message";
import type { WakuRelay } from "./waku_relay";
import type { WakuStore } from "./waku_store";
export interface Waku {
libp2p: Libp2p;
relay?: WakuRelay;
store?: WakuStore;
filter?: WakuFilter;
lightPush?: WakuLightPush;
dial(peer: PeerId | Multiaddr, protocols?: Protocols[]): Promise<Stream>;
addPeerToAddressBook(
peerId: PeerId | string,
multiaddrs: Multiaddr[] | string[]
): void;
start(): Promise<void>;
stop(): Promise<void>;
isStarted(): boolean;
addDecryptionKey(
key: Uint8Array | string,
options?: { method?: DecryptionMethod; contentTopics?: string[] }
): void;
deleteDecryptionKey(key: Uint8Array | string): void;
}
export interface WakuLight extends Waku {
relay: undefined;
store: WakuStore;
filter: WakuFilter;
lightPush: WakuLightPush;
}
export interface WakuPrivacy extends Waku {
relay: WakuRelay;
store: undefined;
filter: undefined;
lightPush: undefined;
}
export interface WakuFull extends Waku {
relay: WakuRelay;
store: WakuStore;
filter: WakuFilter;
lightPush: WakuLightPush;
}

View File

@ -3,12 +3,14 @@ import { expect } from "chai";
import { makeLogFileName, NOISE_KEY_1, Nwaku } from "../test_utils";
import { delay } from "../test_utils/delay";
import { createWaku } from "./create_waku";
import { createLightNode, createPrivacyNode } from "./create_waku";
import type { WakuLight, WakuPrivacy } from "./interfaces";
import { waitForRemotePeer } from "./wait_for_remote_peer";
import { Protocols, Waku } from "./waku";
import { Protocols } from "./waku";
describe("Wait for remote peer", function () {
let waku: Waku;
let waku1: WakuPrivacy;
let waku2: WakuLight;
let nwaku: Nwaku | undefined;
afterEach(async function () {
@ -16,7 +18,8 @@ describe("Wait for remote peer", function () {
nwaku.stop();
nwaku = undefined;
}
!!waku && waku.stop().catch((e) => console.log("Waku failed to stop", e));
waku1?.stop().catch((e) => console.log("Waku failed to stop", e));
waku2?.stop().catch((e) => console.log("Waku failed to stop", e));
});
it("Relay - dialed first", async function () {
@ -30,14 +33,14 @@ describe("Wait for remote peer", function () {
});
const multiAddrWithId = await nwaku.getMultiaddrWithId();
waku = await createWaku({
waku1 = await createPrivacyNode({
staticNoiseKey: NOISE_KEY_1,
});
await waku.start();
await waku.dial(multiAddrWithId);
await waku1.start();
await waku1.dial(multiAddrWithId);
await delay(1000);
await waitForRemotePeer(waku, [Protocols.Relay]);
const peers = waku.relay.getMeshPeers();
await waitForRemotePeer(waku1, [Protocols.Relay]);
const peers = waku1.relay.getMeshPeers();
const nimPeerId = multiAddrWithId.getPeerId();
expect(nimPeerId).to.not.be.undefined;
@ -55,17 +58,17 @@ describe("Wait for remote peer", function () {
});
const multiAddrWithId = await nwaku.getMultiaddrWithId();
waku = await createWaku({
waku1 = await createPrivacyNode({
staticNoiseKey: NOISE_KEY_1,
});
await waku.start();
await waku1.start();
const waitPromise = waitForRemotePeer(waku, [Protocols.Relay]);
const waitPromise = waitForRemotePeer(waku1, [Protocols.Relay]);
await delay(1000);
await waku.dial(multiAddrWithId);
await waku1.dial(multiAddrWithId);
await waitPromise;
const peers = waku.relay.getMeshPeers();
const peers = waku1.relay.getMeshPeers();
const nimPeerId = multiAddrWithId.getPeerId();
expect(nimPeerId).to.not.be.undefined;
@ -74,12 +77,12 @@ describe("Wait for remote peer", function () {
it("Relay - times out", function (done) {
this.timeout(5000);
createWaku({
createPrivacyNode({
staticNoiseKey: NOISE_KEY_1,
})
.then((waku) => waku.start().then(() => waku))
.then((waku) => {
waitForRemotePeer(waku, [Protocols.Relay], 200).then(
.then((waku1) => waku1.start().then(() => waku1))
.then((waku1) => {
waitForRemotePeer(waku1, [Protocols.Relay], 200).then(
() => {
throw "Promise expected to reject on time out";
},
@ -103,15 +106,15 @@ describe("Wait for remote peer", function () {
});
const multiAddrWithId = await nwaku.getMultiaddrWithId();
waku = await createWaku({
waku2 = await createLightNode({
staticNoiseKey: NOISE_KEY_1,
});
await waku.start();
await waku.dial(multiAddrWithId);
await waku2.start();
await waku2.dial(multiAddrWithId);
await delay(1000);
await waitForRemotePeer(waku, [Protocols.Store]);
await waitForRemotePeer(waku2, [Protocols.Store]);
const peers = (await waku.store.peers()).map((peer) => peer.id.toString());
const peers = (await waku2.store.peers()).map((peer) => peer.id.toString());
const nimPeerId = multiAddrWithId.getPeerId();
expect(nimPeerId).to.not.be.undefined;
@ -130,16 +133,16 @@ describe("Wait for remote peer", function () {
});
const multiAddrWithId = await nwaku.getMultiaddrWithId();
waku = await createWaku({
waku2 = await createLightNode({
staticNoiseKey: NOISE_KEY_1,
});
await waku.start();
const waitPromise = waitForRemotePeer(waku, [Protocols.Store], 2000);
await waku2.start();
const waitPromise = waitForRemotePeer(waku2, [Protocols.Store], 2000);
await delay(1000);
await waku.dial(multiAddrWithId);
await waku2.dial(multiAddrWithId);
await waitPromise;
const peers = (await waku.store.peers()).map((peer) => peer.id.toString());
const peers = (await waku2.store.peers()).map((peer) => peer.id.toString());
const nimPeerId = multiAddrWithId.getPeerId();
@ -158,14 +161,14 @@ describe("Wait for remote peer", function () {
});
const multiAddrWithId = await nwaku.getMultiaddrWithId();
waku = await createWaku({
waku2 = await createLightNode({
staticNoiseKey: NOISE_KEY_1,
});
await waku.start();
await waku.dial(multiAddrWithId);
await waitForRemotePeer(waku, [Protocols.LightPush]);
await waku2.start();
await waku2.dial(multiAddrWithId);
await waitForRemotePeer(waku2, [Protocols.LightPush]);
const peers = (await waku.lightPush.peers()).map((peer) =>
const peers = (await waku2.lightPush.peers()).map((peer) =>
peer.id.toString()
);
@ -186,14 +189,79 @@ describe("Wait for remote peer", function () {
});
const multiAddrWithId = await nwaku.getMultiaddrWithId();
waku = await createWaku({
waku2 = await createLightNode({
staticNoiseKey: NOISE_KEY_1,
});
await waku.start();
await waku.dial(multiAddrWithId);
await waitForRemotePeer(waku, [Protocols.Filter]);
await waku2.start();
await waku2.dial(multiAddrWithId);
await waitForRemotePeer(waku2, [Protocols.Filter]);
const peers = (await waku.filter.peers()).map((peer) => peer.id.toString());
const peers = (await waku2.filter.peers()).map((peer) =>
peer.id.toString()
);
const nimPeerId = multiAddrWithId.getPeerId();
expect(nimPeerId).to.not.be.undefined;
expect(peers.includes(nimPeerId as string)).to.be.true;
});
it("Light Node - default protocols", async function () {
this.timeout(20_000);
nwaku = new Nwaku(makeLogFileName(this));
await nwaku.start({
filter: true,
lightpush: true,
relay: false,
store: true,
persistMessages: true,
});
const multiAddrWithId = await nwaku.getMultiaddrWithId();
waku2 = await createLightNode({
staticNoiseKey: NOISE_KEY_1,
});
await waku2.start();
await waku2.dial(multiAddrWithId);
await waitForRemotePeer(waku2);
const filterPeers = (await waku2.filter.peers()).map((peer) =>
peer.id.toString()
);
const storePeers = (await waku2.store.peers()).map((peer) =>
peer.id.toString()
);
const lightPushPeers = (await waku2.lightPush.peers()).map((peer) =>
peer.id.toString()
);
const nimPeerId = multiAddrWithId.getPeerId();
expect(nimPeerId).to.not.be.undefined;
expect(filterPeers.includes(nimPeerId as string)).to.be.true;
expect(storePeers.includes(nimPeerId as string)).to.be.true;
expect(lightPushPeers.includes(nimPeerId as string)).to.be.true;
});
it("Privacy Node - default protocol", async function () {
this.timeout(20_000);
nwaku = new Nwaku(makeLogFileName(this));
await nwaku.start({
filter: false,
lightpush: false,
relay: true,
store: false,
});
const multiAddrWithId = await nwaku.getMultiaddrWithId();
waku1 = await createPrivacyNode({
staticNoiseKey: NOISE_KEY_1,
});
await waku1.start();
await waku1.dial(multiAddrWithId);
await waitForRemotePeer(waku1);
const peers = await waku1.relay.getMeshPeers();
const nimPeerId = multiAddrWithId.getPeerId();

View File

@ -5,7 +5,8 @@ import type { Libp2p } from "libp2p";
import { pEvent } from "p-event";
import { StoreCodecs } from "./constants";
import { Protocols, Waku } from "./waku";
import type { Waku } from "./interfaces";
import { Protocols } from "./waku";
import { FilterCodec } from "./waku_filter";
import { LightPushCodec } from "./waku_light_push";
@ -22,8 +23,9 @@ interface WakuGossipSubProtocol extends GossipSub {
/**
* Wait for a remote peer to be ready given the passed protocols.
* Must be used after attempting to connect to nodes, using {@link index.waku.Waku.dial} or
* a bootstrap method with {@link index.waku.Waku.constructor}.
* Must be used after attempting to connect to nodes, using
* {@link index.waku.WakuNode.dial} or a bootstrap method with
* {@link lib/create_waku.createLightNode}.
*
* If the passed protocols is a GossipSub protocol, then it resolves only once
* a peer is in a mesh, to help ensure that other peers will send and receive
@ -35,33 +37,41 @@ interface WakuGossipSubProtocol extends GossipSub {
*
* @returns A promise that **resolves** if all desired protocols are fulfilled by
* remote nodes, **rejects** if the timeoutMs is reached.
*
* @default Remote peer must have Waku Relay enabled and no time out is applied.
* @throws If passing a protocol that is not mounted
* @default Wait for remote peers with protocols enabled locally and no time out is applied.
*/
export async function waitForRemotePeer(
waku: Waku,
protocols?: Protocols[],
timeoutMs?: number
): Promise<void> {
protocols = protocols ?? [Protocols.Relay];
protocols = protocols ?? getEnabledProtocols(waku);
if (!waku.isStarted()) return Promise.reject("Waku node is not started");
const promises = [];
if (protocols.includes(Protocols.Relay)) {
if (!waku.relay)
throw new Error("Cannot wait for Relay peer: protocol not mounted");
promises.push(waitForGossipSubPeerInMesh(waku.relay));
}
if (protocols.includes(Protocols.Store)) {
if (!waku.store)
throw new Error("Cannot wait for Store peer: protocol not mounted");
promises.push(waitForConnectedPeer(waku.store, Object.values(StoreCodecs)));
}
if (protocols.includes(Protocols.LightPush)) {
if (!waku.lightPush)
throw new Error("Cannot wait for LightPush peer: protocol not mounted");
promises.push(waitForConnectedPeer(waku.lightPush, [LightPushCodec]));
}
if (protocols.includes(Protocols.Filter)) {
if (!waku.filter)
throw new Error("Cannot wait for Filter peer: protocol not mounted");
promises.push(waitForConnectedPeer(waku.filter, [FilterCodec]));
}
@ -130,3 +140,25 @@ async function rejectOnTimeout<T>(
): Promise<void> {
await Promise.race([promise, awaitTimeout(timeoutMs, rejectReason)]);
}
function getEnabledProtocols(waku: Waku): Protocols[] {
const protocols = [];
if (waku.relay) {
protocols.push(Protocols.Relay);
}
if (waku.filter) {
protocols.push(Protocols.Filter);
}
if (waku.store) {
protocols.push(Protocols.Store);
}
if (waku.lightPush) {
protocols.push(Protocols.LightPush);
}
return protocols;
}

View File

@ -8,11 +8,12 @@ import {
Nwaku,
} from "../test_utils/";
import { createWaku } from "./create_waku";
import { createLightNode, createPrivacyNode } from "./create_waku";
import { generateSymmetricKey } from "./crypto";
import type { Waku, WakuLight, WakuPrivacy } from "./interfaces";
import { PeerDiscoveryStaticPeers } from "./peer_discovery_static_list";
import { waitForRemotePeer } from "./wait_for_remote_peer";
import { Protocols, Waku } from "./waku";
import { Protocols } from "./waku";
import { WakuMessage } from "./waku_message";
const TestContentTopic = "/test/1/waku/utf8";
@ -30,15 +31,20 @@ describe("Waku Dial [node only]", function () {
it("connects to nwaku", async function () {
this.timeout(20_000);
nwaku = new Nwaku(makeLogFileName(this));
await nwaku.start();
await nwaku.start({
filter: true,
store: true,
lightpush: true,
persistMessages: true,
});
const multiAddrWithId = await nwaku.getMultiaddrWithId();
waku = await createWaku({
waku = await createLightNode({
staticNoiseKey: NOISE_KEY_1,
});
await waku.start();
await waku.dial(multiAddrWithId);
await waitForRemotePeer(waku, [Protocols.Relay]);
await waitForRemotePeer(waku);
const nimPeerId = await nwaku.getPeerId();
expect(await waku.libp2p.peerStore.has(nimPeerId)).to.be.true;
@ -46,7 +52,7 @@ describe("Waku Dial [node only]", function () {
});
describe("Bootstrap", function () {
let waku: Waku;
let waku: WakuLight;
let nwaku: Nwaku;
afterEach(async function () {
@ -60,7 +66,7 @@ describe("Waku Dial [node only]", function () {
nwaku = new Nwaku(makeLogFileName(this));
await nwaku.start();
const multiAddrWithId = await nwaku.getMultiaddrWithId();
waku = await createWaku({
waku = await createLightNode({
staticNoiseKey: NOISE_KEY_1,
libp2p: {
peerDiscovery: [new PeerDiscoveryStaticPeers([multiAddrWithId])],
@ -86,7 +92,7 @@ describe("Waku Dial [node only]", function () {
nwaku = new Nwaku(makeLogFileName(this));
await nwaku.start();
waku = await createWaku({
waku = await createLightNode({
staticNoiseKey: NOISE_KEY_1,
libp2p: {
peerDiscovery: [
@ -118,15 +124,15 @@ describe("Decryption Keys", () => {
}
});
let waku1: Waku;
let waku2: Waku;
let waku1: WakuPrivacy;
let waku2: WakuPrivacy;
beforeEach(async function () {
this.timeout(5000);
[waku1, waku2] = await Promise.all([
createWaku({ staticNoiseKey: NOISE_KEY_1 }).then((waku) =>
createPrivacyNode({ staticNoiseKey: NOISE_KEY_1 }).then((waku) =>
waku.start().then(() => waku)
),
createWaku({
createPrivacyNode({
staticNoiseKey: NOISE_KEY_2,
libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } },
}).then((waku) => waku.start().then(() => waku)),

View File

@ -2,7 +2,7 @@ import type { PeerId } from "@libp2p/interface-peer-id";
import { expect } from "chai";
import { createWaku } from "./create_waku";
import { Waku } from "./waku";
import type { Waku } from "./interfaces";
describe("Waku Dial", function () {
describe("Bootstrap [live data]", function () {

View File

@ -1,16 +1,19 @@
import type { Stream } from "@libp2p/interface-connection";
import type { PeerId } from "@libp2p/interface-peer-id";
import type { PubSub } from "@libp2p/interface-pubsub";
import { peerIdFromString } from "@libp2p/peer-id";
import type { Multiaddr } from "@multiformats/multiaddr";
import { multiaddr } from "@multiformats/multiaddr";
import debug from "debug";
import type { Libp2p } from "libp2p";
import { Waku } from "./interfaces";
import { FilterCodec, WakuFilter } from "./waku_filter";
import { LightPushCodec, WakuLightPush } from "./waku_light_push";
import { DecryptionMethod, WakuMessage } from "./waku_message";
import { WakuRelay } from "./waku_relay";
import { RelayCodecs, RelayPingContentTopic } from "./waku_relay/constants";
import * as relayConstants from "./waku_relay/constants";
import { StoreCodecs, WakuStore } from "./waku_store";
export const DefaultPingKeepAliveValueSecs = 0;
@ -43,12 +46,12 @@ export interface WakuOptions {
decryptionKeys?: Array<Uint8Array | string>;
}
export class Waku {
export class WakuNode implements Waku {
public libp2p: Libp2p;
public relay: WakuRelay;
public store: WakuStore;
public filter: WakuFilter;
public lightPush: WakuLightPush;
public relay?: WakuRelay;
public store?: WakuStore;
public filter?: WakuFilter;
public lightPush?: WakuLightPush;
private pingKeepAliveTimers: {
[peer: string]: ReturnType<typeof setInterval>;
@ -60,22 +63,27 @@ export class Waku {
constructor(
options: WakuOptions,
libp2p: Libp2p,
store: WakuStore,
lightPush: WakuLightPush,
filter: WakuFilter
store?: WakuStore,
lightPush?: WakuLightPush,
filter?: WakuFilter
) {
this.libp2p = libp2p;
this.relay = libp2p.pubsub as unknown as WakuRelay;
this.store = store;
this.filter = filter;
this.lightPush = lightPush;
if (isWakuRelay(libp2p.pubsub)) {
this.relay = libp2p.pubsub;
}
this.pingKeepAliveTimers = {};
this.relayKeepAliveTimers = {};
const pingKeepAlive =
options.pingKeepAlive || DefaultPingKeepAliveValueSecs;
const relayKeepAlive =
options.relayKeepAlive || DefaultRelayKeepAliveValueSecs;
const relayKeepAlive = this.relay
? options.relayKeepAlive || DefaultRelayKeepAliveValueSecs
: 0;
libp2p.connectionManager.addEventListener("peer:connect", (evt) => {
this.startKeepAlive(evt.detail.remotePeer, pingKeepAlive, relayKeepAlive);
@ -179,9 +187,9 @@ export class Waku {
key: Uint8Array | string,
options?: { method?: DecryptionMethod; contentTopics?: string[] }
): void {
this.relay.addDecryptionKey(key, options);
this.store.addDecryptionKey(key, options);
this.filter.addDecryptionKey(key, options);
if (this.relay) this.relay.addDecryptionKey(key, options);
if (this.store) this.store.addDecryptionKey(key, options);
if (this.filter) this.filter.addDecryptionKey(key, options);
}
/**
@ -191,9 +199,9 @@ export class Waku {
* Strings must be in hex format.
*/
deleteDecryptionKey(key: Uint8Array | string): void {
this.relay.deleteDecryptionKey(key);
this.store.deleteDecryptionKey(key);
this.filter.deleteDecryptionKey(key);
if (this.relay) this.relay.deleteDecryptionKey(key);
if (this.store) this.store.deleteDecryptionKey(key);
if (this.filter) this.filter.deleteDecryptionKey(key);
}
/**
@ -229,11 +237,12 @@ export class Waku {
}, pingPeriodSecs * 1000);
}
if (relayPeriodSecs !== 0) {
const relay = this.relay;
if (relay && relayPeriodSecs !== 0) {
this.relayKeepAliveTimers[peerIdStr] = setInterval(() => {
log("Sending Waku Relay ping message");
WakuMessage.fromBytes(new Uint8Array(), RelayPingContentTopic).then(
(wakuMsg) => this.relay.send(wakuMsg)
(wakuMsg) => relay.send(wakuMsg)
);
}, relayPeriodSecs * 1000);
}
@ -265,3 +274,16 @@ export class Waku {
this.relayKeepAliveTimers = {};
}
}
function isWakuRelay(pubsub: PubSub): pubsub is WakuRelay {
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;
}

View File

@ -3,9 +3,10 @@ import debug from "debug";
import { makeLogFileName, NOISE_KEY_1, Nwaku } from "../../test_utils";
import { delay } from "../../test_utils/delay";
import { createWaku } from "../create_waku";
import { createFullNode } from "../create_waku";
import type { WakuFull } from "../interfaces";
import { waitForRemotePeer } from "../wait_for_remote_peer";
import { Protocols, Waku } from "../waku";
import { Protocols } from "../waku";
import { WakuMessage } from "../waku_message";
const log = debug("waku:test");
@ -13,7 +14,7 @@ const log = debug("waku:test");
const TestContentTopic = "/test/1/waku-filter";
describe("Waku Filter", () => {
let waku: Waku;
let waku: WakuFull;
let nwaku: Nwaku;
afterEach(async function () {
@ -25,7 +26,7 @@ describe("Waku Filter", () => {
this.timeout(15000);
nwaku = new Nwaku(makeLogFileName(this));
await nwaku.start({ filter: true, lightpush: true });
waku = await createWaku({
waku = await createFullNode({
staticNoiseKey: NOISE_KEY_1,
libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } },
});
@ -46,7 +47,7 @@ describe("Waku Filter", () => {
expect(msg.payloadAsUtf8).to.eq(messageText);
};
await waku.filter.subscribe(callback, [TestContentTopic]);
// As the filter protocol does not cater for a ack of subscription
// As the filter protocol does not cater for an ack of subscription
// we cannot know whether the subscription happened. Something we want to
// correct in future versions of the protocol.
await delay(200);

View File

@ -3,9 +3,10 @@ import debug from "debug";
import { makeLogFileName, NOISE_KEY_1, Nwaku } from "../../test_utils";
import { delay } from "../../test_utils/delay";
import { createWaku } from "../create_waku";
import { createFullNode } from "../create_waku";
import type { WakuFull } from "../interfaces";
import { waitForRemotePeer } from "../wait_for_remote_peer";
import { Protocols, Waku } from "../waku";
import { Protocols } from "../waku";
import { WakuMessage } from "../waku_message";
const log = debug("waku:test:lightpush");
@ -13,7 +14,7 @@ const log = debug("waku:test:lightpush");
const TestContentTopic = "/test/1/waku-light-push/utf8";
describe("Waku Light Push [node only]", () => {
let waku: Waku;
let waku: WakuFull;
let nwaku: Nwaku;
afterEach(async function () {
@ -27,7 +28,7 @@ describe("Waku Light Push [node only]", () => {
nwaku = new Nwaku(makeLogFileName(this));
await nwaku.start({ lightpush: true });
waku = await createWaku({
waku = await createFullNode({
staticNoiseKey: NOISE_KEY_1,
});
await waku.start();
@ -63,7 +64,7 @@ describe("Waku Light Push [node only]", () => {
nwaku = new Nwaku(makeLogFileName(this));
await nwaku.start({ lightpush: true, topics: customPubSubTopic });
waku = await createWaku({
waku = await createFullNode({
pubSubTopic: customPubSubTopic,
staticNoiseKey: NOISE_KEY_1,
});

View File

@ -8,15 +8,16 @@ import {
WakuRelayMessage,
} from "../../test_utils";
import { delay } from "../../test_utils/delay";
import { createWaku } from "../create_waku";
import { createPrivacyNode } from "../create_waku";
import {
generatePrivateKey,
generateSymmetricKey,
getPublicKey,
} from "../crypto";
import type { WakuPrivacy } from "../interfaces";
import { bytesToHex, bytesToUtf8, hexToBytes, utf8ToBytes } from "../utils";
import { waitForRemotePeer } from "../wait_for_remote_peer";
import { Protocols, Waku } from "../waku";
import { Protocols } from "../waku";
import { DecryptionMethod, WakuMessage } from "./index";
@ -26,12 +27,12 @@ const TestContentTopic = "/test/1/waku-message/utf8";
describe("Waku Message [node only]", function () {
describe("Interop: nwaku", function () {
let waku: Waku;
let waku: WakuPrivacy;
let nwaku: Nwaku;
beforeEach(async function () {
this.timeout(30_000);
waku = await createWaku({
waku = await createPrivacyNode({
staticNoiseKey: NOISE_KEY_1,
});
await waku.start();

View File

@ -11,14 +11,15 @@ import {
} from "../../test_utils";
import { delay } from "../../test_utils/delay";
import { DefaultPubSubTopic } from "../constants";
import { createWaku } from "../create_waku";
import { createPrivacyNode } from "../create_waku";
import {
generatePrivateKey,
generateSymmetricKey,
getPublicKey,
} from "../crypto";
import type { WakuPrivacy } from "../interfaces";
import { waitForRemotePeer } from "../wait_for_remote_peer";
import { Protocols, Waku } from "../waku";
import { Protocols } from "../waku";
import { DecryptionMethod, WakuMessage } from "../waku_message";
const log = debug("waku:test");
@ -35,17 +36,17 @@ describe("Waku Relay [node only]", () => {
}
});
let waku1: Waku;
let waku2: Waku;
let waku1: WakuPrivacy;
let waku2: WakuPrivacy;
beforeEach(async function () {
this.timeout(10000);
log("Starting JS Waku instances");
[waku1, waku2] = await Promise.all([
createWaku({ staticNoiseKey: NOISE_KEY_1 }).then((waku) =>
createPrivacyNode({ staticNoiseKey: NOISE_KEY_1 }).then((waku) =>
waku.start().then(() => waku)
),
createWaku({
createPrivacyNode({
staticNoiseKey: NOISE_KEY_2,
libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } },
}).then((waku) => waku.start().then(() => waku)),
@ -258,9 +259,9 @@ describe("Waku Relay [node only]", () => {
});
describe("Custom pubsub topic", () => {
let waku1: Waku;
let waku2: Waku;
let waku3: Waku;
let waku1: WakuPrivacy;
let waku2: WakuPrivacy;
let waku3: WakuPrivacy;
afterEach(async function () {
!!waku1 &&
waku1.stop().catch((e) => console.log("Waku failed to stop", e));
@ -278,16 +279,16 @@ describe("Waku Relay [node only]", () => {
// 1 and 2 uses a custom pubsub
// 3 uses the default pubsub
[waku1, waku2, waku3] = await Promise.all([
createWaku({
createPrivacyNode({
pubSubTopic: pubSubTopic,
staticNoiseKey: NOISE_KEY_1,
}).then((waku) => waku.start().then(() => waku)),
createWaku({
createPrivacyNode({
pubSubTopic: pubSubTopic,
staticNoiseKey: NOISE_KEY_2,
libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } },
}).then((waku) => waku.start().then(() => waku)),
createWaku({
createPrivacyNode({
staticNoiseKey: NOISE_KEY_3,
}).then((waku) => waku.start().then(() => waku)),
]);
@ -337,12 +338,12 @@ describe("Waku Relay [node only]", () => {
});
describe("Interop: nwaku", function () {
let waku: Waku;
let waku: WakuPrivacy;
let nwaku: Nwaku;
beforeEach(async function () {
this.timeout(30_000);
waku = await createWaku({
waku = await createPrivacyNode({
staticNoiseKey: NOISE_KEY_1,
});
await waku.start();
@ -422,8 +423,8 @@ describe("Waku Relay [node only]", () => {
});
describe.skip("Two nodes connected to nwaku", function () {
let waku1: Waku;
let waku2: Waku;
let waku1: WakuPrivacy;
let waku2: WakuPrivacy;
let nwaku: Nwaku;
afterEach(async function () {
@ -437,10 +438,10 @@ describe("Waku Relay [node only]", () => {
it("Js publishes, other Js receives", async function () {
this.timeout(60_000);
[waku1, waku2] = await Promise.all([
createWaku({
createPrivacyNode({
staticNoiseKey: NOISE_KEY_1,
}).then((waku) => waku.start().then(() => waku)),
createWaku({
createPrivacyNode({
staticNoiseKey: NOISE_KEY_2,
}).then((waku) => waku.start().then(() => waku)),
]);

View File

@ -18,7 +18,7 @@ import * as constants from "./constants";
const log = debug("waku:relay");
export interface CreateOptions {
export type CreateOptions = {
/**
* The PubSub Topic to use. Defaults to {@link DefaultPubSubTopic}.
*
@ -34,7 +34,7 @@ export interface CreateOptions {
*/
pubSubTopic?: string;
decryptionKeys?: Array<Uint8Array | string>;
}
} & GossipsubOpts;
/**
* Implements the [Waku v2 Relay protocol](https://rfc.vac.dev/spec/11/).
@ -59,7 +59,7 @@ export class WakuRelay extends GossipSub {
[contentTopic: string]: Set<(message: WakuMessage) => void>;
};
constructor(options?: Partial<CreateOptions & GossipsubOpts>) {
constructor(options?: Partial<CreateOptions>) {
options = Object.assign(options ?? {}, {
// Ensure that no signature is included nor expected in the messages.
globalSignaturePolicy: SignaturePolicy.StrictNoSign,
@ -229,3 +229,5 @@ export class WakuRelay extends GossipSub {
return super.getMeshPeers(topic ?? this.pubSubTopic);
}
}
WakuRelay.multicodec = constants.RelayCodecs[constants.RelayCodecs.length - 1];

View File

@ -7,14 +7,15 @@ import {
NOISE_KEY_2,
Nwaku,
} from "../../test_utils";
import { createWaku } from "../create_waku";
import { createFullNode } from "../create_waku";
import {
generatePrivateKey,
generateSymmetricKey,
getPublicKey,
} from "../crypto";
import type { WakuFull } from "../interfaces";
import { waitForRemotePeer } from "../wait_for_remote_peer";
import { Protocols, Waku } from "../waku";
import { Protocols } from "../waku";
import { DecryptionMethod, WakuMessage } from "../waku_message";
import { PageDirection } from "./history_rpc";
@ -24,7 +25,7 @@ const log = debug("waku:test:store");
const TestContentTopic = "/test/1/waku-store/utf8";
describe("Waku Store", () => {
let waku: Waku;
let waku: WakuFull;
let nwaku: Nwaku;
afterEach(async function () {
@ -48,7 +49,7 @@ describe("Waku Store", () => {
).to.be.true;
}
waku = await createWaku({
waku = await createFullNode({
staticNoiseKey: NOISE_KEY_1,
});
await waku.start();
@ -81,7 +82,7 @@ describe("Waku Store", () => {
).to.be.true;
}
waku = await createWaku({
waku = await createFullNode({
staticNoiseKey: NOISE_KEY_1,
});
await waku.start();
@ -121,7 +122,7 @@ describe("Waku Store", () => {
).to.be.true;
}
waku = await createWaku({
waku = await createFullNode({
staticNoiseKey: NOISE_KEY_1,
});
await waku.start();
@ -158,7 +159,7 @@ describe("Waku Store", () => {
).to.be.true;
}
waku = await createWaku({
waku = await createFullNode({
staticNoiseKey: NOISE_KEY_1,
});
await waku.start();
@ -201,7 +202,7 @@ describe("Waku Store", () => {
).to.be.true;
}
waku = await createWaku({
waku = await createFullNode({
pubSubTopic: customPubSubTopic,
staticNoiseKey: NOISE_KEY_1,
});
@ -268,10 +269,10 @@ describe("Waku Store", () => {
log("Messages have been encrypted");
const [waku1, waku2, nimWakuMultiaddr] = await Promise.all([
createWaku({
createFullNode({
staticNoiseKey: NOISE_KEY_1,
}).then((waku) => waku.start().then(() => waku)),
createWaku({
createFullNode({
staticNoiseKey: NOISE_KEY_2,
}).then((waku) => waku.start().then(() => waku)),
nwaku.getMultiaddrWithId(),
@ -370,10 +371,10 @@ describe("Waku Store", () => {
log("Messages have been encrypted");
const [waku1, waku2, nimWakuMultiaddr] = await Promise.all([
createWaku({
createFullNode({
staticNoiseKey: NOISE_KEY_1,
}).then((waku) => waku.start().then(() => waku)),
createWaku({
createFullNode({
staticNoiseKey: NOISE_KEY_2,
}).then((waku) => waku.start().then(() => waku)),
nwaku.getMultiaddrWithId(),
@ -457,7 +458,7 @@ describe("Waku Store", () => {
).to.be.true;
}
waku = await createWaku({
waku = await createFullNode({
staticNoiseKey: NOISE_KEY_1,
});
await waku.start();

View File

@ -2,6 +2,7 @@
"entryPoints": [
"./src/index.ts",
"./src/lib/create_waku.ts",
"./src/lib/interfaces.ts",
"./src/lib/peer_discovery_dns.ts",
"./src/lib/peer_discovery_static_list.ts",
"./src/lib/predefined_bootstrap_nodes.ts",