diff --git a/packages/discovery/src/dns/dns.ts b/packages/discovery/src/dns/dns.ts index 422afba9f5..781d5333e1 100644 --- a/packages/discovery/src/dns/dns.ts +++ b/packages/discovery/src/dns/dns.ts @@ -1,6 +1,6 @@ import { ENR, EnrDecoder } from "@waku/enr"; import type { DnsClient, IEnr, SearchContext } from "@waku/interfaces"; -import { Logger } from "@waku/utils"; +import { Logger, shuffle } from "@waku/utils"; import { DnsOverHttps } from "./dns_over_https.js"; import { ENRTree } from "./enrtree.js"; @@ -27,19 +27,23 @@ export class DnsNodeDiscovery { } /** - * {@inheritDoc getPeers} + * Retrieve the next peers from the passed [[enrTreeUrls]], */ public async *getNextPeer(enrTreeUrls: string[]): AsyncGenerator { - const networkIndex = Math.floor(Math.random() * enrTreeUrls.length); - const { publicKey, domain } = ENRTree.parseTree(enrTreeUrls[networkIndex]); - const context: SearchContext = { - domain, - publicKey, - visits: {} - }; + // Shuffle the ENR Trees so that not all clients connect to same nodes first. + for (const enrTreeUrl of shuffle(enrTreeUrls)) { + const { publicKey, domain } = ENRTree.parseTree(enrTreeUrl); + const context: SearchContext = { + domain, + publicKey, + visits: {} + }; - for await (const peer of fetchNodes(() => this._search(domain, context))) { - yield peer; + for await (const peer of fetchNodes(() => + this._search(domain, context) + )) { + yield peer; + } } } @@ -113,7 +117,7 @@ export class DnsNodeDiscovery { throw new Error("Received empty result array while fetching TXT record"); if (!response[0].length) throw new Error("Received empty TXT record"); - // Branch entries can be an array of strings of comma delimited subdomains, with + // Branch entries can be an array of strings of comma-delimited subdomains, with // some subdomain strings split across the array elements const result = response.join(""); diff --git a/packages/tests/tests/dns-peer-discovery.spec.ts b/packages/tests/tests/dns-peer-discovery.spec.ts index 1bbc01cb9d..3c6d88839e 100644 --- a/packages/tests/tests/dns-peer-discovery.spec.ts +++ b/packages/tests/tests/dns-peer-discovery.spec.ts @@ -77,7 +77,7 @@ describe("DNS Node Discovery [live data]", function () { expect(dnsPeers).to.gte(minQuantityExpected); }); - it(`should retrieve ${maxQuantity} multiaddrs for test.waku.nodes.status.im`, async function () { + it(`should retrieve ${maxQuantity} multiaddrs for sandbox.waku.nodes.status.im`, async function () { if (process.env.CI) this.skip(); this.timeout(10000); @@ -102,6 +102,34 @@ describe("DNS Node Discovery [live data]", function () { seen.push(ma!.toString()); } }); + + it(`should retrieve all multiaddrs when several ENR Tree URLs are passed`, async function () { + if (process.env.CI) this.skip(); + + this.timeout(10000); + // Google's dns server address. Needs to be set explicitly to run in CI + const dnsNodeDiscovery = await DnsNodeDiscovery.dnsOverHttp(); + + const peers = []; + for await (const peer of dnsNodeDiscovery.getNextPeer([ + enrTree["SANDBOX"], + enrTree["TEST"] + ])) { + peers.push(peer); + } + + expect(peers.length).to.eq(6); + + const multiaddrs = peers.map((peer) => peer.multiaddrs).flat(); + + const seen: string[] = []; + for (const ma of multiaddrs) { + expect(ma).to.not.be.undefined; + expect(seen).to.not.include(ma!.toString()); + seen.push(ma!.toString()); + } + }); + it("passes more than one ENR URLs and attempts connection", async function () { if (process.env.CI) this.skip(); this.timeout(30_000); diff --git a/packages/utils/src/common/random_subset.ts b/packages/utils/src/common/random_subset.ts index 20d0ca83ce..55bc230838 100644 --- a/packages/utils/src/common/random_subset.ts +++ b/packages/utils/src/common/random_subset.ts @@ -12,7 +12,7 @@ export function getPseudoRandomSubset( return shuffle(values).slice(0, wantedNumber); } -function shuffle(arr: T[]): T[] { +export function shuffle(arr: T[]): T[] { if (arr.length <= 1) { return arr; }