Repository: waku-org/js-waku
Pull request #2363: feat: enable/disable discoveries without `defaultBootstrap: true`
Author: login: danisharora099
Created: 2025-04-18T09:30:40Z | Merged: 2025-05-30T10:57:00Z

### Problem / Description
Currently, peer-exchange is only enabled when `defaultBootstrap: true` and a developer needs to know/understand how to enable peer-exchange via libp2p config.

I don't see any reason why peer-exchange should not be enabled by default for LightNodes regardless of defaultBootstrap setting.

We can extend this logic to other available discovery methods.

### Solution
As a developer I want to be able to discover new peers automatically even if I am not using defaultBootstrap, so that I can have more reliable js-waku experience

### Notes
- Resolves https://github.com/waku-org/js-waku/issues/2328

---

#### Checklist
- [x] Code changes are **covered by unit tests**.
- [x] Code changes are **covered by e2e tests**, if applicable.
- [x] **Dogfooding has been performed**, if feasible.
- [ ] A **test version has been published**, if required.
- [ ] All **CI checks** pass successfully.



Diff:
diff --git a/packages/interfaces/src/protocols.ts b/packages/interfaces/src/protocols.ts
index 741f52056a..f80ad0806e 100644
--- a/packages/interfaces/src/protocols.ts
+++ b/packages/interfaces/src/protocols.ts
@@ -89,6 +89,17 @@ export type CreateNodeOptions = {
    */
   defaultBootstrap?: boolean;
 
+  /**
+   * Enable or disable specific discovery methods.
+   *
+   * @default { peerExchange: true, dns: true, localPeerCache: true }
+   */
+  discovery?: {
+    peerExchange: boolean;
+    dns: boolean;
+    localPeerCache: boolean;
+  };
+
   /**
    * List of peers to use to bootstrap the node. Ignored if defaultBootstrap is set to true.
    */
diff --git a/packages/sdk/src/create/discovery.spec.ts b/packages/sdk/src/create/discovery.spec.ts
new file mode 100644
index 0000000000..40ce90ba94
--- /dev/null
+++ b/packages/sdk/src/create/discovery.spec.ts
@@ -0,0 +1,58 @@
+import { PubsubTopic } from "@waku/interfaces";
+import { expect } from "chai";
+
+import { getPeerDiscoveries } from "./discovery.js";
+
+describe("Default Peer Discoveries", () => {
+  const pubsubTopics: PubsubTopic[] = [];
+
+  it("should have no discoveries enabled by default", () => {
+    const discoveries = getPeerDiscoveries(pubsubTopics);
+    expect(discoveries.length).to.equal(0);
+  });
+
+  it("should enable all discoveries when explicitly set", () => {
+    const discoveries = getPeerDiscoveries(pubsubTopics, {
+      dns: true,
+      peerExchange: true,
+      localPeerCache: true
+    });
+    expect(discoveries.length).to.equal(3);
+  });
+
+  it("should enable only peerExchange and localPeerCache when dns is disabled", () => {
+    const discoveries = getPeerDiscoveries(pubsubTopics, {
+      dns: false,
+      peerExchange: true,
+      localPeerCache: true
+    });
+    expect(discoveries.length).to.equal(2);
+  });
+
+  it("should enable only dns and localPeerCache when peerExchange is disabled", () => {
+    const discoveries = getPeerDiscoveries(pubsubTopics, {
+      dns: true,
+      peerExchange: false,
+      localPeerCache: true
+    });
+    expect(discoveries.length).to.equal(2);
+  });
+
+  it("should enable only dns and peerExchange when localPeerCache is disabled", () => {
+    const discoveries = getPeerDiscoveries(pubsubTopics, {
+      dns: true,
+      peerExchange: true,
+      localPeerCache: false
+    });
+    expect(discoveries.length).to.equal(2);
+  });
+
+  it("should enable only localPeerCache when dns and peerExchange are disabled", () => {
+    const discoveries = getPeerDiscoveries(pubsubTopics, {
+      dns: false,
+      peerExchange: false,
+      localPeerCache: true
+    });
+    expect(discoveries.length).to.equal(1);
+  });
+});
diff --git a/packages/sdk/src/create/discovery.ts b/packages/sdk/src/create/discovery.ts
index 341cff0e4e..c18cc1ec0b 100644
--- a/packages/sdk/src/create/discovery.ts
+++ b/packages/sdk/src/create/discovery.ts
@@ -5,18 +5,31 @@ import {
   wakuLocalPeerCacheDiscovery,
   wakuPeerExchangeDiscovery
 } from "@waku/discovery";
-import { type Libp2pComponents, PubsubTopic } from "@waku/interfaces";
+import {
+  CreateNodeOptions,
+  type Libp2pComponents,
+  PubsubTopic
+} from "@waku/interfaces";
 
-export function defaultPeerDiscoveries(
-  pubsubTopics: PubsubTopic[]
+export function getPeerDiscoveries(
+  pubsubTopics: PubsubTopic[],
+  enabled?: CreateNodeOptions["discovery"]
 ): ((components: Libp2pComponents) => PeerDiscovery)[] {
   const dnsEnrTrees = [enrTree["SANDBOX"]];
 
-  const discoveries = [
-    wakuDnsDiscovery(dnsEnrTrees),
-    wakuLocalPeerCacheDiscovery(),
-    wakuPeerExchangeDiscovery(pubsubTopics)
-  ];
+  const discoveries: ((components: Libp2pComponents) => PeerDiscovery)[] = [];
+
+  if (enabled?.dns) {
+    discoveries.push(wakuDnsDiscovery(dnsEnrTrees));
+  }
+
+  if (enabled?.localPeerCache) {
+    discoveries.push(wakuLocalPeerCacheDiscovery());
+  }
+
+  if (enabled?.peerExchange) {
+    discoveries.push(wakuPeerExchangeDiscovery(pubsubTopics));
+  }
 
   return discoveries;
 }
diff --git a/packages/sdk/src/create/libp2p.ts b/packages/sdk/src/create/libp2p.ts
index 1b4afce675..868cc3f3d0 100644
--- a/packages/sdk/src/create/libp2p.ts
+++ b/packages/sdk/src/create/libp2p.ts
@@ -20,7 +20,7 @@ import { createLibp2p } from "libp2p";
 
 import { isTestEnvironment } from "../env.js";
 
-import { defaultPeerDiscoveries } from "./discovery.js";
+import { getPeerDiscoveries } from "./discovery.js";
 
 type MetadataService = {
   metadata?: (components: Libp2pComponents) => IMetadata;
@@ -77,6 +77,12 @@ export async function defaultLibp2p(
   }) as any as Libp2p; // TODO: make libp2p include it;
 }
 
+const DEFAULT_DISCOVERIES_ENABLED = {
+  dns: true,
+  peerExchange: true,
+  localPeerCache: true
+};
+
 export async function createLibp2pAndUpdateOptions(
   options: CreateNodeOptions
 ): Promise<{ libp2p: Libp2p; pubsubTopics: PubsubTopic[] }> {
@@ -90,7 +96,14 @@ export async function createLibp2pAndUpdateOptions(
   const peerDiscovery = libp2pOptions.peerDiscovery ?? [];
 
   if (options?.defaultBootstrap) {
-    peerDiscovery.push(...defaultPeerDiscoveries(pubsubTopics));
+    peerDiscovery.push(
+      ...getPeerDiscoveries(pubsubTopics, {
+        ...DEFAULT_DISCOVERIES_ENABLED,
+        ...options.discovery
+      })
+    );
+  } else {
+    peerDiscovery.push(...getPeerDiscoveries(pubsubTopics, options.discovery));
   }
 
   if (options?.bootstrapPeers) {
diff --git a/packages/tests/src/lib/runNodes.ts b/packages/tests/src/lib/runNodes.ts
index bba48f343a..0c3b6fc6e0 100644
--- a/packages/tests/src/lib/runNodes.ts
+++ b/packages/tests/src/lib/runNodes.ts
@@ -15,6 +15,12 @@ import { ServiceNode } from "./service_node.js";
 
 export const log = new Logger("test:runNodes");
 
+export const DEFAULT_DISCOVERIES_ENABLED = {
+  dns: true,
+  peerExchange: true,
+  localPeerCache: true
+};
+
 type RunNodesOptions = {
   context: Context;
   networkConfig: NetworkConfig;
@@ -46,7 +52,8 @@ export async function runNodes<T>(
     staticNoiseKey: NOISE_KEY_1,
     libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } },
     networkConfig: shardInfo,
-    lightPush: { numPeersToUse: 2 }
+    lightPush: { numPeersToUse: 2 },
+    discovery: DEFAULT_DISCOVERIES_ENABLED
   };
 
   log.info("Starting js waku node with :", JSON.stringify(waku_options));
diff --git a/packages/tests/src/utils/nodes.ts b/packages/tests/src/utils/nodes.ts
index 497c6b9684..dc56d4adf0 100644
--- a/packages/tests/src/utils/nodes.ts
+++ b/packages/tests/src/utils/nodes.ts
@@ -13,6 +13,7 @@ import pRetry from "p-retry";
 
 import { NOISE_KEY_1 } from "../constants.js";
 import { ServiceNodesFleet } from "../lib/index.js";
+import { DEFAULT_DISCOVERIES_ENABLED } from "../lib/runNodes.js";
 import { Args } from "../types.js";
 
 import { waitForConnections } from "./waitForConnections.js";
@@ -41,7 +42,8 @@ export async function runMultipleNodes(
       addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] }
     },
     networkConfig,
-    lightPush: { numPeersToUse: numServiceNodes }
+    lightPush: { numPeersToUse: numServiceNodes },
+    discovery: DEFAULT_DISCOVERIES_ENABLED
   };
 
   const waku = await createLightNode(wakuOptions);


