mirror of
https://github.com/logos-messaging/logos-delivery-js.git
synced 2026-04-22 08:43:10 +00:00
Nodes returned by DNS Discovery are not guaranteed to be reachable. Hence, setting an upfront limit based on sought capability does not provide any guarantees that such capability should be reached. The discovery's mechanism role is to find Waku 2 nodes. As many as possible. It is then the role of the peer manager to decide: - whether to attempt connecting to the node based on advertised capabilities - retain connection to the node based on actual mounted protocols. We still want to prevent infinite loops, hence the `maxGet` parameter. Also, there was a dichotomy between code tested, and code actually used by libp2p peer discovery, now resolved. # Conflicts: # packages/discovery/src/dns/constants.ts
288 lines
8.8 KiB
TypeScript
288 lines
8.8 KiB
TypeScript
import type { DnsClient } from "@waku/interfaces";
|
|
import { expect } from "chai";
|
|
|
|
import { DnsNodeDiscovery } from "./dns.js";
|
|
import testData from "./testdata.json" with { type: "json" };
|
|
|
|
import { enrTree } from "./index.js";
|
|
|
|
const mockData = testData.dns;
|
|
|
|
const host = "nodes.example.org";
|
|
const rootDomain = "JORXBYVVM7AEKETX5DGXW44EAY";
|
|
const branchDomainA = "D2SNLTAGWNQ34NTQTPHNZDECFU";
|
|
const branchDomainB = "D3SNLTAGWNQ34NTQTPHNZDECFU";
|
|
const branchDomainC = "D4SNLTAGWNQ34NTQTPHNZDECFU";
|
|
const branchDomainD = "D5SNLTAGWNQ34NTQTPHNZDECFU";
|
|
const partialBranchA = "AAAA";
|
|
const partialBranchB = "BBBB";
|
|
const singleBranch = `enrtree-branch:${branchDomainA}`;
|
|
const multiComponentBranch = [
|
|
`enrtree-branch:${branchDomainA},${partialBranchA}`,
|
|
`${partialBranchB},${branchDomainB}`
|
|
];
|
|
|
|
// Note: once td.when is asked to throw for an input it will always throw.
|
|
// Input can't be re-used for a passing case.
|
|
const errorBranchA = `enrtree-branch:${branchDomainC}`;
|
|
const errorBranchB = `enrtree-branch:${branchDomainD}`;
|
|
|
|
/**
|
|
* Mocks DNS resolution.
|
|
*/
|
|
class MockDNS implements DnsClient {
|
|
private fqdnRes: Map<string, string[]>;
|
|
private fqdnThrows: string[];
|
|
|
|
public constructor() {
|
|
this.fqdnRes = new Map();
|
|
this.fqdnThrows = [];
|
|
}
|
|
|
|
public addRes(fqdn: string, res: string[]): void {
|
|
this.fqdnRes.set(fqdn, res);
|
|
}
|
|
|
|
public addThrow(fqdn: string): void {
|
|
this.fqdnThrows.push(fqdn);
|
|
}
|
|
|
|
public resolveTXT(fqdn: string): Promise<string[]> {
|
|
if (this.fqdnThrows.includes(fqdn)) throw "Mock DNS throws.";
|
|
|
|
const res = this.fqdnRes.get(fqdn);
|
|
|
|
if (!res) throw `Mock DNS could not resolve ${fqdn}`;
|
|
|
|
return Promise.resolve(res);
|
|
}
|
|
}
|
|
|
|
describe("DNS Node Discovery", () => {
|
|
let mockDns: MockDNS;
|
|
|
|
beforeEach(() => {
|
|
mockDns = new MockDNS();
|
|
mockDns.addRes(host, [mockData.enrRoot]);
|
|
});
|
|
|
|
it("retrieves a single peer", async function () {
|
|
mockDns.addRes(`${rootDomain}.${host}`, [singleBranch]);
|
|
mockDns.addRes(`${branchDomainA}.${host}`, [mockData.enrWithWaku2Relay]);
|
|
|
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
|
const peers = [];
|
|
for await (const peer of dnsNodeDiscovery.getNextPeer([mockData.enrTree])) {
|
|
peers.push(peer);
|
|
}
|
|
|
|
expect(peers.length).to.eq(1);
|
|
expect(peers[0].ip).to.eq("192.168.178.251");
|
|
expect(peers[0].tcp).to.eq(8002);
|
|
});
|
|
|
|
it("it tolerates circular branch references", async function () {
|
|
// root --> branchA
|
|
// branchA --> branchA
|
|
mockDns.addRes(`${rootDomain}.${host}`, [singleBranch]);
|
|
mockDns.addRes(`${branchDomainA}.${host}`, [singleBranch]);
|
|
|
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
|
const peers = [];
|
|
for await (const peer of dnsNodeDiscovery.getNextPeer([mockData.enrTree])) {
|
|
peers.push(peer);
|
|
}
|
|
|
|
expect(peers.length).to.eq(0);
|
|
});
|
|
|
|
it("recovers when dns.resolve returns empty", async function () {
|
|
mockDns.addRes(`${rootDomain}.${host}`, [singleBranch]);
|
|
|
|
// Empty response case
|
|
mockDns.addRes(`${branchDomainA}.${host}`, []);
|
|
|
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
|
const peersA = [];
|
|
for await (const peer of dnsNodeDiscovery.getNextPeer([mockData.enrTree])) {
|
|
peersA.push(peer);
|
|
}
|
|
|
|
expect(peersA.length).to.eq(0);
|
|
|
|
// No TXT records case
|
|
mockDns.addRes(`${branchDomainA}.${host}`, []);
|
|
|
|
const peersB = [];
|
|
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 () {
|
|
mockDns.addRes(`${rootDomain}.${host}`, [errorBranchA]);
|
|
mockDns.addThrow(`${branchDomainC}.${host}`);
|
|
|
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
|
const peers = [];
|
|
for await (const peer of dnsNodeDiscovery.getNextPeer([mockData.enrTree])) {
|
|
peers.push(peer);
|
|
}
|
|
expect(peers.length).to.eq(0);
|
|
});
|
|
|
|
it("ignores unrecognized TXT record formats", async function () {
|
|
mockDns.addRes(`${rootDomain}.${host}`, [mockData.enrBranchBadPrefix]);
|
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
|
const peers = [];
|
|
for await (const peer of dnsNodeDiscovery.getNextPeer([mockData.enrTree])) {
|
|
peers.push(peer);
|
|
}
|
|
expect(peers.length).to.eq(0);
|
|
});
|
|
|
|
it("caches peers it previously fetched", async function () {
|
|
mockDns.addRes(`${rootDomain}.${host}`, [errorBranchB]);
|
|
mockDns.addRes(`${branchDomainD}.${host}`, [mockData.enrWithWaku2Relay]);
|
|
|
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
|
const peersA = [];
|
|
for await (const peer of dnsNodeDiscovery.getNextPeer([mockData.enrTree])) {
|
|
peersA.push(peer);
|
|
}
|
|
expect(peersA.length).to.eq(1);
|
|
|
|
// Specify that a subsequent network call retrieving the same peer should throw.
|
|
// This test passes only if the peer is fetched from cache
|
|
mockDns.addThrow(`${branchDomainD}.${host}`);
|
|
|
|
const peersB = [];
|
|
for await (const peer of dnsNodeDiscovery.getNextPeer([mockData.enrTree])) {
|
|
peersB.push(peer);
|
|
}
|
|
expect(peersB.length).to.eq(1);
|
|
expect(peersA[0].ip).to.eq(peersB[0].ip);
|
|
});
|
|
});
|
|
|
|
describe("DNS Node Discovery w/ capabilities", () => {
|
|
let mockDns: MockDNS;
|
|
|
|
beforeEach(() => {
|
|
mockDns = new MockDNS();
|
|
mockDns.addRes(host, [mockData.enrRoot]);
|
|
});
|
|
|
|
it("should only return 1 node with relay capability", async () => {
|
|
mockDns.addRes(`${rootDomain}.${host}`, [mockData.enrWithWaku2Relay]);
|
|
|
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
|
const peers = [];
|
|
for await (const peer of dnsNodeDiscovery.getNextPeer([mockData.enrTree])) {
|
|
peers.push(peer);
|
|
}
|
|
|
|
expect(peers.length).to.eq(1);
|
|
expect(peers[0].peerId?.toString()).to.eq(
|
|
"16Uiu2HAmPsYLvfKafxgRsb6tioYyGnSvGXS2iuMigptHrqHPNPzx"
|
|
);
|
|
});
|
|
|
|
it("should only return 1 node with relay and store capability", async () => {
|
|
mockDns.addRes(`${rootDomain}.${host}`, [mockData.enrWithWaku2RelayStore]);
|
|
|
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
|
const peers = [];
|
|
for await (const peer of dnsNodeDiscovery.getNextPeer([mockData.enrTree])) {
|
|
peers.push(peer);
|
|
}
|
|
|
|
expect(peers.length).to.eq(1);
|
|
expect(peers[0].peerId?.toString()).to.eq(
|
|
"16Uiu2HAm2HyS6brcCspSbszG9i36re2bWBVjMe3tMdnFp1Hua34F"
|
|
);
|
|
});
|
|
|
|
it("retrieves all peers (3) when branch entries are composed of multiple strings", async function () {
|
|
mockDns.addRes(`${rootDomain}.${host}`, multiComponentBranch);
|
|
mockDns.addRes(`${branchDomainA}.${host}`, [
|
|
mockData.enrWithWaku2RelayStore
|
|
]);
|
|
mockDns.addRes(`${branchDomainB}.${host}`, [mockData.enrWithWaku2Relay]);
|
|
mockDns.addRes(`${partialBranchA}${partialBranchB}.${host}`, [
|
|
mockData.enrWithWaku2Store
|
|
]);
|
|
|
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
|
const peers = [];
|
|
for await (const peer of dnsNodeDiscovery.getNextPeer([mockData.enrTree])) {
|
|
peers.push(peer);
|
|
}
|
|
|
|
expect(peers.length).to.eq(3);
|
|
const peerIds = peers.map((p) => p.peerId?.toString());
|
|
expect(peerIds).to.contain(
|
|
"16Uiu2HAm2HyS6brcCspSbszG9i36re2bWBVjMe3tMdnFp1Hua34F"
|
|
);
|
|
expect(peerIds).to.contain(
|
|
"16Uiu2HAmPsYLvfKafxgRsb6tioYyGnSvGXS2iuMigptHrqHPNPzx"
|
|
);
|
|
expect(peerIds).to.contain(
|
|
"16Uiu2HAkv3La3ECgQpdYeEJfrX36EWdhkUDv4C9wvXM8TFZ9dNgd"
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("DNS Node Discovery [live data]", function () {
|
|
const maxQuantity = 3;
|
|
|
|
before(function () {
|
|
if (process.env.CI) {
|
|
this.skip();
|
|
}
|
|
});
|
|
|
|
it(`should retrieve ${maxQuantity} multiaddrs for test.waku.nodes.status.im`, async function () {
|
|
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.TEST])) {
|
|
peers.push(peer);
|
|
}
|
|
|
|
expect(peers.length).to.eq(maxQuantity);
|
|
|
|
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(`should retrieve ${maxQuantity} multiaddrs for sandbox.waku.nodes.status.im`, async function () {
|
|
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])) {
|
|
peers.push(peer);
|
|
}
|
|
|
|
expect(peers.length).to.eq(maxQuantity);
|
|
|
|
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());
|
|
}
|
|
});
|
|
});
|