From 02394e815f783cb62de15ed34116b1372efcad3a Mon Sep 17 00:00:00 2001 From: Sacha Froment Date: Fri, 31 Jan 2025 17:20:09 +0100 Subject: [PATCH 1/5] feat: wait for node to be dialable after libp2p.start Signed-off-by: Sacha Froment --- packages/network/src/node.ts | 34 +++++++++++++++++++++++++++- packages/network/src/utils/waiter.ts | 29 ++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 packages/network/src/utils/waiter.ts diff --git a/packages/network/src/node.ts b/packages/network/src/node.ts index 8c048f480..8e5f85733 100644 --- a/packages/network/src/node.ts +++ b/packages/network/src/node.ts @@ -37,6 +37,7 @@ import { fromString as uint8ArrayFromString } from "uint8arrays/from-string"; import { Message } from "./proto/drp/network/v1/messages_pb.js"; import { uint8ArrayToStream } from "./stream.js"; +import { waitForEvent } from "./utils/waiter.js"; export * from "./stream.js"; @@ -195,8 +196,11 @@ export class DRPNetworkNode { webTransport(), ], }); + log.info( + "running on:", + this._node.getMultiaddrs().map((addr) => addr.toString()) + ); - log.info("running on:", this._node.getMultiaddrs()); if (!this._config?.bootstrap) { for (const addr of this._config?.bootstrap_peers || []) { try { @@ -238,6 +242,34 @@ export class DRPNetworkNode { await this.start(); } + async isDialable(timeout = 5000) { + return waitForEvent(async (resolve, reject) => { + if (!this._node) { + resolve(false); + return; + } + + if (await this._node?.isDialable(this._node.getMultiaddrs())) { + resolve(true); + return; + } + + const checkDialable = async () => { + try { + if (await this._node?.isDialable(this._node.getMultiaddrs())) { + resolve(true); + } + } catch (error) { + reject(error); + } finally { + this._node?.removeEventListener("transport:listening", checkDialable); + } + }; + + this._node.addEventListener("transport:listening", checkDialable); + }, timeout); + } + private _sortAddresses(a: Address, b: Address) { const localRegex = /(^\/ip4\/127\.)|(^\/ip4\/10\.)|(^\/ip4\/172\.1[6-9]\.)|(^\/ip4\/172\.2[0-9]\.)|(^\/ip4\/172\.3[0-1]\.)|(^\/ip4\/192\.168\.)/; diff --git a/packages/network/src/utils/waiter.ts b/packages/network/src/utils/waiter.ts new file mode 100644 index 000000000..59895cfd8 --- /dev/null +++ b/packages/network/src/utils/waiter.ts @@ -0,0 +1,29 @@ +export type waitFunction = ( + resolve: (value: boolean) => void, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + reject: (reason?: any) => void +) => void; + +export async function waitForEvent(fn: waitFunction, timeout = 5000) { + return new Promise((resolve, reject) => { + const timeoutId = setTimeout(() => { + resolve(false); + }, timeout); + + try { + fn( + (value) => { + clearTimeout(timeoutId); + resolve(value); + }, + (error) => { + clearTimeout(timeoutId); + reject(error); + } + ); + } catch (error) { + clearTimeout(timeoutId); + reject(error); + } + }); +} From 0a929e8e7e5cbcef1be4c1cf87ea587a11d95b22 Mon Sep 17 00:00:00 2001 From: Sacha Froment Date: Mon, 3 Feb 2025 16:49:23 +0100 Subject: [PATCH 2/5] chore: add test Signed-off-by: Sacha Froment --- packages/network/tests/index.test.ts | 36 ++++++++++++++- packages/network/tests/utils.test.ts | 66 ++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 packages/network/tests/utils.test.ts diff --git a/packages/network/tests/index.test.ts b/packages/network/tests/index.test.ts index 59f99d4c7..86ea8cb02 100644 --- a/packages/network/tests/index.test.ts +++ b/packages/network/tests/index.test.ts @@ -1,3 +1,35 @@ -import { test } from "vitest"; +import { beforeAll, describe, expect, test } from "vitest"; -test("mock", () => {}); +import { DRPNetworkNode } from "../src/node.js"; + +describe("isDialable", () => { + let btNode: DRPNetworkNode; + beforeAll(async () => { + btNode = new DRPNetworkNode({ + bootstrap: true, + listen_addresses: ["/ip4/0.0.0.0/tcp/0/ws"], + bootstrap_peers: [], + private_key_seed: "bootstrap_is_dialable", + }); + await btNode.start(); + }); + + test("should return true if the node is dialable", async () => { + const node = new DRPNetworkNode({ + bootstrap_peers: btNode.getMultiaddrs()?.map((addr) => addr.toString()) || [], + private_key_seed: "is_dialable_node_1", + }); + await node.start(); + expect(await node.isDialable(100)).toBe(true); + }); + + test("should return false if the node is not dialable", async () => { + const node = new DRPNetworkNode({ + bootstrap_peers: btNode.getMultiaddrs()?.map((addr) => addr.toString()) || [], + private_key_seed: "is_dialable_node_2", + listen_addresses: [], + }); + await node.start(); + expect(await node.isDialable(100)).toBe(false); + }); +}); diff --git a/packages/network/tests/utils.test.ts b/packages/network/tests/utils.test.ts new file mode 100644 index 000000000..1bd5e6610 --- /dev/null +++ b/packages/network/tests/utils.test.ts @@ -0,0 +1,66 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; + +import { waitForEvent } from "../src/utils/waiter.js"; // Note the .js extension + +describe("waitForEvent", () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it("should resolve with true when event occurs before timeout", async () => { + const waitFn = (resolve: (value: boolean) => void) => { + setTimeout(() => resolve(true), 1000); + }; + + const promise = waitForEvent(waitFn); + await vi.advanceTimersByTimeAsync(1000); + const result = await promise; + + expect(result).toBe(true); + }); + + it("should resolve with false when timeout occurs", async () => { + const waitFn = (resolve: (value: boolean) => void) => { + setTimeout(() => resolve(true), 6000); // Longer than default timeout + }; + + const promise = waitForEvent(waitFn); + await vi.advanceTimersByTimeAsync(5000); // Default timeout + const result = await promise; + + expect(result).toBe(false); + }); + + it("should respect custom timeout", async () => { + const waitFn = (resolve: (value: boolean) => void) => { + setTimeout(() => resolve(true), 3000); + }; + + const promise = waitForEvent(waitFn, 2000); // Custom timeout of 2 seconds + await vi.advanceTimersByTimeAsync(2000); + const result = await promise; + + expect(result).toBe(false); + }); + + it("should handle rejection from wait function", async () => { + const error = new Error("Test error"); + const waitFn = (_: (value: boolean) => void, reject: (reason?: unknown) => void) => { + reject(error); + }; + + await expect(waitForEvent(waitFn)).rejects.toThrow("Test error"); + }); + + it("should handle thrown errors in wait function", async () => { + const waitFn = () => { + throw new Error("Test error"); + }; + + await expect(waitForEvent(waitFn)).rejects.toThrow("Test error"); + }); +}); From e7e4261f546fbbe06398abafb0a6aacb71389f59 Mon Sep 17 00:00:00 2001 From: Sacha Froment Date: Tue, 4 Feb 2025 13:01:21 +0100 Subject: [PATCH 3/5] chore: -- Signed-off-by: Sacha Froment --- packages/network/src/node.ts | 33 +++++--------- packages/network/src/utils/waiter.ts | 29 ------------ packages/network/tests/index.test.ts | 24 +++++++++- packages/network/tests/utils.test.ts | 66 ---------------------------- 4 files changed, 32 insertions(+), 120 deletions(-) delete mode 100644 packages/network/src/utils/waiter.ts delete mode 100644 packages/network/tests/utils.test.ts diff --git a/packages/network/src/node.ts b/packages/network/src/node.ts index 8e5f85733..34650c765 100644 --- a/packages/network/src/node.ts +++ b/packages/network/src/node.ts @@ -37,7 +37,6 @@ import { fromString as uint8ArrayFromString } from "uint8arrays/from-string"; import { Message } from "./proto/drp/network/v1/messages_pb.js"; import { uint8ArrayToStream } from "./stream.js"; -import { waitForEvent } from "./utils/waiter.js"; export * from "./stream.js"; @@ -242,32 +241,20 @@ export class DRPNetworkNode { await this.start(); } - async isDialable(timeout = 5000) { - return waitForEvent(async (resolve, reject) => { - if (!this._node) { - resolve(false); - return; - } + async isDialable(callback: () => void | Promise) { + if (await this._node?.isDialable(this._node.getMultiaddrs())) { + await callback(); + return; + } + const checkDialable = async () => { if (await this._node?.isDialable(this._node.getMultiaddrs())) { - resolve(true); - return; + this._node?.removeEventListener("transport:listening", checkDialable); + await callback(); } + }; - const checkDialable = async () => { - try { - if (await this._node?.isDialable(this._node.getMultiaddrs())) { - resolve(true); - } - } catch (error) { - reject(error); - } finally { - this._node?.removeEventListener("transport:listening", checkDialable); - } - }; - - this._node.addEventListener("transport:listening", checkDialable); - }, timeout); + this._node?.addEventListener("transport:listening", checkDialable); } private _sortAddresses(a: Address, b: Address) { diff --git a/packages/network/src/utils/waiter.ts b/packages/network/src/utils/waiter.ts deleted file mode 100644 index 59895cfd8..000000000 --- a/packages/network/src/utils/waiter.ts +++ /dev/null @@ -1,29 +0,0 @@ -export type waitFunction = ( - resolve: (value: boolean) => void, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - reject: (reason?: any) => void -) => void; - -export async function waitForEvent(fn: waitFunction, timeout = 5000) { - return new Promise((resolve, reject) => { - const timeoutId = setTimeout(() => { - resolve(false); - }, timeout); - - try { - fn( - (value) => { - clearTimeout(timeoutId); - resolve(value); - }, - (error) => { - clearTimeout(timeoutId); - reject(error); - } - ); - } catch (error) { - clearTimeout(timeoutId); - reject(error); - } - }); -} diff --git a/packages/network/tests/index.test.ts b/packages/network/tests/index.test.ts index 86ea8cb02..eb185c207 100644 --- a/packages/network/tests/index.test.ts +++ b/packages/network/tests/index.test.ts @@ -14,13 +14,33 @@ describe("isDialable", () => { await btNode.start(); }); + const isDialable = async (node: DRPNetworkNode, timeout = false) => { + let resolver: (value: boolean) => void; + const promise = new Promise((resolve) => { + resolver = resolve; + }); + + if (timeout) { + setTimeout(() => { + resolver(false); + }, 10); + } + + const callback = () => { + resolver(true); + }; + + await node.isDialable(callback); + return await promise; + }; + test("should return true if the node is dialable", async () => { const node = new DRPNetworkNode({ bootstrap_peers: btNode.getMultiaddrs()?.map((addr) => addr.toString()) || [], private_key_seed: "is_dialable_node_1", }); await node.start(); - expect(await node.isDialable(100)).toBe(true); + expect(await isDialable(node)).toBe(true); }); test("should return false if the node is not dialable", async () => { @@ -30,6 +50,6 @@ describe("isDialable", () => { listen_addresses: [], }); await node.start(); - expect(await node.isDialable(100)).toBe(false); + expect(await isDialable(node, true)).toBe(false); }); }); diff --git a/packages/network/tests/utils.test.ts b/packages/network/tests/utils.test.ts deleted file mode 100644 index 1bd5e6610..000000000 --- a/packages/network/tests/utils.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; - -import { waitForEvent } from "../src/utils/waiter.js"; // Note the .js extension - -describe("waitForEvent", () => { - beforeEach(() => { - vi.useFakeTimers(); - }); - - afterEach(() => { - vi.useRealTimers(); - }); - - it("should resolve with true when event occurs before timeout", async () => { - const waitFn = (resolve: (value: boolean) => void) => { - setTimeout(() => resolve(true), 1000); - }; - - const promise = waitForEvent(waitFn); - await vi.advanceTimersByTimeAsync(1000); - const result = await promise; - - expect(result).toBe(true); - }); - - it("should resolve with false when timeout occurs", async () => { - const waitFn = (resolve: (value: boolean) => void) => { - setTimeout(() => resolve(true), 6000); // Longer than default timeout - }; - - const promise = waitForEvent(waitFn); - await vi.advanceTimersByTimeAsync(5000); // Default timeout - const result = await promise; - - expect(result).toBe(false); - }); - - it("should respect custom timeout", async () => { - const waitFn = (resolve: (value: boolean) => void) => { - setTimeout(() => resolve(true), 3000); - }; - - const promise = waitForEvent(waitFn, 2000); // Custom timeout of 2 seconds - await vi.advanceTimersByTimeAsync(2000); - const result = await promise; - - expect(result).toBe(false); - }); - - it("should handle rejection from wait function", async () => { - const error = new Error("Test error"); - const waitFn = (_: (value: boolean) => void, reject: (reason?: unknown) => void) => { - reject(error); - }; - - await expect(waitForEvent(waitFn)).rejects.toThrow("Test error"); - }); - - it("should handle thrown errors in wait function", async () => { - const waitFn = () => { - throw new Error("Test error"); - }; - - await expect(waitForEvent(waitFn)).rejects.toThrow("Test error"); - }); -}); From f600fc971d5535dd94e9cbc6c5b09cc4bf278750 Mon Sep 17 00:00:00 2001 From: droak Date: Tue, 4 Feb 2025 15:32:45 +0100 Subject: [PATCH 4/5] return boolean on the isDialable --- packages/network/src/node.ts | 13 ++++++++----- packages/node/src/version.ts | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/network/src/node.ts b/packages/network/src/node.ts index 34650c765..f8216e81a 100644 --- a/packages/network/src/node.ts +++ b/packages/network/src/node.ts @@ -241,20 +241,23 @@ export class DRPNetworkNode { await this.start(); } - async isDialable(callback: () => void | Promise) { - if (await this._node?.isDialable(this._node.getMultiaddrs())) { + async isDialable(callback?: () => void | Promise) { + let dialable = await this._node?.isDialable(this._node.getMultiaddrs()); + if (dialable && callback) { await callback(); - return; + return true; } + if (!callback) return false; const checkDialable = async () => { - if (await this._node?.isDialable(this._node.getMultiaddrs())) { - this._node?.removeEventListener("transport:listening", checkDialable); + dialable = await this._node?.isDialable(this._node.getMultiaddrs()); + if (dialable) { await callback(); } }; this._node?.addEventListener("transport:listening", checkDialable); + return false; } private _sortAddresses(a: Address, b: Address) { diff --git a/packages/node/src/version.ts b/packages/node/src/version.ts index 72a311182..909cf14de 100644 --- a/packages/node/src/version.ts +++ b/packages/node/src/version.ts @@ -1 +1 @@ -export const VERSION = "0.6.1"; +export const VERSION = "0.7.0"; From a4c6378d36618b5cd5b9c51b0eba148fc91eac09 Mon Sep 17 00:00:00 2001 From: Oak Date: Tue, 4 Feb 2025 14:33:16 +0000 Subject: [PATCH 5/5] Update packages/network/src/node.ts --- packages/network/src/node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/network/src/node.ts b/packages/network/src/node.ts index f8216e81a..fb6d9b92a 100644 --- a/packages/network/src/node.ts +++ b/packages/network/src/node.ts @@ -196,7 +196,7 @@ export class DRPNetworkNode { ], }); log.info( - "running on:", + "::start: running on:", this._node.getMultiaddrs().map((addr) => addr.toString()) );