Reviews:
user: login: weboko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-12T21:10:50Z
user: login: weboko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-12T21:12:24Z
user: login: danisharora099
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-19T11:06:14Z
user: login: weboko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-19T23:00:18Z
user: login: weboko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-19T23:03:36Z
user: login: weboko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-19T23:04:47Z
user: login: danisharora099
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T05:34:42Z
user: login: danisharora099
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T05:36:43Z
user: login: weboko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T12:58:23Z
user: login: danisharora099
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T12:09:46Z
user: login: weboko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T13:35:41Z
user: login: weboko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T13:37:01Z
user: login: weboko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T13:37:28Z
user: login: weboko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T13:38:10Z
user: login: weboko
body: added some corrections, please, address before merge 
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-29T13:38:35Z
user: login: danisharora099
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T14:00:52Z

Review comments:
diff_hunk: @@ -10,7 +10,13 @@ import { createLibp2pAndUpdateOptions } from "./libp2p.js";
  * Uses Waku Filter V2 by default.
  */
 export async function createLightNode(
-  options: CreateNodeOptions = {}
+  options: CreateNodeOptions = {
path: packages/sdk/src/create/create.ts
user: login: weboko
body: Let's not add it here and instead set it in packages/sdk/src/create/discovery.ts
created_at: 2025-05-12T21:10:50Z
author_association: COLLABORATOR
diff_hunk: @@ -91,6 +91,14 @@ export async function createLibp2pAndUpdateOptions(
 
   if (options?.defaultBootstrap) {
path: packages/sdk/src/create/libp2p.ts
user: login: weboko
body: I am failing to understand difference between passing `defaultBootstrap` as `true` and not passing it at all (or as `false`)
created_at: 2025-05-12T21:12:24Z
author_association: COLLABORATOR
diff_hunk: @@ -10,7 +10,13 @@ import { createLibp2pAndUpdateOptions } from "./libp2p.js";
  * Uses Waku Filter V2 by default.
  */
 export async function createLightNode(
-  options: CreateNodeOptions = {}
+  options: CreateNodeOptions = {
path: packages/sdk/src/create/create.ts
user: login: danisharora099
body: 22df03eefaf093c0330dddbadcd58e644e18cf73
created_at: 2025-05-19T11:02:03Z
author_association: COLLABORATOR
diff_hunk: @@ -91,6 +91,14 @@ export async function createLibp2pAndUpdateOptions(
 
   if (options?.defaultBootstrap) {
path: packages/sdk/src/create/libp2p.ts
user: login: danisharora099
body: - If you pass `defaultBootstrap: true`, the node uses the default peer discovery settings (no customizations).
- If you do not pass `defaultBootstrap` (or pass `false`), the node uses the peer discovery settings specified in options.discoveriesEnabled (i.e., you can enable/disable DNS, peer exchange, local peer cache, etc.).

In short:
`defaultBootstrap: true` → default peer discovery
`defaultBootstrap: false` or omitted → custom peer discovery based on your config
created_at: 2025-05-19T11:06:06Z
author_association: COLLABORATOR
diff_hunk: @@ -89,6 +89,17 @@ export type CreateNodeOptions = {
    */
   defaultBootstrap?: boolean;
 
+  /**
+   * Enable or disable specific discovery methods.
+   *
+   * @default { peerExchange: true, dns: true, localPeerCache: true }
+   */
+  discoveriesEnabled: {
path: packages/interfaces/src/protocols.ts
user: login: weboko
body: let's rename to simply `discovery`

```suggestion
  discovery: {
```
created_at: 2025-05-19T23:00:18Z
author_association: COLLABORATOR
diff_hunk: @@ -5,18 +5,37 @@ import {
   wakuLocalPeerCacheDiscovery,
   wakuPeerExchangeDiscovery
 } from "@waku/discovery";
-import { type Libp2pComponents, PubsubTopic } from "@waku/interfaces";
+import {
+  CreateNodeOptions,
+  type Libp2pComponents,
+  PubsubTopic
+} from "@waku/interfaces";
+
+export const DEFAULT_DISCOVERIES_ENABLED = {
path: packages/sdk/src/create/discovery.ts
user: login: weboko
body: let's not pollute our export space with this and define what is needed in place - e.g define what discoveries are needed in `test` package as it is separate execution space, otherwise, default values should be default and not needed for export/import to be enabled
created_at: 2025-05-19T23:03:35Z
author_association: COLLABORATOR
diff_hunk: @@ -91,6 +91,14 @@ export async function createLibp2pAndUpdateOptions(
 
   if (options?.defaultBootstrap) {
path: packages/sdk/src/create/libp2p.ts
user: login: weboko
body: that part I understand, I just don't understand why we need all these steps to come back to default values which are:
```
  dns: true,
  peerExchange: true,
  localPeerCache: true
```
created_at: 2025-05-19T23:04:47Z
author_association: COLLABORATOR
diff_hunk: @@ -5,18 +5,37 @@ import {
   wakuLocalPeerCacheDiscovery,
   wakuPeerExchangeDiscovery
 } from "@waku/discovery";
-import { type Libp2pComponents, PubsubTopic } from "@waku/interfaces";
+import {
+  CreateNodeOptions,
+  type Libp2pComponents,
+  PubsubTopic
+} from "@waku/interfaces";
+
+export const DEFAULT_DISCOVERIES_ENABLED = {
path: packages/sdk/src/create/discovery.ts
user: login: danisharora099
body: Ah, good point!
created_at: 2025-05-23T05:34:42Z
author_association: COLLABORATOR
diff_hunk: @@ -91,6 +91,14 @@ export async function createLibp2pAndUpdateOptions(
 
   if (options?.defaultBootstrap) {
path: packages/sdk/src/create/libp2p.ts
user: login: danisharora099
body: The two-step logic is there to let you override the default peer discovery settings if you want. If you use defaultBootstrap: true, you get all discovery mechanisms enabled. If you want to disable any, you set defaultBootstrap: false (or omit it) and use options.discoveriesEnabled to control each mechanism. If you set all to true, the result is the same as the default—so the extra steps are just for flexibility
created_at: 2025-05-23T05:36:43Z
author_association: COLLABORATOR
diff_hunk: @@ -91,6 +91,14 @@ export async function createLibp2pAndUpdateOptions(
 
   if (options?.defaultBootstrap) {
path: packages/sdk/src/create/libp2p.ts
user: login: weboko
body: what I see is:
- `defaultBootstrap: true` -> `peerExchange: true, dns: true, localPeerCache: true`
- `defaultBootstrap: false` -> `peerExchange: true, dns: true, localPeerCache: true, ...options.discovery`

what I think should be:
- `defaultBootstrap: true` -> `peerExchange: true, dns: true, localPeerCache: true, ...options.discovery`
- `defaultBootstrap: false` -> `undefined, ...options.discovery`

@danisharora099 wdyt? 


created_at: 2025-05-23T12:58:23Z
author_association: COLLABORATOR
diff_hunk: @@ -91,6 +91,14 @@ export async function createLibp2pAndUpdateOptions(
 
   if (options?.defaultBootstrap) {
path: packages/sdk/src/create/libp2p.ts
user: login: danisharora099
body: Ah, I see what you mean -- wdyt about this:  0888a2cef48ba2da061700067fe1047b6c0a11c6 @weboko 
created_at: 2025-05-29T12:09:46Z
author_association: COLLABORATOR
diff_hunk: @@ -90,7 +96,16 @@ export async function createLibp2pAndUpdateOptions(
   const peerDiscovery = libp2pOptions.peerDiscovery ?? [];
 
   if (options?.defaultBootstrap) {
-    peerDiscovery.push(...defaultPeerDiscoveries(pubsubTopics));
+    peerDiscovery.push(
+      ...defaultPeerDiscoveries(pubsubTopics, {
+        ...DEFAULT_DISCOVERIES_ENABLED,
+        ...options.discovery
+      })
+    );
+  } else {
+    peerDiscovery.push(
+      ...defaultPeerDiscoveries(pubsubTopics, options.discovery)
path: packages/sdk/src/create/libp2p.ts
user: login: weboko
body: ```suggestion
      ...defaultPeerDiscoveries(pubsubTopics, options.discovery || {})
```
created_at: 2025-05-29T13:35:41Z
author_association: COLLABORATOR
diff_hunk: @@ -5,18 +5,31 @@ import {
   wakuLocalPeerCacheDiscovery,
   wakuPeerExchangeDiscovery
 } from "@waku/discovery";
-import { type Libp2pComponents, PubsubTopic } from "@waku/interfaces";
+import {
+  CreateNodeOptions,
+  type Libp2pComponents,
+  PubsubTopic
+} from "@waku/interfaces";
 
 export function defaultPeerDiscoveries(
path: packages/sdk/src/create/discovery.ts
user: login: weboko
body: ```suggestion
export function getPeerDiscoveries(
```
created_at: 2025-05-29T13:37:00Z
author_association: COLLABORATOR
diff_hunk: @@ -10,7 +10,7 @@ import { createLibp2pAndUpdateOptions } from "./libp2p.js";
  * Uses Waku Filter V2 by default.
  */
 export async function createLightNode(
-  options: CreateNodeOptions = {}
+  options: CreateNodeOptions
path: packages/sdk/src/create/create.ts
user: login: weboko
body: ```suggestion
  options: CreateNodeOptions = {}
```
created_at: 2025-05-29T13:37:28Z
author_association: COLLABORATOR
diff_hunk: @@ -91,6 +91,14 @@ export async function createLibp2pAndUpdateOptions(
 
   if (options?.defaultBootstrap) {
path: packages/sdk/src/create/libp2p.ts
user: login: weboko
body: thx
created_at: 2025-05-29T13:38:09Z
author_association: COLLABORATOR
diff_hunk: @@ -90,7 +96,16 @@ export async function createLibp2pAndUpdateOptions(
   const peerDiscovery = libp2pOptions.peerDiscovery ?? [];
 
   if (options?.defaultBootstrap) {
-    peerDiscovery.push(...defaultPeerDiscoveries(pubsubTopics));
+    peerDiscovery.push(
+      ...defaultPeerDiscoveries(pubsubTopics, {
+        ...DEFAULT_DISCOVERIES_ENABLED,
+        ...options.discovery
+      })
+    );
+  } else {
+    peerDiscovery.push(
+      ...defaultPeerDiscoveries(pubsubTopics, options.discovery)
path: packages/sdk/src/create/libp2p.ts
user: login: danisharora099
body: updated interface to have `discovery` be optional
created_at: 2025-05-29T14:00:52Z
author_association: COLLABORATOR

PR comments:
user: login: weboko
created_at: 2025-05-12T21:10:07Z
body: @danisharora099 pipeline fails, can you, please, take a look? 
author_association: COLLABORATOR
user: login: github-actions[bot]
created_at: 2025-05-19T11:05:59Z
body: ## size-limit report 📦 
| Path                          | Size                 | Loading time (3g) | Running time (snapdragon) | Total time |
| ----------------------------- | -------------------- | ----------------- | ------------------------- | ---------- |
| Waku node                     | 63.34 KB (0%)        | 1.3 s (0%)        | 548 ms (+40.64% 🔺)       | 1.9 s      |
| Waku Simple Light Node        | 117.08 KB (+0.1% 🔺) | 2.4 s (+0.1% 🔺)  | 765 ms (+101.41% 🔺)      | 3.2 s      |
| ECIES encryption              | 23.13 KB (0%)        | 463 ms (0%)       | 410 ms (+265.38% 🔺)      | 873 ms     |
| Symmetric encryption          | 22.53 KB (0%)        | 451 ms (0%)       | 173 ms (-16.71% 🔽)       | 623 ms     |
| DNS discovery                 | 50.75 KB (0%)        | 1.1 s (0%)        | 312 ms (+15.82% 🔺)       | 1.4 s      |
| Peer Exchange discovery       | 51.13 KB (0%)        | 1.1 s (0%)        | 278 ms (-6.29% 🔽)        | 1.4 s      |
| Local Peer Cache Discovery    | 44.49 KB (0%)        | 890 ms (0%)       | 275 ms (-6.48% 🔽)        | 1.2 s      |
| Privacy preserving protocols  | 51.99 KB (0%)        | 1.1 s (0%)        | 289 ms (+25.09% 🔺)       | 1.4 s      |
| Waku Filter                   | 54.08 KB (0%)        | 1.1 s (0%)        | 278 ms (+12.13% 🔺)       | 1.4 s      |
| Waku LightPush                | 51.37 KB (0%)        | 1.1 s (0%)        | 354 ms (+28.53% 🔺)       | 1.4 s      |
| History retrieval protocols   | 51.63 KB (0%)        | 1.1 s (0%)        | 344 ms (+39.47% 🔺)       | 1.4 s      |
| Deterministic Message Hashing | 7.28 KB (0%)         | 146 ms (0%)       | 112 ms (+12% 🔺)          | 258 ms     |
author_association: NONE