mirror of https://github.com/status-im/js-waku.git
Implement libp2p bootstrap interface
This commit is contained in:
parent
081f62b07b
commit
4cf197e54d
|
@ -14,7 +14,10 @@
|
||||||
],
|
],
|
||||||
"globals": { "BigInt": true, "console": true, "WebAssembly": true },
|
"globals": { "BigInt": true, "console": true, "WebAssembly": true },
|
||||||
"rules": {
|
"rules": {
|
||||||
"@typescript-eslint/explicit-function-return-type": ["error"],
|
"@typescript-eslint/explicit-function-return-type": [
|
||||||
|
"error",
|
||||||
|
{ "allowExpressions": true }
|
||||||
|
],
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||||
"eslint-comments/disable-enable-pair": [
|
"eslint-comments/disable-enable-pair": [
|
||||||
"error",
|
"error",
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -69,10 +69,14 @@
|
||||||
"@chainsafe/libp2p-gossipsub": "^2.0.0",
|
"@chainsafe/libp2p-gossipsub": "^2.0.0",
|
||||||
"@chainsafe/libp2p-noise": "^7.0.0",
|
"@chainsafe/libp2p-noise": "^7.0.0",
|
||||||
"@ethersproject/rlp": "^5.5.0",
|
"@ethersproject/rlp": "^5.5.0",
|
||||||
|
"@libp2p/interface-peer-discovery": "^1.0.0",
|
||||||
"@libp2p/interface-peer-id": "^1.0.2",
|
"@libp2p/interface-peer-id": "^1.0.2",
|
||||||
|
"@libp2p/interface-peer-info": "^1.0.1",
|
||||||
"@libp2p/interface-peer-store": "^1.0.0",
|
"@libp2p/interface-peer-store": "^1.0.0",
|
||||||
"@libp2p/interfaces": "^3.0.2",
|
"@libp2p/interfaces": "^3.0.2",
|
||||||
|
"@libp2p/mplex": "^3.0.0",
|
||||||
"@libp2p/peer-id": "^1.1.10",
|
"@libp2p/peer-id": "^1.1.10",
|
||||||
|
"@libp2p/websockets": "^3.0.0",
|
||||||
"@multiformats/multiaddr": "^10.2.0",
|
"@multiformats/multiaddr": "^10.2.0",
|
||||||
"@noble/secp256k1": "^1.3.4",
|
"@noble/secp256k1": "^1.3.4",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
|
@ -83,10 +87,7 @@
|
||||||
"it-pipe": "^1.1.0",
|
"it-pipe": "^1.1.0",
|
||||||
"js-sha3": "^0.8.0",
|
"js-sha3": "^0.8.0",
|
||||||
"libp2p": "^0.37.3",
|
"libp2p": "^0.37.3",
|
||||||
"libp2p-bootstrap": "^0.14.0",
|
|
||||||
"libp2p-crypto": "^0.21.2",
|
"libp2p-crypto": "^0.21.2",
|
||||||
"libp2p-mplex": "^0.10.4",
|
|
||||||
"libp2p-websockets": "^0.16.1",
|
|
||||||
"multiformats": "^9.6.5",
|
"multiformats": "^9.6.5",
|
||||||
"protons-runtime": "^1.0.4",
|
"protons-runtime": "^1.0.4",
|
||||||
"uint8arrays": "^3.0.0",
|
"uint8arrays": "^3.0.0",
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
|
import type {
|
||||||
|
PeerDiscovery,
|
||||||
|
PeerDiscoveryEvents,
|
||||||
|
} from "@libp2p/interface-peer-discovery";
|
||||||
|
import { symbol } from "@libp2p/interface-peer-discovery";
|
||||||
|
import type { PeerInfo } from "@libp2p/interface-peer-info";
|
||||||
|
import { CustomEvent, EventEmitter } from "@libp2p/interfaces/events";
|
||||||
|
import { peerIdFromString } from "@libp2p/peer-id/src";
|
||||||
import { Multiaddr } from "@multiformats/multiaddr";
|
import { Multiaddr } from "@multiformats/multiaddr";
|
||||||
import debug from "debug";
|
import debug from "debug";
|
||||||
|
|
||||||
|
@ -5,7 +13,7 @@ import { DnsNodeDiscovery, NodeCapabilityCount } from "./dns";
|
||||||
import { getPredefinedBootstrapNodes } from "./predefined";
|
import { getPredefinedBootstrapNodes } from "./predefined";
|
||||||
import { getPseudoRandomSubset } from "./random_subset";
|
import { getPseudoRandomSubset } from "./random_subset";
|
||||||
|
|
||||||
const dbg = debug("waku:discovery:bootstrap");
|
const log = debug("waku:discovery:bootstrap");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup discovery method used to bootstrap.
|
* Setup discovery method used to bootstrap.
|
||||||
|
@ -35,8 +43,14 @@ export interface BootstrapOptions {
|
||||||
peers?: string[] | Multiaddr[];
|
peers?: string[] | Multiaddr[];
|
||||||
/**
|
/**
|
||||||
* Getter that retrieve multiaddrs of peers to connect to.
|
* Getter that retrieve multiaddrs of peers to connect to.
|
||||||
|
* will be called once.
|
||||||
*/
|
*/
|
||||||
getPeers?: () => Promise<string[] | Multiaddr[]>;
|
getPeers?: () => Promise<string[] | Multiaddr[]>;
|
||||||
|
/**
|
||||||
|
* The interval between emitting addresses in milliseconds.
|
||||||
|
* Used if [[peers]] is passed or a sync function is passed for [[getPeers]]
|
||||||
|
*/
|
||||||
|
interval?: number;
|
||||||
/**
|
/**
|
||||||
* An EIP-1459 ENR Tree URL. For example:
|
* An EIP-1459 ENR Tree URL. For example:
|
||||||
* "enrtree://AOFTICU2XWDULNLZGRMQS4RIZPAZEHYMV4FYHAPW563HNRAOERP7C@test.nodes.vac.dev"
|
* "enrtree://AOFTICU2XWDULNLZGRMQS4RIZPAZEHYMV4FYHAPW563HNRAOERP7C@test.nodes.vac.dev"
|
||||||
|
@ -56,23 +70,50 @@ export interface BootstrapOptions {
|
||||||
*
|
*
|
||||||
* @throws if an invalid combination of options is passed, see [[BootstrapOptions]] for details.
|
* @throws if an invalid combination of options is passed, see [[BootstrapOptions]] for details.
|
||||||
*/
|
*/
|
||||||
export class Bootstrap {
|
export class Bootstrap
|
||||||
public static DefaultMaxPeers = 1;
|
extends EventEmitter<PeerDiscoveryEvents>
|
||||||
|
implements PeerDiscovery
|
||||||
|
{
|
||||||
|
static DefaultMaxPeers = 1;
|
||||||
|
|
||||||
public readonly getBootstrapPeers: (() => Promise<Multiaddr[]>) | undefined;
|
private readonly asyncGetBootstrapPeers:
|
||||||
|
| (() => Promise<Multiaddr[]>)
|
||||||
|
| undefined;
|
||||||
|
private peers: PeerInfo[];
|
||||||
|
private timer?: ReturnType<typeof setInterval>;
|
||||||
|
private readonly interval: number;
|
||||||
|
|
||||||
constructor(opts: BootstrapOptions) {
|
constructor(opts: BootstrapOptions) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
const methods = [
|
||||||
|
!!opts.default,
|
||||||
|
!!opts.peers,
|
||||||
|
!!opts.getPeers,
|
||||||
|
!!opts.enrUrl,
|
||||||
|
].filter((x) => x);
|
||||||
|
if (methods.length > 1) {
|
||||||
|
throw new Error(
|
||||||
|
"Bootstrap does not support several discovery methods (yet)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.interval = opts.interval ?? 10000;
|
||||||
|
opts.default =
|
||||||
|
opts.default ?? (!opts.peers && !opts.getPeers && !opts.enrUrl);
|
||||||
const maxPeers = opts.maxPeers ?? Bootstrap.DefaultMaxPeers;
|
const maxPeers = opts.maxPeers ?? Bootstrap.DefaultMaxPeers;
|
||||||
|
this.peers = [];
|
||||||
|
|
||||||
if (opts.default) {
|
if (opts.default) {
|
||||||
dbg("Use hosted list of peers.");
|
log("Use hosted list of peers.");
|
||||||
|
|
||||||
this.getBootstrapPeers = (): Promise<Multiaddr[]> => {
|
this.peers = multiaddrsToPeerInfo(
|
||||||
return Promise.resolve(
|
getPredefinedBootstrapNodes(undefined, maxPeers)
|
||||||
getPredefinedBootstrapNodes(undefined, maxPeers)
|
);
|
||||||
);
|
return;
|
||||||
};
|
}
|
||||||
} else if (opts.peers !== undefined && opts.peers.length > 0) {
|
|
||||||
|
if (!!opts.peers && opts.peers.length > 0) {
|
||||||
const allPeers: Multiaddr[] = opts.peers.map(
|
const allPeers: Multiaddr[] = opts.peers.map(
|
||||||
(node: string | Multiaddr) => {
|
(node: string | Multiaddr) => {
|
||||||
if (typeof node === "string") {
|
if (typeof node === "string") {
|
||||||
|
@ -82,41 +123,121 @@ export class Bootstrap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const peers = getPseudoRandomSubset(allPeers, maxPeers);
|
this.peers = multiaddrsToPeerInfo(
|
||||||
dbg(
|
getPseudoRandomSubset(allPeers, maxPeers)
|
||||||
"Use provided list of peers (reduced to maxPeers)",
|
|
||||||
allPeers.map((ma) => ma.toString())
|
|
||||||
);
|
);
|
||||||
this.getBootstrapPeers = (): Promise<Multiaddr[]> =>
|
log(
|
||||||
Promise.resolve(peers);
|
"Use provided list of peers (reduced to maxPeers)",
|
||||||
} else if (typeof opts.getPeers === "function") {
|
this.peers.map((ma) => ma.toString())
|
||||||
dbg("Bootstrap: Use provided getPeers function.");
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof opts.getPeers === "function") {
|
||||||
|
log("Bootstrap: Use provided getPeers function.");
|
||||||
const getPeers = opts.getPeers;
|
const getPeers = opts.getPeers;
|
||||||
|
|
||||||
this.getBootstrapPeers = async (): Promise<Multiaddr[]> => {
|
this.asyncGetBootstrapPeers = async () => {
|
||||||
const allPeers = await getPeers();
|
const allPeers = await getPeers();
|
||||||
return getPseudoRandomSubset<string | Multiaddr>(
|
return getPseudoRandomSubset<string | Multiaddr>(
|
||||||
allPeers,
|
allPeers,
|
||||||
maxPeers
|
maxPeers
|
||||||
).map((node) => new Multiaddr(node));
|
).map((node) => new Multiaddr(node));
|
||||||
};
|
};
|
||||||
} else if (opts.enrUrl) {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.enrUrl) {
|
||||||
const wantedNodeCapabilityCount = opts.wantedNodeCapabilityCount;
|
const wantedNodeCapabilityCount = opts.wantedNodeCapabilityCount;
|
||||||
if (!wantedNodeCapabilityCount)
|
if (!wantedNodeCapabilityCount)
|
||||||
throw "`wantedNodeCapabilityCount` must be defined when using `enrUrl`";
|
throw "`wantedNodeCapabilityCount` must be defined when using `enrUrl`";
|
||||||
const enrUrl = opts.enrUrl;
|
const enrUrl = opts.enrUrl;
|
||||||
dbg("Use provided EIP-1459 ENR Tree URL.");
|
log("Use provided EIP-1459 ENR Tree URL.");
|
||||||
|
|
||||||
const dns = DnsNodeDiscovery.dnsOverHttp();
|
const dns = DnsNodeDiscovery.dnsOverHttp();
|
||||||
|
|
||||||
this.getBootstrapPeers = async (): Promise<Multiaddr[]> => {
|
this.asyncGetBootstrapPeers = async () => {
|
||||||
const enrs = await dns.getPeers([enrUrl], wantedNodeCapabilityCount);
|
const enrs = await dns.getPeers([enrUrl], wantedNodeCapabilityCount);
|
||||||
dbg(`Found ${enrs.length} peers`);
|
log(`Found ${enrs.length} peers`);
|
||||||
return enrs.map((enr) => enr.getFullMultiaddrs()).flat();
|
return enrs.map((enr) => enr.getFullMultiaddrs()).flat();
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
dbg("No bootstrap method specified, no peer will be returned");
|
return;
|
||||||
this.getBootstrapPeers = undefined;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start discovery process
|
||||||
|
*/
|
||||||
|
start(): void {
|
||||||
|
if (this.asyncGetBootstrapPeers) {
|
||||||
|
// TODO: This should emit the peer as they are discovered instead of having
|
||||||
|
// to wait for the full DNS discovery process to be done first.
|
||||||
|
// TODO: PeerInfo should be returned by discovery
|
||||||
|
this.asyncGetBootstrapPeers().then((peers) => {
|
||||||
|
this.peers = multiaddrsToPeerInfo(peers);
|
||||||
|
this._startTimer();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this._startTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _startTimer(): void {
|
||||||
|
if (this.peers) {
|
||||||
|
log("Starting bootstrap node discovery");
|
||||||
|
if (this.timer != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.timer = setInterval(() => this._returnPeers(), this.interval);
|
||||||
|
|
||||||
|
this._returnPeers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_returnPeers(): void {
|
||||||
|
if (this.timer == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.peers.forEach((peerData) => {
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent<PeerInfo>("peer", { detail: peerData })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop emitting events
|
||||||
|
*/
|
||||||
|
stop(): void {
|
||||||
|
if (this.timer != null) {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.timer = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
get [symbol](): true {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get [Symbol.toStringTag](): string {
|
||||||
|
return "@waku/bootstrap";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function multiaddrsToPeerInfo(mas: Multiaddr[]): PeerInfo[] {
|
||||||
|
return mas
|
||||||
|
.map((ma) => {
|
||||||
|
const peerIdStr = ma.getPeerId();
|
||||||
|
const protocols: string[] = [];
|
||||||
|
return {
|
||||||
|
id: peerIdStr ? peerIdFromString(peerIdStr) : null,
|
||||||
|
multiaddrs: [ma.decapsulateCode(421)],
|
||||||
|
protocols,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((peerInfo): peerInfo is PeerInfo => peerInfo.id !== null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,13 @@
|
||||||
import { Noise } from "@chainsafe/libp2p-noise";
|
import { Noise } from "@chainsafe/libp2p-noise";
|
||||||
import type { PeerId } from "@libp2p/interface-peer-id";
|
import type { PeerId } from "@libp2p/interface-peer-id";
|
||||||
|
import { MuxedStream } from "@libp2p/interfaces/stream-muxer/types";
|
||||||
|
import { Mplex } from "@libp2p/mplex";
|
||||||
|
import { WebSockets } from "@libp2p/websockets";
|
||||||
|
import filters from "@libp2p/websockets/filters";
|
||||||
import { Multiaddr, multiaddr } from "@multiformats/multiaddr";
|
import { Multiaddr, multiaddr } from "@multiformats/multiaddr";
|
||||||
import debug from "debug";
|
import debug from "debug";
|
||||||
import { createLibp2p, Libp2p, Libp2pOptions } from "libp2p";
|
import { createLibp2p, Libp2p, Libp2pOptions } from "libp2p";
|
||||||
import Libp2pBootstrap from "libp2p-bootstrap";
|
import Libp2pBootstrap from "libp2p-bootstrap";
|
||||||
import { MuxedStream } from "libp2p-interfaces/dist/src/stream-muxer/types";
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: No types available
|
|
||||||
import Mplex from "libp2p-mplex";
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: No types available
|
|
||||||
import Websockets from "libp2p-websockets";
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: No types available
|
|
||||||
import filters from "libp2p-websockets/src/filters";
|
|
||||||
import PingService from "libp2p/src/ping";
|
import PingService from "libp2p/src/ping";
|
||||||
|
|
||||||
import { Bootstrap, BootstrapOptions } from "./discovery";
|
import { Bootstrap, BootstrapOptions } from "./discovery";
|
||||||
|
@ -24,8 +18,6 @@ import { WakuRelay } from "./waku_relay";
|
||||||
import { RelayCodecs, RelayPingContentTopic } from "./waku_relay/constants";
|
import { RelayCodecs, RelayPingContentTopic } from "./waku_relay/constants";
|
||||||
import { StoreCodecs, WakuStore } from "./waku_store";
|
import { StoreCodecs, WakuStore } from "./waku_store";
|
||||||
|
|
||||||
const websocketsTransportKey = Websockets.prototype[Symbol.toStringTag];
|
|
||||||
|
|
||||||
export const DefaultPingKeepAliveValueSecs = 0;
|
export const DefaultPingKeepAliveValueSecs = 0;
|
||||||
export const DefaultRelayKeepAliveValueSecs = 5 * 60;
|
export const DefaultRelayKeepAliveValueSecs = 5 * 60;
|
||||||
|
|
||||||
|
@ -93,6 +85,35 @@ export interface CreateOptions {
|
||||||
decryptionKeys?: Array<Uint8Array | string>;
|
decryptionKeys?: Array<Uint8Array | string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createWaku(): Promise<Waku> {
|
||||||
|
const libp2pOpts = {
|
||||||
|
transports: new WebSockets({ filter: filters.all }),
|
||||||
|
streamMuxers: [new Mplex()],
|
||||||
|
pubsub: new WakuRelay(),
|
||||||
|
connectionEncryption: [new Noise()],
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: modules property is correctly set thanks to voodoo
|
||||||
|
const libp2p = await createLibp2p(libp2pOpts);
|
||||||
|
|
||||||
|
const wakuStore = new WakuStore(libp2p, {
|
||||||
|
pubSubTopic: options?.pubSubTopic,
|
||||||
|
});
|
||||||
|
const wakuLightPush = new WakuLightPush(libp2p);
|
||||||
|
const wakuFilter = new WakuFilter(libp2p);
|
||||||
|
|
||||||
|
await libp2p.start();
|
||||||
|
|
||||||
|
return new Waku(
|
||||||
|
options ? options : {},
|
||||||
|
libp2p,
|
||||||
|
wakuStore,
|
||||||
|
wakuLightPush,
|
||||||
|
wakuFilter
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export class Waku {
|
export class Waku {
|
||||||
public libp2p: Libp2p;
|
public libp2p: Libp2p;
|
||||||
public relay: WakuRelay;
|
public relay: WakuRelay;
|
||||||
|
|
Loading…
Reference in New Issue