Merge pull request #262 from status-im/bootstrap

This commit is contained in:
Franck Royer 2021-08-13 16:27:32 +10:00 committed by GitHub
commit 5be718ffdb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 311 additions and 143 deletions

View File

@ -47,6 +47,7 @@
"livechat", "livechat",
"mkdir", "mkdir",
"multiaddr", "multiaddr",
"multiaddresses",
"multiaddrs", "multiaddrs",
"multicodecs", "multicodecs",
"mplex", "mplex",

View File

@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added
- New `bootstrap` option for `Waku.create` to easily connect to Waku nodes upon start up.
### Changed
- Renamed `discover.getStatusFleetNodes` to `discovery.getBootstrapNodes`;
Changed the API to allow retrieval of bootstrap nodes from other sources.
### Removed ### Removed
- Examples (cli-chat): The focus of this library is Web environment; - Examples (cli-chat): The focus of this library is Web environment;
Several examples now cover usage of Waku Relay and Waku Store making cli-chat example obsolete; Several examples now cover usage of Waku Relay and Waku Store making cli-chat example obsolete;

View File

@ -32,32 +32,7 @@ npm install js-waku
```ts ```ts
import { Waku } from 'js-waku'; import { Waku } from 'js-waku';
const waku = await Waku.create(); const waku = await Waku.create({ bootstrap: true });
```
### Connect to a new peer
```ts
// Directly dial a new peer
await waku.dial('/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ');
// Or, add peer to address book so it auto dials in the background
waku.addPeerToAddressBook(
'16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ',
['/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss']
);
```
You can also use `getStatusFleetNodes` to connect to nodes run by Status:
```ts
import { getStatusFleetNodes } from 'js-waku';
getStatusFleetNodes().then((nodes) => {
nodes.forEach((addr) => {
waku.dial(addr);
});
});
``` ```
### Listen for messages ### Listen for messages

View File

