From 8d773c46208775467ae0168b5e46bab737239b30 Mon Sep 17 00:00:00 2001 From: Sasha Date: Thu, 11 Sep 2025 22:23:02 +0200 Subject: [PATCH] implement peer-store re-bootstrapping --- .../connection_manager/bootstrap_trigger.ts | 100 ++++++++++++++++++ .../connection_manager/connection_manager.ts | 8 ++ 2 files changed, 108 insertions(+) create mode 100644 packages/core/src/lib/connection_manager/bootstrap_trigger.ts diff --git a/packages/core/src/lib/connection_manager/bootstrap_trigger.ts b/packages/core/src/lib/connection_manager/bootstrap_trigger.ts new file mode 100644 index 0000000000..03251a9827 --- /dev/null +++ b/packages/core/src/lib/connection_manager/bootstrap_trigger.ts @@ -0,0 +1,100 @@ +import { PeerId } from "@libp2p/interface"; +import { Libp2p } from "@waku/interfaces"; +import { Logger } from "@waku/utils"; + +type BootstrapTriggerConstructorOptions = { + libp2p: Libp2p; +}; + +interface IBootstrapTrigger { + start(): void; + stop(): void; +} + +const log = new Logger("bootstrap-trigger"); + +const DEFAULT_BOOTSTRAP_TIMEOUT_MS = 1000; + +export class BootstrapTrigger implements IBootstrapTrigger { + private readonly libp2p: Libp2p; + private bootstrapTimeout: NodeJS.Timeout | null = null; + + public constructor(options: BootstrapTriggerConstructorOptions) { + this.libp2p = options.libp2p; + } + + public start(): void { + log.info("Starting bootstrap trigger"); + this.libp2p.addEventListener("peer:disconnect", this.onPeerDisconnectEvent); + } + + public stop(): void { + log.info("Stopping bootstrap trigger"); + this.libp2p.removeEventListener( + "peer:disconnect", + this.onPeerDisconnectEvent + ); + + if (this.bootstrapTimeout) { + clearTimeout(this.bootstrapTimeout); + this.bootstrapTimeout = null; + log.info("Cleared pending bootstrap timeout"); + } + } + + private onPeerDisconnectEvent = (event: CustomEvent): void => { + const peerId = event.detail; + const connections = this.libp2p.getConnections(); + log.info( + `Peer disconnected: ${peerId.toString()}, remaining connections: ${connections.length}` + ); + + if (connections.length !== 0) { + return; + } + + log.info( + `Last peer disconnected, scheduling bootstrap in ${DEFAULT_BOOTSTRAP_TIMEOUT_MS} milliseconds` + ); + + if (this.bootstrapTimeout) { + clearTimeout(this.bootstrapTimeout); + } + + this.bootstrapTimeout = setTimeout(() => { + log.info("Triggering bootstrap after timeout"); + this.triggerBootstrap(); + this.bootstrapTimeout = null; + }, DEFAULT_BOOTSTRAP_TIMEOUT_MS); + }; + + private triggerBootstrap(): void { + log.info("Triggering bootstrap discovery"); + + const bootstrapComponents = Object.values(this.libp2p.components.components) + .filter((c) => !!c) + .filter( + (c: unknown) => + (c as { [Symbol.toStringTag]: string })[Symbol.toStringTag] === + "@waku/bootstrap" + ); + + if (bootstrapComponents.length === 0) { + log.warn("No bootstrap components found to trigger"); + return; + } + + log.info( + `Found ${bootstrapComponents.length} bootstrap components, starting them` + ); + + bootstrapComponents.forEach((component) => { + try { + (component as { start: () => void }).start(); + log.info("Successfully started bootstrap component"); + } catch (error) { + log.error("Failed to start bootstrap component", error); + } + }); + } +} diff --git a/packages/core/src/lib/connection_manager/connection_manager.ts b/packages/core/src/lib/connection_manager/connection_manager.ts index f5d6ded196..8b044aa33f 100644 --- a/packages/core/src/lib/connection_manager/connection_manager.ts +++ b/packages/core/src/lib/connection_manager/connection_manager.ts @@ -11,6 +11,7 @@ import { import { Libp2p } from "@waku/interfaces"; import { Logger } from "@waku/utils"; +import { BootstrapTrigger } from "./bootstrap_trigger.js"; import { ConnectionLimiter } from "./connection_limiter.js"; import { Dialer } from "./dialer.js"; import { DiscoveryDialer } from "./discovery_dialer.js"; @@ -45,6 +46,7 @@ export class ConnectionManager implements IConnectionManager { private readonly shardReader: ShardReader; private readonly networkMonitor: NetworkMonitor; private readonly connectionLimiter: ConnectionLimiter; + private readonly bootstrapTrigger: BootstrapTrigger; private readonly options: ConnectionManagerOptions; private libp2p: Libp2p; @@ -64,6 +66,10 @@ export class ConnectionManager implements IConnectionManager { ...options.config }; + this.bootstrapTrigger = new BootstrapTrigger({ + libp2p: options.libp2p + }); + this.keepAliveManager = new KeepAliveManager({ relay: options.relay, libp2p: options.libp2p, @@ -110,6 +116,7 @@ export class ConnectionManager implements IConnectionManager { this.discoveryDialer.start(); this.keepAliveManager.start(); this.connectionLimiter.start(); + this.bootstrapTrigger.start(); } public stop(): void { @@ -118,6 +125,7 @@ export class ConnectionManager implements IConnectionManager { this.discoveryDialer.stop(); this.keepAliveManager.stop(); this.connectionLimiter.stop(); + this.bootstrapTrigger.stop(); } public isConnected(): boolean {