UNPKG

@waku/core

Version:

TypeScript implementation of the Waku v2 protocol

193 lines (156 loc) 5.21 kB
import type { PeerId } from "@libp2p/interface"; import { ConnectionManagerOptions, Libp2p } from "@waku/interfaces"; import { Logger } from "@waku/utils"; import { ShardReader } from "./shard_reader.js"; const log = new Logger("dialer"); type DialerConstructorOptions = { libp2p: Libp2p; shardReader: ShardReader; options: ConnectionManagerOptions; }; interface IDialer { start(): void; stop(): void; dial(peerId: PeerId): Promise<void>; } export class Dialer implements IDialer { private readonly libp2p: Libp2p; private readonly shardReader: ShardReader; private readonly options: ConnectionManagerOptions; private dialingQueue: PeerId[] = []; private dialHistory: Map<string, number> = new Map(); private failedDials: Map<string, number> = new Map(); private dialingInterval: NodeJS.Timeout | null = null; private isProcessing = false; private isImmediateDialing = false; public constructor(options: DialerConstructorOptions) { this.libp2p = options.libp2p; this.shardReader = options.shardReader; this.options = options.options; } public start(): void { log.info("Starting dialer"); if (!this.dialingInterval) { this.dialingInterval = setInterval(() => { void this.processQueue(); }, 500); } this.dialHistory.clear(); this.failedDials.clear(); } public stop(): void { log.info("Stopping dialer"); if (this.dialingInterval) { clearInterval(this.dialingInterval); this.dialingInterval = null; } this.dialHistory.clear(); this.failedDials.clear(); } public async dial(peerId: PeerId): Promise<void> { const shouldSkip = await this.shouldSkipPeer(peerId); if (shouldSkip) { log.info(`Skipping peer: ${peerId}`); return; } const isEmptyQueue = this.dialingQueue.length === 0; const isNotDialing = !this.isProcessing && !this.isImmediateDialing; // If queue is empty and we're not currently processing, dial immediately if (isEmptyQueue && isNotDialing) { this.isImmediateDialing = true; log.info("Dialed peer immediately"); await this.dialPeer(peerId); this.isImmediateDialing = false; log.info("Released immediate dial lock"); } else { this.dialingQueue.push(peerId); log.info( `Added peer to dialing queue, queue size: ${this.dialingQueue.length}` ); } } private async processQueue(): Promise<void> { if (this.dialingQueue.length === 0 || this.isProcessing) { return; } this.isProcessing = true; try { const peersToDial = this.dialingQueue.slice( 0, this.options.maxDialingPeers ); this.dialingQueue = this.dialingQueue.slice(peersToDial.length); log.info( `Processing dial queue: dialing ${peersToDial.length} peers, ${this.dialingQueue.length} remaining in queue` ); await Promise.all(peersToDial.map((peerId) => this.dialPeer(peerId))); } finally { this.isProcessing = false; } } private async dialPeer(peerId: PeerId): Promise<void> { try { log.info(`Dialing peer from queue: ${peerId}`); await this.libp2p.dial(peerId); this.dialHistory.set(peerId.toString(), Date.now()); this.failedDials.delete(peerId.toString()); log.info(`Successfully dialed peer from queue: ${peerId}`); } catch (error) { log.error(`Error dialing peer ${peerId}`, error); this.failedDials.set(peerId.toString(), Date.now()); } } private async shouldSkipPeer(peerId: PeerId): Promise<boolean> { const hasConnection = this.libp2p.getPeers().some((p) => p.equals(peerId)); if (hasConnection) { log.info(`Skipping peer ${peerId} - already connected`); return true; } if (this.isRecentlyDialed(peerId)) { log.info( `Skipping peer ${peerId} - already dialed in the last 10 seconds` ); return true; } if (this.isRecentlyFailed(peerId)) { log.info(`Skipping peer ${peerId} - recently failed to dial`); return true; } try { const hasShardInfo = await this.shardReader.hasShardInfo(peerId); if (!hasShardInfo) { log.info(`Skipping peer ${peerId} - no shard info`); return false; } const isOnSameShard = await this.shardReader.isPeerOnNetwork(peerId); if (!isOnSameShard) { log.info(`Skipping peer ${peerId} - not on same shard`); return true; } return false; } catch (error) { log.error(`Error checking shard info for peer ${peerId}`, error); return true; // Skip peer when there's an error } } private isRecentlyDialed(peerId: PeerId): boolean { const lastDialed = this.dialHistory.get(peerId.toString()); if ( lastDialed && Date.now() - lastDialed < this.options.dialCooldown * 1000 ) { return true; } return false; } private isRecentlyFailed(peerId: PeerId): boolean { const lastFailed = this.failedDials.get(peerId.toString()); if ( lastFailed && Date.now() - lastFailed < this.options.failedDialCooldown * 1000 ) { return true; } return false; } }