@ -1,5 +1,5 @@
import { Dispatch, SetStateAction } from 'react'; import { Dispatch, SetStateAction } from 'react';
import { getStatusFleetNodes, Waku, WakuMessage } from 'js-waku'; import { Waku, WakuMessage } from 'js-waku';
import { DirectMessage, PublicKeyMessage } from './messaging/wire'; import { DirectMessage, PublicKeyMessage } from './messaging/wire';
import { validatePublicKeyMessage } from './crypto'; import { validatePublicKeyMessage } from './crypto';
import { Message } from './messaging/Messages'; import { Message } from './messaging/Messages';
@ -9,14 +9,7 @@ export const PublicKeyContentTopic = '/eth-dm/1/public-key/proto';
export const DirectMessageContentTopic = '/eth-dm/1/direct-message/proto'; export const DirectMessageContentTopic = '/eth-dm/1/direct-message/proto';
export async function initWaku(): Promise<Waku> { export async function initWaku(): Promise<Waku> {
const waku = await Waku.create({}); const waku = await Waku.create({ bootstrap: true });
// Dial all nodes it can find
getStatusFleetNodes().then((nodes) => {
nodes.forEach((addr) => {
waku.dial(addr);
});
});
// Wait to be connected to at least one peer // Wait to be connected to at least one peer
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {

View File

@ -1,5 +1,5 @@
import { Dispatch, SetStateAction } from 'react'; import { Dispatch, SetStateAction } from 'react';
import { getStatusFleetNodes, Waku, WakuMessage } from 'js-waku'; import { Waku, WakuMessage } from 'js-waku';
import { DirectMessage, PublicKeyMessage } from './messaging/wire'; import { DirectMessage, PublicKeyMessage } from './messaging/wire';
import { validatePublicKeyMessage } from './crypto'; import { validatePublicKeyMessage } from './crypto';
import { Message } from './messaging/Messages'; import { Message } from './messaging/Messages';
@ -11,14 +11,7 @@ export const DirectMessageContentTopic =
'/eth-pm-wallet/1/direct-message/proto'; '/eth-pm-wallet/1/direct-message/proto';
export async function initWaku(): Promise<Waku> { export async function initWaku(): Promise<Waku> {
const waku = await Waku.create({}); const waku = await Waku.create({ bootstrap: true });
// Dial all nodes it can find
getStatusFleetNodes().then((nodes) => {
nodes.forEach((addr) => {
waku.dial(addr);
});
});
// Wait to be connected to at least one peer // Wait to be connected to at least one peer
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {

View File

@ -1,5 +1,5 @@
import './App.css'; import './App.css';
import { getStatusFleetNodes, Waku, WakuMessage } from 'js-waku'; import { getBootstrapNodes, Waku, WakuMessage } from 'js-waku';
import * as React from 'react'; import * as React from 'react';
import protons from 'protons'; import protons from 'protons';
@ -96,8 +96,12 @@ function App() {
export default App; export default App;
async function bootstrapWaku(waku) { async function bootstrapWaku(waku) {
const nodes = await getStatusFleetNodes(); try {
const nodes = await getBootstrapNodes();
await Promise.all(nodes.map((addr) => waku.dial(addr))); await Promise.all(nodes.map((addr) => waku.dial(addr)));
} catch (e) {
console.error('Failed to bootstrap to Waku network');
}
} }
async function sendMessage(message, timestamp, waku) { async function sendMessage(message, timestamp, waku) {

View File

@ -1,5 +1,5 @@
import './App.css'; import './App.css';
import { getStatusFleetNodes, StoreCodec, Waku } from 'js-waku'; import { StoreCodec, Waku } from 'js-waku';
import * as React from 'react'; import * as React from 'react';
import protons from 'protons'; import protons from 'protons';
@ -17,9 +17,6 @@ function App() {
const [waku, setWaku] = React.useState(undefined); const [waku, setWaku] = React.useState(undefined);
const [wakuStatus, setWakuStatus] = React.useState('None'); const [wakuStatus, setWakuStatus] = React.useState('None');
const [messages, setMessages] = React.useState([]); const [messages, setMessages] = React.useState([]);
// Set to true when Waku connects to a store node
// it does not reflect whether we then disconnected from said node.
const [connectedToStore, setConnectedToStore] = React.useState(false);
React.useEffect(() => { React.useEffect(() => {
if (!!waku) return; if (!!waku) return;
@ -27,20 +24,15 @@ function App() {
setWakuStatus('Starting'); setWakuStatus('Starting');
Waku.create().then((waku) => { Waku.create({ bootstrap: true }).then((waku) => {
setWaku(waku); setWaku(waku);
setWakuStatus('Connecting'); setWakuStatus('Connecting');
bootstrapWaku(waku).then(() => {
setWakuStatus('Ready');
});
}); });
}, [waku, wakuStatus]); }, [waku, wakuStatus]);
React.useEffect(() => { React.useEffect(() => {
if (!waku) return; if (!waku) return;
// This is superfluous as the try/catch block would catch the failure if if (wakuStatus !== 'Connected to Store') return;
// we are indeed not connected to any store node.
if (!connectedToStore) return;
const interval = setInterval(() => { const interval = setInterval(() => {
waku.store waku.store
@ -57,33 +49,27 @@ function App() {
}, 10000); }, 10000);
return () => clearInterval(interval); return () => clearInterval(interval);
}, [waku, connectedToStore]); }, [waku, wakuStatus]);
React.useEffect(() => { React.useEffect(() => {
if (!waku) return; if (!waku) return;
// We do not handle disconnection/re-connection in this example // We do not handle disconnection/re-connection in this example
if (connectedToStore) return; if (wakuStatus === 'Connected to Store') return;
const isStoreNode = ({ protocols }) => { const isStoreNode = ({ protocols }) => {
if (protocols.includes(StoreCodec)) { if (protocols.includes(StoreCodec)) {
// We are now connected to a store node // We are now connected to a store node
setConnectedToStore(true); setWakuStatus('Connected to Store');
} }
}; };
// This demonstrates how to wait for a connection to a store node.
//
// This is only for demonstration purposes. It is not really needed in this
// example app as we query the store node every 10s and catch if it fails.
// Meaning if we are not connected to a store node, then it just fails and
// we try again 10s later.
waku.libp2p.peerStore.on('change:protocols', isStoreNode); waku.libp2p.peerStore.on('change:protocols', isStoreNode);
return () => { return () => {
waku.libp2p.peerStore.removeListener('change:protocols', isStoreNode); waku.libp2p.peerStore.removeListener('change:protocols', isStoreNode);
}; };
}, [waku, connectedToStore]); }, [waku, wakuStatus]);
return ( return (
<div className="App"> <div className="App">
@ -100,11 +86,6 @@ function App() {
export default App; export default App;
async function bootstrapWaku(waku) {
const nodes = await getStatusFleetNodes();
await Promise.all(nodes.map((addr) => waku.dial(addr)));
}
function decodeMessage(wakuMessage) { function decodeMessage(wakuMessage) {
if (!wakuMessage.payload) return; if (!wakuMessage.payload) return;

View File

@ -2,8 +2,7 @@ import { useEffect, useReducer, useState } from 'react';
import './App.css'; import './App.css';
import { import {
Direction, Direction,
Environment, getBootstrapNodes,
getStatusFleetNodes,
StoreCodec, StoreCodec,
Waku, Waku,
WakuMessage, WakuMessage,
@ -182,16 +181,10 @@ async function initWaku(setter: (waku: Waku) => void) {
}, },
}, },
}, },
bootstrap: getBootstrapNodes.bind({}, selectFleetEnv()),
}); });
setter(waku); setter(waku);
const nodes = await getStatusFleetNodes(selectFleetEnv());
await Promise.all(
nodes.map((addr) => {
return waku.dial(addr);
})
);
} catch (e) { } catch (e) {
console.log('Issue starting waku ', e); console.log('Issue starting waku ', e);
} }
@ -200,9 +193,9 @@ async function initWaku(setter: (waku: Waku) => void) {
function selectFleetEnv() { function selectFleetEnv() {
// Works with react-scripts // Works with react-scripts
if (process?.env?.NODE_ENV === 'development') { if (process?.env?.NODE_ENV === 'development') {
return Environment.Test; return ['fleets', 'wakuv2.test', 'waku-websocket'];
} else { } else {
return Environment.Prod; return ['fleets', 'wakuv2.prod', 'waku-websocket'];
} }
} }

View File

@ -82,16 +82,18 @@ function App() {
# Connect to Other Peers # Connect to Other Peers
The Waku instance needs to connect to other peers to communicate with the network. The Waku instance needs to connect to other peers to communicate with the network.
First, create `bootstrapWaku` to connect to the Status fleet: First, create `bootstrapWaku` to connect to Waku bootstrap nodes:
```js ```js
import { getStatusFleetNodes } from 'js-waku'; import { getBootstrapNodes } from 'js-waku';
async function bootstrapWaku(waku) { async function bootstrapWaku(waku) {
// Retrieve node addresses from https://fleets.status.im/ try {
const nodes = await getStatusFleetNodes(); const nodes = await getBootstrapNodes();
// Connect to the nodes
await Promise.all(nodes.map((addr) => waku.dial(addr))); await Promise.all(nodes.map((addr) => waku.dial(addr)));
} catch (e) {
console.error('Failed to bootstrap to Waku network');
}
} }
``` ```

View File

@ -34,8 +34,9 @@ You are free to choose any method to bootstrap and DappConnect will ship with ne
For now, the easiest way is to connect to Status' Waku fleet: For now, the easiest way is to connect to Status' Waku fleet:
```js ```js
import { getStatusFleetNodes } from 'js-waku'; import { getBootstrapNodes } from 'js-waku';
const nodes = await getStatusFleetNodes();
const nodes = await getBootstrapNodes();
await Promise.all(nodes.map((addr) => waku.dial(addr))); await Promise.all(nodes.map((addr) => waku.dial(addr)));
``` ```
@ -177,7 +178,7 @@ Feel free to check out other [guides](menu.md) or [examples](/examples/examples.
Here is the final code: Here is the final code:
```js ```js
import { getStatusFleetNodes, Waku, WakuMessage } from 'js-waku'; import { getBootstrapNodes, Waku, WakuMessage } from 'js-waku';
import protons from 'protons'; import protons from 'protons';
const proto = protons(` const proto = protons(`
@ -189,7 +190,7 @@ message SimpleChatMessage {
const wakuNode = await Waku.create(); const wakuNode = await Waku.create();
const nodes = await getStatusFleetNodes(); const nodes = await getBootstrapNodes();
await Promise.all(nodes.map((addr) => waku.dial(addr))); await Promise.all(nodes.map((addr) => waku.dial(addr)));
const processIncomingMessage = (wakuMessage) => { const processIncomingMessage = (wakuMessage) => {

View File

@ -38,20 +38,21 @@ In order to interact with the Waku network, you first need a Waku instance:
```js ```js
import { Waku } from 'js-waku'; import { Waku } from 'js-waku';
const wakuNode = await Waku.create(); const wakuNode = await Waku.create({ bootstrap: true });
``` ```
# Connect to Other Peers Passing the `bootstrap` option will connect your node to predefined Waku nodes.
If you want to bootstrap to your own nodes, you can pass an array of multiaddresses instead:
The Waku instance needs to connect to other peers to communicate with the network.
You are free to choose other methods to bootstrap and DappConnect will ship with new bootstrap mechanisms in the future.
For now, the easiest way is to connect to Status' Waku fleet:
```js ```js
import { getStatusFleetNodes } from 'js-waku'; import { Waku } from 'js-waku';
const nodes = await getStatusFleetNodes();
await Promise.all(nodes.map((addr) => waku.dial(addr))); const wakuNode = await Waku.create({
bootstrap: [
'/dns4/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAkvWiyFsgRhuJEb9JfjYxEkoHLgnUQmr1N5mKWnYjxYRVm',
'/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ'
]
});
``` ```
# Use Protobuf # Use Protobuf

60
package-lock.json generated
View File

@ -15,6 +15,7 @@
"it-length-prefixed": "^5.0.2", "it-length-prefixed": "^5.0.2",
"js-sha3": "^0.8.0", "js-sha3": "^0.8.0",
"libp2p": "^0.32.0", "libp2p": "^0.32.0",
"libp2p-bootstrap": "^0.13.0",
"libp2p-gossipsub": "^0.10.0", "libp2p-gossipsub": "^0.10.0",
"libp2p-mplex": "^0.10.4", "libp2p-mplex": "^0.10.4",
"libp2p-noise": "^4.0.0", "libp2p-noise": "^4.0.0",
@ -15055,6 +15056,39 @@
"node": ">=14.0.0" "node": ">=14.0.0"
} }
}, },
"node_modules/libp2p-bootstrap": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/libp2p-bootstrap/-/libp2p-bootstrap-0.13.0.tgz",
"integrity": "sha512-8sXEZrikY+chKvMorkvOi9E/v9GvwsYr9DAEfzQZrOKQZByqhan1aXQKWrSpc4AxEv5/UopRzu1P47bkOi8wdw==",
"dependencies": {
"debug": "^4.3.1",
"mafmt": "^10.0.0",
"multiaddr": "^10.0.0",
"peer-id": "^0.15.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/libp2p-bootstrap/node_modules/peer-id": {
"version": "0.15.2",
"resolved": "https://registry.npmjs.org/peer-id/-/peer-id-0.15.2.tgz",
"integrity": "sha512-3OMbup76F28gKsQK4rGheEJHwosnJGe2+Obsf1xFaS9DpUaG9/JK0rtguWVLbrkxPclsCceci8g3/ulg8jsORA==",
"dependencies": {
"class-is": "^1.1.0",
"libp2p-crypto": "^0.19.0",
"minimist": "^1.2.5",
"multiformats": "^9.3.0",
"protobufjs": "^6.10.2",
"uint8arrays": "^2.0.5"
},
"bin": {
"peer-id": "src/bin.js"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/libp2p-crypto": { "node_modules/libp2p-crypto": {
"version": "0.19.6", "version": "0.19.6",
"resolved": "https://registry.npmjs.org/libp2p-crypto/-/libp2p-crypto-0.19.6.tgz", "resolved": "https://registry.npmjs.org/libp2p-crypto/-/libp2p-crypto-0.19.6.tgz",
@ -36622,6 +36656,32 @@
} }
} }
}, },
"libp2p-bootstrap": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/libp2p-bootstrap/-/libp2p-bootstrap-0.13.0.tgz",
"integrity": "sha512-8sXEZrikY+chKvMorkvOi9E/v9GvwsYr9DAEfzQZrOKQZByqhan1aXQKWrSpc4AxEv5/UopRzu1P47bkOi8wdw==",
"requires": {
"debug": "^4.3.1",
"mafmt": "^10.0.0",
"multiaddr": "^10.0.0",
"peer-id": "^0.15.0"
},
"dependencies": {
"peer-id": {
"version": "0.15.2",
"resolved": "https://registry.npmjs.org/peer-id/-/peer-id-0.15.2.tgz",
"integrity": "sha512-3OMbup76F28gKsQK4rGheEJHwosnJGe2+Obsf1xFaS9DpUaG9/JK0rtguWVLbrkxPclsCceci8g3/ulg8jsORA==",
"requires": {
"class-is": "^1.1.0",
"libp2p-crypto": "^0.19.0",
"minimist": "^1.2.5",
"multiformats": "^9.3.0",
"protobufjs": "^6.10.2",
"uint8arrays": "^2.0.5"
}
}
}
},
"libp2p-crypto": { "libp2p-crypto": {
"version": "0.19.6", "version": "0.19.6",
"resolved": "https://registry.npmjs.org/libp2p-crypto/-/libp2p-crypto-0.19.6.tgz", "resolved": "https://registry.npmjs.org/libp2p-crypto/-/libp2p-crypto-0.19.6.tgz",

View File

@ -65,6 +65,7 @@
"it-length-prefixed": "^5.0.2", "it-length-prefixed": "^5.0.2",
"js-sha3": "^0.8.0", "js-sha3": "^0.8.0",
"libp2p": "^0.32.0", "libp2p": "^0.32.0",
"libp2p-bootstrap": "^0.13.0",
"libp2p-gossipsub": "^0.10.0", "libp2p-gossipsub": "^0.10.0",
"libp2p-mplex": "^0.10.4", "libp2p-mplex": "^0.10.4",
"libp2p-noise": "^4.0.0", "libp2p-noise": "^4.0.0",

View File

@ -1,4 +1,4 @@
export { getStatusFleetNodes, Environment, Protocol } from './lib/discover'; export { getBootstrapNodes } from './lib/discovery';
export * as utils from './lib/utils'; export * as utils from './lib/utils';

View File

@ -1,33 +0,0 @@
/**
* Returns multiaddrs (inc. ip) of nim-waku nodes ran by Status.
* Used as a temporary discovery helper until more parties run their own nodes.
*/
import axios from 'axios';
export enum Protocol {
websocket = 'websocket',
tcp = 'tcp',
}
export enum Environment {
Test = 'test',
Prod = 'prod',
}
export async function getStatusFleetNodes(
env: Environment = Environment.Prod,
protocol: Protocol = Protocol.websocket
): Promise<string[]> {
const res = await axios.get('https://fleets.status.im/', {
headers: { 'Content-Type': 'application/json' },
});
const wakuFleet = res.data.fleets[`wakuv2.${env}`];
switch (protocol) {
case Protocol.tcp:
return Object.values(wakuFleet['waku']);
default:
return Object.values(wakuFleet['waku-websocket']);
}
}

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

@ -0,0 +1,60 @@
import axios from 'axios';
import debug from 'debug';
const dbg = debug('waku:discovery');
/**
* GET list of nodes from remote HTTP host.
*
* Default behaviour is to return nodes hosted by Status.
*
* @param path The property path to access the node list. The result should be
* a string, a string array or an object. If the result is an object then the
* values of the objects are used as multiaddresses. For example, if the GET
* request returns `{ foo: { bar: [address1, address2] } }` then `path` should be
* `[ "foo", "bar" ]`.
* @param url Remote host containing bootstrap peers in JSON format.
*
* @returns An array of multiaddresses.
* @throws If the remote host is unreachable or the response cannot be parsed
* according to the passed _path_.
*/
export async function getBootstrapNodes(
path: string[] = ['fleets', 'wakuv2.prod', 'waku-websocket'],
url = 'https://fleets.status.im/'
): Promise<string[]> {
const res = await axios.get(url, {
headers: { 'Content-Type': 'application/json' },
});
let nodes = res.data;
for (const prop of path) {
if (nodes[prop] === undefined) {
dbg(
`Failed to retrieve bootstrap nodes: ${prop} does not exist on `,
nodes
);
throw `Failed to retrieve bootstrap nodes: ${prop} does not exist on ${JSON.stringify(
nodes
)}`;
}
nodes = nodes[prop];
}
if (Array.isArray(nodes)) {
return nodes;
}
if (typeof nodes === 'string') {
return [nodes];
}
if (typeof nodes === 'object') {
return Object.values(nodes);
}
throw `Failed to retrieve bootstrap nodes: response format is not supported: ${JSON.stringify(
nodes
)}`;
}

View File

@ -2,20 +2,98 @@ import { expect } from 'chai';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: No types available // @ts-ignore: No types available
import TCP from 'libp2p-tcp'; import TCP from 'libp2p-tcp';
import PeerId from 'peer-id';
import { makeLogFileName, NimWaku, NOISE_KEY_1 } from '../test_utils/'; import {
makeLogFileName,
NimWaku,
NOISE_KEY_1,
NOISE_KEY_2,
} from '../test_utils/';
import { Waku } from './waku'; import { Waku } from './waku';
describe('Waku Dial', function () { describe('Waku Dial', function () {
let waku: Waku; let waku: Waku;
let waku2: Waku;
let nimWaku: NimWaku; let nimWaku: NimWaku;
afterEach(async function () { afterEach(async function () {
this.timeout(10_000); this.timeout(10_000);
nimWaku ? nimWaku.stop() : null; nimWaku ? nimWaku.stop() : null;
waku ? await waku.stop() : null;
await Promise.all([waku ? waku.stop() : null, waku2 ? waku2.stop() : null]);
});
describe('Bootstrap', function () {
it('Passing an array', async function () {
this.timeout(10_000);
waku = await Waku.create({
staticNoiseKey: NOISE_KEY_1,
libp2p: {
addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] },
modules: { transport: [TCP] },
},
});
const multiAddrWithId = waku.getLocalMultiaddrWithID();
waku2 = await Waku.create({
staticNoiseKey: NOISE_KEY_2,
libp2p: {
modules: { transport: [TCP] },
},
bootstrap: [multiAddrWithId],
});
const connectedPeerID: PeerId = await new Promise((resolve) => {
waku.libp2p.connectionManager.on('peer:connect', (connection) => {
resolve(connection.remotePeer);
});
});
expect(connectedPeerID.toB58String()).to.eq(
waku2.libp2p.peerId.toB58String()
);
});
});
describe('Bootstrap', function () {
it('Passing a function', async function () {
this.timeout(10_000);
waku = await Waku.create({
staticNoiseKey: NOISE_KEY_1,
libp2p: {
addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] },
modules: { transport: [TCP] },
},
});
const multiAddrWithId = waku.getLocalMultiaddrWithID();
waku2 = await Waku.create({
staticNoiseKey: NOISE_KEY_2,
libp2p: {
modules: { transport: [TCP] },
},
bootstrap: () => {
return [multiAddrWithId];
},
});
const connectedPeerID: PeerId = await new Promise((resolve) => {
waku.libp2p.connectionManager.on('peer:connect', (connection) => {
resolve(connection.remotePeer);
});
});
expect(connectedPeerID.toB58String()).to.eq(
waku2.libp2p.peerId.toB58String()
);
});
}); });
describe('Interop: Nim', function () { describe('Interop: Nim', function () {

View File

@ -1,4 +1,6 @@
import debug from 'debug';
import Libp2p, { Connection, Libp2pModules, Libp2pOptions } from 'libp2p'; import Libp2p, { Connection, Libp2pModules, Libp2pOptions } from 'libp2p';
import Bootstrap from 'libp2p-bootstrap';
import { MuxedStream } from 'libp2p-interfaces/dist/src/stream-muxer/types'; import { MuxedStream } from 'libp2p-interfaces/dist/src/stream-muxer/types';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: No types available // @ts-ignore: No types available
@ -15,6 +17,7 @@ import Ping from 'libp2p/src/ping';
import { Multiaddr, multiaddr } from 'multiaddr'; import { Multiaddr, multiaddr } from 'multiaddr';
import PeerId from 'peer-id'; import PeerId from 'peer-id';
import { getBootstrapNodes } from './discovery';
import { WakuLightPush } from './waku_light_push'; import { WakuLightPush } from './waku_light_push';
import { WakuMessage } from './waku_message'; import { WakuMessage } from './waku_message';
import { RelayCodecs, WakuRelay } from './waku_relay'; import { RelayCodecs, WakuRelay } from './waku_relay';
@ -23,8 +26,10 @@ import { StoreCodec, WakuStore } from './waku_store';
const websocketsTransportKey = Websockets.prototype[Symbol.toStringTag]; const websocketsTransportKey = Websockets.prototype[Symbol.toStringTag];
const DefaultPingKeepAliveValueSecs = 0; export const DefaultPingKeepAliveValueSecs = 0;
const DefaultRelayKeepAliveValueSecs = 5 * 60; export const DefaultRelayKeepAliveValueSecs = 5 * 60;
const dbg = debug('waku:waku');
export interface CreateOptions { export interface CreateOptions {
/** /**
@ -71,6 +76,18 @@ export interface CreateOptions {
* This is only used for test purposes to not run out of entropy during CI runs. * This is only used for test purposes to not run out of entropy during CI runs.
*/ */
staticNoiseKey?: bytes; staticNoiseKey?: bytes;
/**
* Use libp2p-bootstrap to discover and connect to new nodes.
*
* You can pass:
* - `true` to use {@link getBootstrapNodes},
* - an array of multiaddresses,
* - a function that returns an array of multiaddresses (or Promise of).
*
* Note: It overrides any other peerDiscovery modules that may have been set via
* {@link CreateOptions.libp2p}.
*/
bootstrap?: boolean | string[] | (() => string[] | Promise<string[]>);
} }
export class Waku { export class Waku {
@ -161,6 +178,40 @@ export class Waku {
pubsub: WakuRelay, pubsub: WakuRelay,
}); });
if (options?.bootstrap) {
let bootstrap: undefined | (() => string[] | Promise<string[]>);
if (options.bootstrap === true) {
bootstrap = getBootstrapNodes;
} else if (Array.isArray(options.bootstrap)) {
bootstrap = (): string[] => {
return options.bootstrap as string[];
};
} else if (typeof options.bootstrap === 'function') {
bootstrap = options.bootstrap;
}
if (bootstrap !== undefined) {
// Note: this overrides any other peer discover
libp2pOpts.modules = Object.assign(libp2pOpts.modules, {
peerDiscovery: [Bootstrap],
});
try {
const list = await bootstrap();
libp2pOpts.config.peerDiscovery = {
[Bootstrap.tag]: {
list,
enabled: true,
},
};
} catch (e) {
dbg('Failed to retrieve bootstrap nodes', e);
}
}
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: modules property is correctly set thanks to voodoo // @ts-ignore: modules property is correctly set thanks to voodoo
const libp2p = await Libp2p.create(libp2pOpts); const libp2p = await Libp2p.create(libp2pOpts);