mirror of
https://github.com/logos-messaging/js-waku.git
synced 2026-01-02 13:53:12 +00:00
Merge pull request #2313 from waku-org/fix/do-not-limit-dns-discovered-peers
feat!: do not limit DNS Peer Discovery on capability
This commit is contained in:
commit
103f21ef85
@ -1,4 +1,4 @@
|
|||||||
import { type NodeCapabilityCount, Tags } from "@waku/interfaces";
|
import { Tags } from "@waku/interfaces";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ENR tree for the different fleets.
|
* The ENR tree for the different fleets.
|
||||||
@ -13,9 +13,3 @@ export const enrTree = {
|
|||||||
export const DEFAULT_BOOTSTRAP_TAG_NAME = Tags.BOOTSTRAP;
|
export const DEFAULT_BOOTSTRAP_TAG_NAME = Tags.BOOTSTRAP;
|
||||||
export const DEFAULT_BOOTSTRAP_TAG_VALUE = 50;
|
export const DEFAULT_BOOTSTRAP_TAG_VALUE = 50;
|
||||||
export const DEFAULT_BOOTSTRAP_TAG_TTL = 100_000_000;
|
export const DEFAULT_BOOTSTRAP_TAG_TTL = 100_000_000;
|
||||||
|
|
||||||
export const DEFAULT_NODE_REQUIREMENTS: Partial<NodeCapabilityCount> = {
|
|
||||||
store: 1,
|
|
||||||
filter: 2,
|
|
||||||
lightPush: 2
|
|
||||||
};
|
|
||||||
|
|||||||
@ -17,7 +17,6 @@ const branchDomainD = "D5SNLTAGWNQ34NTQTPHNZDECFU";
|
|||||||
const partialBranchA = "AAAA";
|
const partialBranchA = "AAAA";
|
||||||
const partialBranchB = "BBBB";
|
const partialBranchB = "BBBB";
|
||||||
const singleBranch = `enrtree-branch:${branchDomainA}`;
|
const singleBranch = `enrtree-branch:${branchDomainA}`;
|
||||||
const doubleBranch = `enrtree-branch:${branchDomainA},${branchDomainB}`;
|
|
||||||
const multiComponentBranch = [
|
const multiComponentBranch = [
|
||||||
`enrtree-branch:${branchDomainA},${partialBranchA}`,
|
`enrtree-branch:${branchDomainA},${partialBranchA}`,
|
||||||
`${partialBranchB},${branchDomainB}`
|
`${partialBranchB},${branchDomainB}`
|
||||||
@ -34,10 +33,12 @@ const errorBranchB = `enrtree-branch:${branchDomainD}`;
|
|||||||
class MockDNS implements DnsClient {
|
class MockDNS implements DnsClient {
|
||||||
private fqdnRes: Map<string, string[]>;
|
private fqdnRes: Map<string, string[]>;
|
||||||
private fqdnThrows: string[];
|
private fqdnThrows: string[];
|
||||||
|
public hasThrown: boolean;
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
this.fqdnRes = new Map();
|
this.fqdnRes = new Map();
|
||||||
this.fqdnThrows = [];
|
this.fqdnThrows = [];
|
||||||
|
this.hasThrown = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public addRes(fqdn: string, res: string[]): void {
|
public addRes(fqdn: string, res: string[]): void {
|
||||||
@ -49,11 +50,18 @@ class MockDNS implements DnsClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public resolveTXT(fqdn: string): Promise<string[]> {
|
public resolveTXT(fqdn: string): Promise<string[]> {
|
||||||
if (this.fqdnThrows.includes(fqdn)) throw "Mock DNS throws.";
|
if (this.fqdnThrows.includes(fqdn)) {
|
||||||
|
this.hasThrown = true;
|
||||||
|
console.log("throwing");
|
||||||
|
throw "Mock DNS throws.";
|
||||||
|
}
|
||||||
|
|
||||||
const res = this.fqdnRes.get(fqdn);
|
const res = this.fqdnRes.get(fqdn);
|
||||||
|
|
||||||
if (!res) throw `Mock DNS could not resolve ${fqdn}`;
|
if (!res) {
|
||||||
|
this.hasThrown = true;
|
||||||
|
throw `Mock DNS could not resolve ${fqdn}`;
|
||||||
|
}
|
||||||
|
|
||||||
return Promise.resolve(res);
|
return Promise.resolve(res);
|
||||||
}
|
}
|
||||||
@ -72,9 +80,10 @@ describe("DNS Node Discovery", () => {
|
|||||||
mockDns.addRes(`${branchDomainA}.${host}`, [mockData.enrWithWaku2Relay]);
|
mockDns.addRes(`${branchDomainA}.${host}`, [mockData.enrWithWaku2Relay]);
|
||||||
|
|
||||||
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
||||||
const peers = await dnsNodeDiscovery.getPeers([mockData.enrTree], {
|
const peers = [];
|
||||||
relay: 1
|
for await (const peer of dnsNodeDiscovery.getNextPeer([mockData.enrTree])) {
|
||||||
});
|
peers.push(peer);
|
||||||
|
}
|
||||||
|
|
||||||
expect(peers.length).to.eq(1);
|
expect(peers.length).to.eq(1);
|
||||||
expect(peers[0].ip).to.eq("192.168.178.251");
|
expect(peers[0].ip).to.eq("192.168.178.251");
|
||||||
@ -88,9 +97,10 @@ describe("DNS Node Discovery", () => {
|
|||||||
mockDns.addRes(`${branchDomainA}.${host}`, [singleBranch]);
|
mockDns.addRes(`${branchDomainA}.${host}`, [singleBranch]);
|
||||||
|
|
||||||
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
||||||
const peers = await dnsNodeDiscovery.getPeers([mockData.enrTree], {
|
const peers = [];
|
||||||
relay: 1
|
for await (const peer of dnsNodeDiscovery.getNextPeer([mockData.enrTree])) {
|
||||||
});
|
peers.push(peer);
|
||||||
|
}
|
||||||
|
|
||||||
expect(peers.length).to.eq(0);
|
expect(peers.length).to.eq(0);
|
||||||
});
|
});
|
||||||
@ -102,17 +112,21 @@ describe("DNS Node Discovery", () => {
|
|||||||
mockDns.addRes(`${branchDomainA}.${host}`, []);
|
mockDns.addRes(`${branchDomainA}.${host}`, []);
|
||||||
|
|
||||||
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
||||||
let peers = await dnsNodeDiscovery.getPeers([mockData.enrTree], {
|
const peersA = [];
|
||||||
relay: 1
|
for await (const peer of dnsNodeDiscovery.getNextPeer([mockData.enrTree])) {
|
||||||
});
|
peersA.push(peer);
|
||||||
|
}
|
||||||
|
|
||||||
expect(peers.length).to.eq(0);
|
expect(peersA.length).to.eq(0);
|
||||||
|
|
||||||
// No TXT records case
|
// No TXT records case
|
||||||
mockDns.addRes(`${branchDomainA}.${host}`, []);
|
mockDns.addRes(`${branchDomainA}.${host}`, []);
|
||||||
|
|
||||||
peers = await dnsNodeDiscovery.getPeers([mockData.enrTree], { relay: 1 });
|
const peersB = [];
|
||||||
expect(peers.length).to.eq(0);
|
for await (const peer of dnsNodeDiscovery.getNextPeer([mockData.enrTree])) {
|
||||||
|
peersB.push(peer);
|
||||||
|
}
|
||||||
|
expect(peersB.length).to.eq(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ignores domain fetching errors", async function () {
|
it("ignores domain fetching errors", async function () {
|
||||||
@ -120,18 +134,20 @@ describe("DNS Node Discovery", () => {
|
|||||||
mockDns.addThrow(`${branchDomainC}.${host}`);
|
mockDns.addThrow(`${branchDomainC}.${host}`);
|
||||||
|
|
||||||
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
||||||
const peers = await dnsNodeDiscovery.getPeers([mockData.enrTree], {
|
const peers = [];
|
||||||
relay: 1
|
for await (const peer of dnsNodeDiscovery.getNextPeer([mockData.enrTree])) {
|
||||||
});
|
peers.push(peer);
|
||||||
|
}
|
||||||
expect(peers.length).to.eq(0);
|
expect(peers.length).to.eq(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ignores unrecognized TXT record formats", async function () {
|
it("ignores unrecognized TXT record formats", async function () {
|
||||||
mockDns.addRes(`${rootDomain}.${host}`, [mockData.enrBranchBadPrefix]);
|
mockDns.addRes(`${rootDomain}.${host}`, [mockData.enrBranchBadPrefix]);
|
||||||
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
||||||
const peers = await dnsNodeDiscovery.getPeers([mockData.enrTree], {
|
const peers = [];
|
||||||
relay: 1
|
for await (const peer of dnsNodeDiscovery.getNextPeer([mockData.enrTree])) {
|
||||||
});
|
peers.push(peer);
|
||||||
|
}
|
||||||
expect(peers.length).to.eq(0);
|
expect(peers.length).to.eq(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -140,20 +156,23 @@ describe("DNS Node Discovery", () => {
|
|||||||
mockDns.addRes(`${branchDomainD}.${host}`, [mockData.enrWithWaku2Relay]);
|
mockDns.addRes(`${branchDomainD}.${host}`, [mockData.enrWithWaku2Relay]);
|
||||||
|
|
||||||
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
||||||
const peersA = await dnsNodeDiscovery.getPeers([mockData.enrTree], {
|
const peersA = [];
|
||||||
relay: 1
|
for await (const peer of dnsNodeDiscovery.getNextPeer([mockData.enrTree])) {
|
||||||
});
|
peersA.push(peer);
|
||||||
|
}
|
||||||
expect(peersA.length).to.eq(1);
|
expect(peersA.length).to.eq(1);
|
||||||
|
|
||||||
// Specify that a subsequent network call retrieving the same peer should throw.
|
// Specify that a subsequent network call retrieving the same peer should throw.
|
||||||
// This test passes only if the peer is fetched from cache
|
// This test passes only if the peer is fetched from cache
|
||||||
mockDns.addThrow(`${branchDomainD}.${host}`);
|
mockDns.addThrow(`${branchDomainD}.${host}`);
|
||||||
|
|
||||||
const peersB = await dnsNodeDiscovery.getPeers([mockData.enrTree], {
|
const peersB = [];
|
||||||
relay: 1
|
for await (const peer of dnsNodeDiscovery.getNextPeer([mockData.enrTree])) {
|
||||||
});
|
peersB.push(peer);
|
||||||
|
}
|
||||||
expect(peersB.length).to.eq(1);
|
expect(peersB.length).to.eq(1);
|
||||||
expect(peersA[0].ip).to.eq(peersB[0].ip);
|
expect(peersA[0].ip).to.eq(peersB[0].ip);
|
||||||
|
expect(mockDns.hasThrown).to.be.false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -169,9 +188,10 @@ describe("DNS Node Discovery w/ capabilities", () => {
|
|||||||
mockDns.addRes(`${rootDomain}.${host}`, [mockData.enrWithWaku2Relay]);
|
mockDns.addRes(`${rootDomain}.${host}`, [mockData.enrWithWaku2Relay]);
|
||||||
|
|
||||||
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
||||||
const peers = await dnsNodeDiscovery.getPeers([mockData.enrTree], {
|
const peers = [];
|
||||||
relay: 1
|
for await (const peer of dnsNodeDiscovery.getNextPeer([mockData.enrTree])) {
|
||||||
});
|
peers.push(peer);
|
||||||
|
}
|
||||||
|
|
||||||
expect(peers.length).to.eq(1);
|
expect(peers.length).to.eq(1);
|
||||||
expect(peers[0].peerId?.toString()).to.eq(
|
expect(peers[0].peerId?.toString()).to.eq(
|
||||||
@ -183,10 +203,10 @@ describe("DNS Node Discovery w/ capabilities", () => {
|
|||||||
mockDns.addRes(`${rootDomain}.${host}`, [mockData.enrWithWaku2RelayStore]);
|
mockDns.addRes(`${rootDomain}.${host}`, [mockData.enrWithWaku2RelayStore]);
|
||||||
|
|
||||||
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
||||||
const peers = await dnsNodeDiscovery.getPeers([mockData.enrTree], {
|
const peers = [];
|
||||||
store: 1,
|
for await (const peer of dnsNodeDiscovery.getNextPeer([mockData.enrTree])) {
|
||||||
relay: 1
|
peers.push(peer);
|
||||||
});
|
}
|
||||||
|
|
||||||
expect(peers.length).to.eq(1);
|
expect(peers.length).to.eq(1);
|
||||||
expect(peers[0].peerId?.toString()).to.eq(
|
expect(peers[0].peerId?.toString()).to.eq(
|
||||||
@ -194,42 +214,24 @@ describe("DNS Node Discovery w/ capabilities", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should only return 1 node with store capability", async () => {
|
it("return first retrieved peers without further DNS queries", async function () {
|
||||||
mockDns.addRes(`${rootDomain}.${host}`, [mockData.enrWithWaku2Store]);
|
mockDns.addRes(`${rootDomain}.${host}`, multiComponentBranch);
|
||||||
|
|
||||||
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
|
||||||
const peers = await dnsNodeDiscovery.getPeers([mockData.enrTree], {
|
|
||||||
store: 1
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(peers.length).to.eq(1);
|
|
||||||
expect(peers[0].peerId?.toString()).to.eq(
|
|
||||||
"16Uiu2HAkv3La3ECgQpdYeEJfrX36EWdhkUDv4C9wvXM8TFZ9dNgd"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("retrieves all peers (2) when cannot fulfill all requirements", async () => {
|
|
||||||
mockDns.addRes(`${rootDomain}.${host}`, [doubleBranch]);
|
|
||||||
mockDns.addRes(`${branchDomainA}.${host}`, [
|
mockDns.addRes(`${branchDomainA}.${host}`, [
|
||||||
mockData.enrWithWaku2RelayStore
|
mockData.enrWithWaku2RelayStore
|
||||||
]);
|
]);
|
||||||
mockDns.addRes(`${branchDomainB}.${host}`, [mockData.enrWithWaku2Relay]);
|
// The ENR Tree is such as there are more branches to be explored.
|
||||||
|
// But they should not be explored if it isn't asked
|
||||||
|
mockDns.addThrow(`${branchDomainB}.${host}`);
|
||||||
|
|
||||||
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
||||||
const peers = await dnsNodeDiscovery.getPeers([mockData.enrTree], {
|
|
||||||
store: 1,
|
|
||||||
relay: 2,
|
|
||||||
filter: 1
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(peers.length).to.eq(2);
|
const iterator = dnsNodeDiscovery.getNextPeer([mockData.enrTree]);
|
||||||
const peerIds = peers.map((p) => p.peerId?.toString());
|
const { value: peer } = await iterator.next();
|
||||||
expect(peerIds).to.contain(
|
|
||||||
|
expect(peer.peerId?.toString()).to.eq(
|
||||||
"16Uiu2HAm2HyS6brcCspSbszG9i36re2bWBVjMe3tMdnFp1Hua34F"
|
"16Uiu2HAm2HyS6brcCspSbszG9i36re2bWBVjMe3tMdnFp1Hua34F"
|
||||||
);
|
);
|
||||||
expect(peerIds).to.contain(
|
expect(mockDns.hasThrown).to.be.false;
|
||||||
"16Uiu2HAmPsYLvfKafxgRsb6tioYyGnSvGXS2iuMigptHrqHPNPzx"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("retrieves all peers (3) when branch entries are composed of multiple strings", async function () {
|
it("retrieves all peers (3) when branch entries are composed of multiple strings", async function () {
|
||||||
@ -243,10 +245,10 @@ describe("DNS Node Discovery w/ capabilities", () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
||||||
const peers = await dnsNodeDiscovery.getPeers([mockData.enrTree], {
|
const peers = [];
|
||||||
store: 2,
|
for await (const peer of dnsNodeDiscovery.getNextPeer([mockData.enrTree])) {
|
||||||
relay: 2
|
peers.push(peer);
|
||||||
});
|
}
|
||||||
|
|
||||||
expect(peers.length).to.eq(3);
|
expect(peers.length).to.eq(3);
|
||||||
const peerIds = peers.map((p) => p.peerId?.toString());
|
const peerIds = peers.map((p) => p.peerId?.toString());
|
||||||
@ -275,12 +277,10 @@ describe("DNS Node Discovery [live data]", function () {
|
|||||||
this.timeout(10000);
|
this.timeout(10000);
|
||||||
// Google's dns server address. Needs to be set explicitly to run in CI
|
// Google's dns server address. Needs to be set explicitly to run in CI
|
||||||
const dnsNodeDiscovery = await DnsNodeDiscovery.dnsOverHttp();
|
const dnsNodeDiscovery = await DnsNodeDiscovery.dnsOverHttp();
|
||||||
const peers = await dnsNodeDiscovery.getPeers([enrTree.TEST], {
|
const peers = [];
|
||||||
relay: maxQuantity,
|
for await (const peer of dnsNodeDiscovery.getNextPeer([enrTree.TEST])) {
|
||||||
store: maxQuantity,
|
peers.push(peer);
|
||||||
filter: maxQuantity,
|
}
|
||||||
lightPush: maxQuantity
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(peers.length).to.eq(maxQuantity);
|
expect(peers.length).to.eq(maxQuantity);
|
||||||
|
|
||||||
@ -298,12 +298,10 @@ describe("DNS Node Discovery [live data]", function () {
|
|||||||
this.timeout(10000);
|
this.timeout(10000);
|
||||||
// Google's dns server address. Needs to be set explicitly to run in CI
|
// Google's dns server address. Needs to be set explicitly to run in CI
|
||||||
const dnsNodeDiscovery = await DnsNodeDiscovery.dnsOverHttp();
|
const dnsNodeDiscovery = await DnsNodeDiscovery.dnsOverHttp();
|
||||||
const peers = await dnsNodeDiscovery.getPeers([enrTree.SANDBOX], {
|
const peers = [];
|
||||||
relay: maxQuantity,
|
for await (const peer of dnsNodeDiscovery.getNextPeer([enrTree.SANDBOX])) {
|
||||||
store: maxQuantity,
|
peers.push(peer);
|
||||||
filter: maxQuantity,
|
}
|
||||||
lightPush: maxQuantity
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(peers.length).to.eq(maxQuantity);
|
expect(peers.length).to.eq(maxQuantity);
|
||||||
|
|
||||||
|
|||||||
@ -1,25 +1,16 @@
|
|||||||
import { ENR, EnrDecoder } from "@waku/enr";
|
import { ENR, EnrDecoder } from "@waku/enr";
|
||||||
import type {
|
import type { DnsClient, IEnr, SearchContext } from "@waku/interfaces";
|
||||||
DnsClient,
|
import { Logger, shuffle } from "@waku/utils";
|
||||||
IEnr,
|
|
||||||
NodeCapabilityCount,
|
|
||||||
SearchContext
|
|
||||||
} from "@waku/interfaces";
|
|
||||||
import { Logger } from "@waku/utils";
|
|
||||||
|
|
||||||
import { DnsOverHttps } from "./dns_over_https.js";
|
import { DnsOverHttps } from "./dns_over_https.js";
|
||||||
import { ENRTree } from "./enrtree.js";
|
import { ENRTree } from "./enrtree.js";
|
||||||
import {
|
import { fetchNodes } from "./fetch_nodes.js";
|
||||||
fetchNodesUntilCapabilitiesFulfilled,
|
|
||||||
yieldNodesUntilCapabilitiesFulfilled
|
|
||||||
} from "./fetch_nodes.js";
|
|
||||||
|
|
||||||
const log = new Logger("discovery:dns");
|
const log = new Logger("discovery:dns");
|
||||||
|
|
||||||
export class DnsNodeDiscovery {
|
export class DnsNodeDiscovery {
|
||||||
private readonly dns: DnsClient;
|
private readonly dns: DnsClient;
|
||||||
private readonly _DNSTreeCache: { [key: string]: string };
|
private readonly _DNSTreeCache: { [key: string]: string };
|
||||||
private readonly _errorTolerance: number = 10;
|
|
||||||
|
|
||||||
public static async dnsOverHttp(
|
public static async dnsOverHttp(
|
||||||
dnsClient?: DnsClient
|
dnsClient?: DnsClient
|
||||||
@ -30,68 +21,29 @@ export class DnsNodeDiscovery {
|
|||||||
return new DnsNodeDiscovery(dnsClient);
|
return new DnsNodeDiscovery(dnsClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of verified peers listed in an EIP-1459 DNS tree. Method may
|
|
||||||
* return fewer peers than requested if @link wantedNodeCapabilityCount requires
|
|
||||||
* larger quantity of peers than available or the number of errors/duplicate
|
|
||||||
* peers encountered by randomized search exceeds the sum of the fields of
|
|
||||||
* @link wantedNodeCapabilityCount plus the @link _errorTolerance factor.
|
|
||||||
*/
|
|
||||||
public async getPeers(
|
|
||||||
enrTreeUrls: string[],
|
|
||||||
wantedNodeCapabilityCount: Partial<NodeCapabilityCount>
|
|
||||||
): Promise<IEnr[]> {
|
|
||||||
const networkIndex = Math.floor(Math.random() * enrTreeUrls.length);
|
|
||||||
const { publicKey, domain } = ENRTree.parseTree(enrTreeUrls[networkIndex]);
|
|
||||||
const context: SearchContext = {
|
|
||||||
domain,
|
|
||||||
publicKey,
|
|
||||||
visits: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
const peers = await fetchNodesUntilCapabilitiesFulfilled(
|
|
||||||
wantedNodeCapabilityCount,
|
|
||||||
this._errorTolerance,
|
|
||||||
() => this._search(domain, context)
|
|
||||||
);
|
|
||||||
log.info(
|
|
||||||
"retrieved peers: ",
|
|
||||||
peers.map((peer) => {
|
|
||||||
return {
|
|
||||||
id: peer.peerId?.toString(),
|
|
||||||
multiaddrs: peer.multiaddrs?.map((ma) => ma.toString())
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return peers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public constructor(dns: DnsClient) {
|
public constructor(dns: DnsClient) {
|
||||||
this._DNSTreeCache = {};
|
this._DNSTreeCache = {};
|
||||||
this.dns = dns;
|
this.dns = dns;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc getPeers}
|
* Retrieve the next peers from the passed [[enrTreeUrls]],
|
||||||
*/
|
*/
|
||||||
public async *getNextPeer(
|
public async *getNextPeer(enrTreeUrls: string[]): AsyncGenerator<IEnr> {
|
||||||
enrTreeUrls: string[],
|
// Shuffle the ENR Trees so that not all clients connect to same nodes first.
|
||||||
wantedNodeCapabilityCount: Partial<NodeCapabilityCount>
|
for (const enrTreeUrl of shuffle(enrTreeUrls)) {
|
||||||
): AsyncGenerator<IEnr> {
|
const { publicKey, domain } = ENRTree.parseTree(enrTreeUrl);
|
||||||
const networkIndex = Math.floor(Math.random() * enrTreeUrls.length);
|
const context: SearchContext = {
|
||||||
const { publicKey, domain } = ENRTree.parseTree(enrTreeUrls[networkIndex]);
|
domain,
|
||||||
const context: SearchContext = {
|
publicKey,
|
||||||
domain,
|
visits: {}
|
||||||
publicKey,
|
};
|
||||||
visits: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
for await (const peer of yieldNodesUntilCapabilitiesFulfilled(
|
for await (const peer of fetchNodes(() =>
|
||||||
wantedNodeCapabilityCount,
|
this._search(domain, context)
|
||||||
this._errorTolerance,
|
)) {
|
||||||
() => this._search(domain, context)
|
yield peer;
|
||||||
)) {
|
}
|
||||||
yield peer;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,7 +117,7 @@ export class DnsNodeDiscovery {
|
|||||||
throw new Error("Received empty result array while fetching TXT record");
|
throw new Error("Received empty result array while fetching TXT record");
|
||||||
if (!response[0].length) throw new Error("Received empty 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
|
// some subdomain strings split across the array elements
|
||||||
const result = response.join("");
|
const result = response.join("");
|
||||||
|
|
||||||
|
|||||||
@ -9,8 +9,7 @@ import type {
|
|||||||
DiscoveryTrigger,
|
DiscoveryTrigger,
|
||||||
DnsDiscOptions,
|
DnsDiscOptions,
|
||||||
DnsDiscoveryComponents,
|
DnsDiscoveryComponents,
|
||||||
IEnr,
|
IEnr
|
||||||
NodeCapabilityCount
|
|
||||||
} from "@waku/interfaces";
|
} from "@waku/interfaces";
|
||||||
import { DNS_DISCOVERY_TAG } from "@waku/interfaces";
|
import { DNS_DISCOVERY_TAG } from "@waku/interfaces";
|
||||||
import { encodeRelayShard, Logger } from "@waku/utils";
|
import { encodeRelayShard, Logger } from "@waku/utils";
|
||||||
@ -18,8 +17,7 @@ import { encodeRelayShard, Logger } from "@waku/utils";
|
|||||||
import {
|
import {
|
||||||
DEFAULT_BOOTSTRAP_TAG_NAME,
|
DEFAULT_BOOTSTRAP_TAG_NAME,
|
||||||
DEFAULT_BOOTSTRAP_TAG_TTL,
|
DEFAULT_BOOTSTRAP_TAG_TTL,
|
||||||
DEFAULT_BOOTSTRAP_TAG_VALUE,
|
DEFAULT_BOOTSTRAP_TAG_VALUE
|
||||||
DEFAULT_NODE_REQUIREMENTS
|
|
||||||
} from "./constants.js";
|
} from "./constants.js";
|
||||||
import { DnsNodeDiscovery } from "./dns.js";
|
import { DnsNodeDiscovery } from "./dns.js";
|
||||||
|
|
||||||
@ -35,7 +33,7 @@ export class PeerDiscoveryDns
|
|||||||
private nextPeer: (() => AsyncGenerator<IEnr>) | undefined;
|
private nextPeer: (() => AsyncGenerator<IEnr>) | undefined;
|
||||||
private _started: boolean;
|
private _started: boolean;
|
||||||
private _components: DnsDiscoveryComponents;
|
private _components: DnsDiscoveryComponents;
|
||||||
private _options: DnsDiscOptions;
|
private readonly _options: DnsDiscOptions;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
components: DnsDiscoveryComponents,
|
components: DnsDiscoveryComponents,
|
||||||
@ -65,14 +63,9 @@ export class PeerDiscoveryDns
|
|||||||
let { enrUrls } = this._options;
|
let { enrUrls } = this._options;
|
||||||
if (!Array.isArray(enrUrls)) enrUrls = [enrUrls];
|
if (!Array.isArray(enrUrls)) enrUrls = [enrUrls];
|
||||||
|
|
||||||
const { wantedNodeCapabilityCount } = this._options;
|
|
||||||
const dns = await DnsNodeDiscovery.dnsOverHttp();
|
const dns = await DnsNodeDiscovery.dnsOverHttp();
|
||||||
|
|
||||||
this.nextPeer = dns.getNextPeer.bind(
|
this.nextPeer = dns.getNextPeer.bind(dns, enrUrls);
|
||||||
dns,
|
|
||||||
enrUrls,
|
|
||||||
wantedNodeCapabilityCount
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for await (const peerEnr of this.nextPeer()) {
|
for await (const peerEnr of this.nextPeer()) {
|
||||||
@ -94,9 +87,11 @@ export class PeerDiscoveryDns
|
|||||||
};
|
};
|
||||||
|
|
||||||
let isPeerChanged = false;
|
let isPeerChanged = false;
|
||||||
const isPeerExists = await this._components.peerStore.has(peerInfo.id);
|
const isPeerAlreadyInPeerStore = await this._components.peerStore.has(
|
||||||
|
peerInfo.id
|
||||||
|
);
|
||||||
|
|
||||||
if (isPeerExists) {
|
if (isPeerAlreadyInPeerStore) {
|
||||||
const peer = await this._components.peerStore.get(peerInfo.id);
|
const peer = await this._components.peerStore.get(peerInfo.id);
|
||||||
const hasBootstrapTag = peer.tags.has(DEFAULT_BOOTSTRAP_TAG_NAME);
|
const hasBootstrapTag = peer.tags.has(DEFAULT_BOOTSTRAP_TAG_NAME);
|
||||||
|
|
||||||
@ -143,9 +138,8 @@ export class PeerDiscoveryDns
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function wakuDnsDiscovery(
|
export function wakuDnsDiscovery(
|
||||||
enrUrls: string[],
|
enrUrls: string[]
|
||||||
wantedNodeCapabilityCount: Partial<NodeCapabilityCount> = DEFAULT_NODE_REQUIREMENTS
|
|
||||||
): (components: DnsDiscoveryComponents) => PeerDiscoveryDns {
|
): (components: DnsDiscoveryComponents) => PeerDiscoveryDns {
|
||||||
return (components: DnsDiscoveryComponents) =>
|
return (components: DnsDiscoveryComponents) =>
|
||||||
new PeerDiscoveryDns(components, { enrUrls, wantedNodeCapabilityCount });
|
new PeerDiscoveryDns(components, { enrUrls });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,12 +3,11 @@ import { peerIdFromPrivateKey } from "@libp2p/peer-id";
|
|||||||
import { multiaddr } from "@multiformats/multiaddr";
|
import { multiaddr } from "@multiformats/multiaddr";
|
||||||
import { ENR } from "@waku/enr";
|
import { ENR } from "@waku/enr";
|
||||||
import { EnrCreator } from "@waku/enr";
|
import { EnrCreator } from "@waku/enr";
|
||||||
import type { Waku2 } from "@waku/interfaces";
|
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
|
||||||
import { fetchNodesUntilCapabilitiesFulfilled } from "./fetch_nodes.js";
|
import { fetchNodes } from "./fetch_nodes.js";
|
||||||
|
|
||||||
async function createEnr(waku2: Waku2): Promise<ENR> {
|
async function createEnr(): Promise<ENR> {
|
||||||
const peerId = await generateKeyPair("secp256k1").then(peerIdFromPrivateKey);
|
const peerId = await generateKeyPair("secp256k1").then(peerIdFromPrivateKey);
|
||||||
const enr = await EnrCreator.fromPeerId(peerId);
|
const enr = await EnrCreator.fromPeerId(peerId);
|
||||||
enr.setLocationMultiaddr(multiaddr("/ip4/18.223.219.100/udp/9000"));
|
enr.setLocationMultiaddr(multiaddr("/ip4/18.223.219.100/udp/9000"));
|
||||||
@ -20,38 +19,13 @@ async function createEnr(waku2: Waku2): Promise<ENR> {
|
|||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
enr.waku2 = waku2;
|
enr.waku2 = { lightPush: true, filter: true, relay: false, store: false };
|
||||||
return enr;
|
return enr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Waku2None = {
|
describe("Fetch nodes", function () {
|
||||||
relay: false,
|
it("Get Nodes", async function () {
|
||||||
store: false,
|
const retrievedNodes = [await createEnr(), await createEnr()];
|
||||||
filter: false,
|
|
||||||
lightPush: false
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("Fetch nodes until capabilities are fulfilled", function () {
|
|
||||||
it("1 Relay, 1 fetch", async function () {
|
|
||||||
const relayNode = await createEnr({ ...Waku2None, relay: true });
|
|
||||||
|
|
||||||
const getNode = (): Promise<ENR> => Promise.resolve(relayNode);
|
|
||||||
|
|
||||||
const res = await fetchNodesUntilCapabilitiesFulfilled(
|
|
||||||
{ relay: 1 },
|
|
||||||
0,
|
|
||||||
getNode
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(res.length).to.eq(1);
|
|
||||||
expect(res[0].peerId!.toString()).to.eq(relayNode.peerId?.toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
it("1 Store, 2 fetches", async function () {
|
|
||||||
const relayNode = await createEnr({ ...Waku2None, relay: true });
|
|
||||||
const storeNode = await createEnr({ ...Waku2None, store: true });
|
|
||||||
|
|
||||||
const retrievedNodes = [relayNode, storeNode];
|
|
||||||
|
|
||||||
let fetchCount = 0;
|
let fetchCount = 0;
|
||||||
const getNode = (): Promise<ENR> => {
|
const getNode = (): Promise<ENR> => {
|
||||||
@ -60,27 +34,21 @@ describe("Fetch nodes until capabilities are fulfilled", function () {
|
|||||||
return Promise.resolve(node);
|
return Promise.resolve(node);
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await fetchNodesUntilCapabilitiesFulfilled(
|
const res = [];
|
||||||
{ store: 1 },
|
for await (const node of fetchNodes(getNode, 5)) {
|
||||||
1,
|
res.push(node);
|
||||||
getNode
|
}
|
||||||
);
|
|
||||||
|
|
||||||
expect(res.length).to.eq(1);
|
expect(res.length).to.eq(2);
|
||||||
expect(res[0].peerId!.toString()).to.eq(storeNode.peerId?.toString());
|
expect(res[0].peerId!.toString()).to.not.eq(res[1].peerId!.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
it("1 Store, 2 relays, 2 fetches", async function () {
|
it("Stops search when maxGet is reached", async function () {
|
||||||
const relayNode1 = await createEnr({ ...Waku2None, relay: true });
|
const retrievedNodes = [
|
||||||
const relayNode2 = await createEnr({ ...Waku2None, relay: true });
|
await createEnr(),
|
||||||
const relayNode3 = await createEnr({ ...Waku2None, relay: true });
|
await createEnr(),
|
||||||
const relayStoreNode = await createEnr({
|
await createEnr()
|
||||||
...Waku2None,
|
];
|
||||||
relay: true,
|
|
||||||
store: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const retrievedNodes = [relayNode1, relayNode2, relayNode3, relayStoreNode];
|
|
||||||
|
|
||||||
let fetchCount = 0;
|
let fetchCount = 0;
|
||||||
const getNode = (): Promise<ENR> => {
|
const getNode = (): Promise<ENR> => {
|
||||||
@ -89,30 +57,29 @@ describe("Fetch nodes until capabilities are fulfilled", function () {
|
|||||||
return Promise.resolve(node);
|
return Promise.resolve(node);
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await fetchNodesUntilCapabilitiesFulfilled(
|
const res = [];
|
||||||
{ store: 1, relay: 2 },
|
for await (const node of fetchNodes(getNode, 2)) {
|
||||||
1,
|
res.push(node);
|
||||||
getNode
|
}
|
||||||
);
|
|
||||||
|
|
||||||
expect(res.length).to.eq(3);
|
expect(res.length).to.eq(2);
|
||||||
expect(res[0].peerId!.toString()).to.eq(relayNode1.peerId?.toString());
|
|
||||||
expect(res[1].peerId!.toString()).to.eq(relayNode2.peerId?.toString());
|
|
||||||
expect(res[2].peerId!.toString()).to.eq(relayStoreNode.peerId?.toString());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("1 Relay, 1 Filter, gives up", async function () {
|
it("Stops search when 2 null results are returned", async function () {
|
||||||
const relayNode = await createEnr({ ...Waku2None, relay: true });
|
const retrievedNodes = [await createEnr(), null, null, await createEnr()];
|
||||||
|
|
||||||
const getNode = (): Promise<ENR> => Promise.resolve(relayNode);
|
let fetchCount = 0;
|
||||||
|
const getNode = (): Promise<ENR | null> => {
|
||||||
|
const node = retrievedNodes[fetchCount];
|
||||||
|
fetchCount++;
|
||||||
|
return Promise.resolve(node);
|
||||||
|
};
|
||||||
|
|
||||||
const res = await fetchNodesUntilCapabilitiesFulfilled(
|
const res = [];
|
||||||
{ filter: 1, relay: 1 },
|
for await (const node of fetchNodes(getNode, 10, 2)) {
|
||||||
5,
|
res.push(node);
|
||||||
getNode
|
}
|
||||||
);
|
|
||||||
|
|
||||||
expect(res.length).to.eq(1);
|
expect(res.length).to.eq(1);
|
||||||
expect(res[0].peerId!.toString()).to.eq(relayNode.peerId?.toString());
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,181 +1,44 @@
|
|||||||
import type { IEnr, NodeCapabilityCount, Waku2 } from "@waku/interfaces";
|
import type { IEnr } from "@waku/interfaces";
|
||||||
import { Logger } from "@waku/utils";
|
import { Logger } from "@waku/utils";
|
||||||
|
|
||||||
const log = new Logger("discovery:fetch_nodes");
|
const log = new Logger("discovery:fetch_nodes");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch nodes using passed [[getNode]] until all wanted capabilities are
|
* Fetch nodes using passed [[getNode]] until it has been called [[maxGet]]
|
||||||
* fulfilled or the number of [[getNode]] call exceeds the sum of
|
* times, or it has returned empty or duplicate results more than [[maxErrors]]
|
||||||
* [[wantedNodeCapabilityCount]] plus [[errorTolerance]].
|
* times.
|
||||||
*/
|
*/
|
||||||
export async function fetchNodesUntilCapabilitiesFulfilled(
|
export async function* fetchNodes(
|
||||||
wantedNodeCapabilityCount: Partial<NodeCapabilityCount>,
|
getNode: () => Promise<IEnr | null>,
|
||||||
errorTolerance: number,
|
maxGet: number = 10,
|
||||||
getNode: () => Promise<IEnr | null>
|
maxErrors: number = 3
|
||||||
): Promise<IEnr[]> {
|
|
||||||
const wanted = {
|
|
||||||
relay: wantedNodeCapabilityCount.relay ?? 0,
|
|
||||||
store: wantedNodeCapabilityCount.store ?? 0,
|
|
||||||
filter: wantedNodeCapabilityCount.filter ?? 0,
|
|
||||||
lightPush: wantedNodeCapabilityCount.lightPush ?? 0
|
|
||||||
};
|
|
||||||
|
|
||||||
const maxSearches =
|
|
||||||
wanted.relay + wanted.store + wanted.filter + wanted.lightPush;
|
|
||||||
|
|
||||||
const actual = {
|
|
||||||
relay: 0,
|
|
||||||
store: 0,
|
|
||||||
filter: 0,
|
|
||||||
lightPush: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
let totalSearches = 0;
|
|
||||||
const peers: IEnr[] = [];
|
|
||||||
|
|
||||||
while (
|
|
||||||
!isSatisfied(wanted, actual) &&
|
|
||||||
totalSearches < maxSearches + errorTolerance
|
|
||||||
) {
|
|
||||||
const peer = await getNode();
|
|
||||||
if (peer && isNewPeer(peer, peers)) {
|
|
||||||
// ENRs without a waku2 key are ignored.
|
|
||||||
if (peer.waku2) {
|
|
||||||
if (helpsSatisfyCapabilities(peer.waku2, wanted, actual)) {
|
|
||||||
addCapabilities(peer.waku2, actual);
|
|
||||||
peers.push(peer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.info(
|
|
||||||
`got new peer candidate from DNS address=${peer.nodeId}@${peer.ip}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
totalSearches++;
|
|
||||||
}
|
|
||||||
return peers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch nodes using passed [[getNode]] until all wanted capabilities are
|
|
||||||
* fulfilled or the number of [[getNode]] call exceeds the sum of
|
|
||||||
* [[wantedNodeCapabilityCount]] plus [[errorTolerance]].
|
|
||||||
*/
|
|
||||||
export async function* yieldNodesUntilCapabilitiesFulfilled(
|
|
||||||
wantedNodeCapabilityCount: Partial<NodeCapabilityCount>,
|
|
||||||
errorTolerance: number,
|
|
||||||
getNode: () => Promise<IEnr | null>
|
|
||||||
): AsyncGenerator<IEnr> {
|
): AsyncGenerator<IEnr> {
|
||||||
const wanted = {
|
|
||||||
relay: wantedNodeCapabilityCount.relay ?? 0,
|
|
||||||
store: wantedNodeCapabilityCount.store ?? 0,
|
|
||||||
filter: wantedNodeCapabilityCount.filter ?? 0,
|
|
||||||
lightPush: wantedNodeCapabilityCount.lightPush ?? 0
|
|
||||||
};
|
|
||||||
|
|
||||||
const maxSearches =
|
|
||||||
wanted.relay + wanted.store + wanted.filter + wanted.lightPush;
|
|
||||||
|
|
||||||
const actual = {
|
|
||||||
relay: 0,
|
|
||||||
store: 0,
|
|
||||||
filter: 0,
|
|
||||||
lightPush: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
let totalSearches = 0;
|
|
||||||
const peerNodeIds = new Set();
|
const peerNodeIds = new Set();
|
||||||
|
|
||||||
|
let totalSearches = 0;
|
||||||
|
let erroneousSearches = 0;
|
||||||
|
|
||||||
while (
|
while (
|
||||||
!isSatisfied(wanted, actual) &&
|
totalSearches < maxGet &&
|
||||||
totalSearches < maxSearches + errorTolerance
|
erroneousSearches < maxErrors // Allows a couple of empty results before calling it quit
|
||||||
) {
|
) {
|
||||||
|
totalSearches++;
|
||||||
|
|
||||||
const peer = await getNode();
|
const peer = await getNode();
|
||||||
if (peer && peer.nodeId && !peerNodeIds.has(peer.nodeId)) {
|
if (!peer || !peer.nodeId) {
|
||||||
|
erroneousSearches++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!peerNodeIds.has(peer.nodeId)) {
|
||||||
peerNodeIds.add(peer.nodeId);
|
peerNodeIds.add(peer.nodeId);
|
||||||
// ENRs without a waku2 key are ignored.
|
// ENRs without a waku2 key are ignored.
|
||||||
if (peer.waku2) {
|
if (peer.waku2) {
|
||||||
if (helpsSatisfyCapabilities(peer.waku2, wanted, actual)) {
|
yield peer;
|
||||||
addCapabilities(peer.waku2, actual);
|
|
||||||
yield peer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
log.info(
|
log.info(
|
||||||
`got new peer candidate from DNS address=${peer.nodeId}@${peer.ip}`
|
`got new peer candidate from DNS address=${peer.nodeId}@${peer.ip}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
totalSearches++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSatisfied(
|
|
||||||
wanted: NodeCapabilityCount,
|
|
||||||
actual: NodeCapabilityCount
|
|
||||||
): boolean {
|
|
||||||
return (
|
|
||||||
actual.relay >= wanted.relay &&
|
|
||||||
actual.store >= wanted.store &&
|
|
||||||
actual.filter >= wanted.filter &&
|
|
||||||
actual.lightPush >= wanted.lightPush
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isNewPeer(peer: IEnr, peers: IEnr[]): boolean {
|
|
||||||
if (!peer.nodeId) return false;
|
|
||||||
|
|
||||||
for (const existingPeer of peers) {
|
|
||||||
if (peer.nodeId === existingPeer.nodeId) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addCapabilities(node: Waku2, total: NodeCapabilityCount): void {
|
|
||||||
if (node.relay) total.relay += 1;
|
|
||||||
if (node.store) total.store += 1;
|
|
||||||
if (node.filter) total.filter += 1;
|
|
||||||
if (node.lightPush) total.lightPush += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the proposed ENR [[node]] helps satisfy the [[wanted]] capabilities,
|
|
||||||
* considering the [[actual]] capabilities of nodes retrieved so far..
|
|
||||||
*
|
|
||||||
* @throws If the function is called when the wanted capabilities are already fulfilled.
|
|
||||||
*/
|
|
||||||
function helpsSatisfyCapabilities(
|
|
||||||
node: Waku2,
|
|
||||||
wanted: NodeCapabilityCount,
|
|
||||||
actual: NodeCapabilityCount
|
|
||||||
): boolean {
|
|
||||||
if (isSatisfied(wanted, actual)) {
|
|
||||||
throw "Internal Error: Waku2 wanted capabilities are already fulfilled";
|
|
||||||
}
|
|
||||||
|
|
||||||
const missing = missingCapabilities(wanted, actual);
|
|
||||||
|
|
||||||
return (
|
|
||||||
(missing.relay && node.relay) ||
|
|
||||||
(missing.store && node.store) ||
|
|
||||||
(missing.filter && node.filter) ||
|
|
||||||
(missing.lightPush && node.lightPush)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a [[Waku2]] Object for which capabilities are set to true if they are
|
|
||||||
* [[wanted]] yet missing from [[actual]].
|
|
||||||
*/
|
|
||||||
function missingCapabilities(
|
|
||||||
wanted: NodeCapabilityCount,
|
|
||||||
actual: NodeCapabilityCount
|
|
||||||
): Waku2 {
|
|
||||||
return {
|
|
||||||
relay: actual.relay < wanted.relay,
|
|
||||||
store: actual.store < wanted.store,
|
|
||||||
filter: actual.filter < wanted.filter,
|
|
||||||
lightPush: actual.lightPush < wanted.lightPush
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@ -12,13 +12,6 @@ export interface DnsClient {
|
|||||||
resolveTXT: (domain: string) => Promise<string[]>;
|
resolveTXT: (domain: string) => Promise<string[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NodeCapabilityCount {
|
|
||||||
relay: number;
|
|
||||||
store: number;
|
|
||||||
filter: number;
|
|
||||||
lightPush: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DnsDiscoveryComponents {
|
export interface DnsDiscoveryComponents {
|
||||||
peerStore: PeerStore;
|
peerStore: PeerStore;
|
||||||
}
|
}
|
||||||
@ -28,10 +21,7 @@ export interface DnsDiscOptions {
|
|||||||
* ENR URL to use for DNS discovery
|
* ENR URL to use for DNS discovery
|
||||||
*/
|
*/
|
||||||
enrUrls: string | string[];
|
enrUrls: string | string[];
|
||||||
/**
|
|
||||||
* Specifies what type of nodes are wanted from the discovery process
|
|
||||||
*/
|
|
||||||
wantedNodeCapabilityCount: Partial<NodeCapabilityCount>;
|
|
||||||
/**
|
/**
|
||||||
* Tag a bootstrap peer with this name before "discovering" it (default: 'bootstrap')
|
* Tag a bootstrap peer with this name before "discovering" it (default: 'bootstrap')
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -36,10 +36,7 @@ describe("DNS Discovery: Compliance Test", function () {
|
|||||||
} as unknown as Libp2pComponents;
|
} as unknown as Libp2pComponents;
|
||||||
|
|
||||||
return new PeerDiscoveryDns(components, {
|
return new PeerDiscoveryDns(components, {
|
||||||
enrUrls: [enrTree["SANDBOX"]],
|
enrUrls: [enrTree["SANDBOX"]]
|
||||||
wantedNodeCapabilityCount: {
|
|
||||||
filter: 1
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async teardown() {
|
async teardown() {
|
||||||
@ -57,20 +54,11 @@ describe("DNS Node Discovery [live data]", function () {
|
|||||||
|
|
||||||
it(`should use DNS peer discovery with light client`, async function () {
|
it(`should use DNS peer discovery with light client`, async function () {
|
||||||
this.timeout(100000);
|
this.timeout(100000);
|
||||||
const maxQuantity = 3;
|
const minQuantityExpected = 3; // We have at least 3 nodes in Waku Sandbox ENR tree
|
||||||
|
|
||||||
const nodeRequirements = {
|
|
||||||
relay: maxQuantity,
|
|
||||||
store: maxQuantity,
|
|
||||||
filter: maxQuantity,
|
|
||||||
lightPush: maxQuantity
|
|
||||||
};
|
|
||||||
|
|
||||||
const waku = await createLightNode({
|
const waku = await createLightNode({
|
||||||
libp2p: {
|
libp2p: {
|
||||||
peerDiscovery: [
|
peerDiscovery: [wakuDnsDiscovery([enrTree["SANDBOX"]])]
|
||||||
wakuDnsDiscovery([enrTree["SANDBOX"]], nodeRequirements)
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -86,22 +74,22 @@ describe("DNS Node Discovery [live data]", function () {
|
|||||||
}
|
}
|
||||||
expect(hasTag).to.be.eq(true);
|
expect(hasTag).to.be.eq(true);
|
||||||
}
|
}
|
||||||
expect(dnsPeers).to.eq(maxQuantity);
|
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();
|
if (process.env.CI) this.skip();
|
||||||
|
|
||||||
this.timeout(10000);
|
this.timeout(10000);
|
||||||
// Google's dns server address. Needs to be set explicitly to run in CI
|
// Google's dns server address. Needs to be set explicitly to run in CI
|
||||||
const dnsNodeDiscovery = await DnsNodeDiscovery.dnsOverHttp();
|
const dnsNodeDiscovery = await DnsNodeDiscovery.dnsOverHttp();
|
||||||
|
|
||||||
const peers = await dnsNodeDiscovery.getPeers([enrTree["SANDBOX"]], {
|
const peers = [];
|
||||||
relay: maxQuantity,
|
for await (const peer of dnsNodeDiscovery.getNextPeer([
|
||||||
store: maxQuantity,
|
enrTree["SANDBOX"]
|
||||||
filter: maxQuantity,
|
])) {
|
||||||
lightPush: maxQuantity
|
peers.push(peer);
|
||||||
});
|
}
|
||||||
|
|
||||||
expect(peers.length).to.eq(maxQuantity);
|
expect(peers.length).to.eq(maxQuantity);
|
||||||
|
|
||||||
@ -114,28 +102,52 @@ describe("DNS Node Discovery [live data]", function () {
|
|||||||
seen.push(ma!.toString());
|
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 () {
|
it("passes more than one ENR URLs and attempts connection", async function () {
|
||||||
if (process.env.CI) this.skip();
|
if (process.env.CI) this.skip();
|
||||||
this.timeout(30_000);
|
this.timeout(30_000);
|
||||||
|
|
||||||
const nodesToConnect = 2;
|
const minQuantityExpected = 2;
|
||||||
|
|
||||||
const waku = await createLightNode({
|
const waku = await createLightNode({
|
||||||
libp2p: {
|
libp2p: {
|
||||||
peerDiscovery: [
|
peerDiscovery: [wakuDnsDiscovery([enrTree["SANDBOX"], enrTree["TEST"]])]
|
||||||
wakuDnsDiscovery([enrTree["SANDBOX"], enrTree["TEST"]], {
|
|
||||||
filter: nodesToConnect
|
|
||||||
})
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await waku.start();
|
await waku.start();
|
||||||
|
|
||||||
const allPeers = await waku.libp2p.peerStore.all();
|
const allPeers = await waku.libp2p.peerStore.all();
|
||||||
while (allPeers.length < nodesToConnect) {
|
while (allPeers.length < minQuantityExpected) {
|
||||||
await delay(2000);
|
await delay(2000);
|
||||||
}
|
}
|
||||||
expect(allPeers.length).to.be.eq(nodesToConnect);
|
expect(allPeers.length).to.be.gte(minQuantityExpected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -23,12 +23,10 @@ describe("Peer Exchange", () => {
|
|||||||
this.timeout(50_000);
|
this.timeout(50_000);
|
||||||
|
|
||||||
const dns = await DnsNodeDiscovery.dnsOverHttp();
|
const dns = await DnsNodeDiscovery.dnsOverHttp();
|
||||||
const dnsEnrs = await dns.getPeers(
|
const dnsEnrs = [];
|
||||||
[enrTree["SANDBOX"], enrTree["TEST"]],
|
for await (const node of dns.getNextPeer([enrTree["SANDBOX"]])) {
|
||||||
{
|
dnsEnrs.push(node);
|
||||||
lightPush: 1
|
}
|
||||||
}
|
|
||||||
);
|
|
||||||
const dnsPeerMultiaddrs = dnsEnrs
|
const dnsPeerMultiaddrs = dnsEnrs
|
||||||
.flatMap(
|
.flatMap(
|
||||||
(enr) => enr.peerInfo?.multiaddrs.map((ma) => ma.toString()) ?? []
|
(enr) => enr.peerInfo?.multiaddrs.map((ma) => ma.toString()) ?? []
|
||||||
|
|||||||
@ -9,17 +9,9 @@ describe("Use static and several ENR trees for bootstrap", function () {
|
|||||||
it("", async function () {
|
it("", async function () {
|
||||||
this.timeout(10_000);
|
this.timeout(10_000);
|
||||||
|
|
||||||
const NODE_REQUIREMENTS = {
|
|
||||||
store: 3,
|
|
||||||
lightPush: 3,
|
|
||||||
filter: 3
|
|
||||||
};
|
|
||||||
|
|
||||||
waku = await createLightNode({
|
waku = await createLightNode({
|
||||||
libp2p: {
|
libp2p: {
|
||||||
peerDiscovery: [
|
peerDiscovery: [wakuDnsDiscovery([enrTree["SANDBOX"]])]
|
||||||
wakuDnsDiscovery([enrTree["SANDBOX"]], NODE_REQUIREMENTS)
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await waku.start();
|
await waku.start();
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export function getPseudoRandomSubset<T>(
|
|||||||
return shuffle(values).slice(0, wantedNumber);
|
return shuffle(values).slice(0, wantedNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
function shuffle<T>(arr: T[]): T[] {
|
export function shuffle<T>(arr: T[]): T[] {
|
||||||
if (arr.length <= 1) {
|
if (arr.length <= 1) {
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user