UNPKG

libp2p

Version:

JavaScript implementation of libp2p, a modular peer to peer network stack

118 lines 4.75 kB
import { randomBytes } from '@libp2p/crypto'; import { anySignal } from 'any-signal'; import { TypedEventEmitter, setMaxListeners } from 'main-event'; import pDefer from 'p-defer'; import { raceEvent } from 'race-event'; import { raceSignal } from 'race-signal'; export class RandomWalk extends TypedEventEmitter { peerRouting; log; walking; walkers; shutdownController; walkController; needNext; constructor(components) { super(); this.log = components.logger.forComponent('libp2p:random-walk'); this.peerRouting = components.peerRouting; this.walkers = 0; this.walking = false; // stops any in-progress walks when the node is shut down this.shutdownController = new AbortController(); setMaxListeners(Infinity, this.shutdownController.signal); } [Symbol.toStringTag] = '@libp2p/random-walk'; start() { this.shutdownController = new AbortController(); setMaxListeners(Infinity, this.shutdownController.signal); } stop() { this.shutdownController.abort(); } async *walk(options) { if (!this.walking) { // start the query that causes walk:peer events to be emitted this.startWalk(); } this.walkers++; const signal = anySignal([this.shutdownController.signal, options?.signal]); setMaxListeners(Infinity, signal); try { while (true) { // if another consumer has paused the query, start it again this.needNext?.resolve(); this.needNext = pDefer(); // wait for a walk:peer or walk:error event const event = await raceEvent(this, 'walk:peer', signal, { errorEvent: 'walk:error' }); yield event.detail; } } finally { signal.clear(); this.walkers--; // stop the walk if no more consumers are interested if (this.walkers === 0) { this.walkController?.abort(); this.walkController = undefined; } } } startWalk() { this.walking = true; // the signal for this controller will be aborted if no more random peers // are required this.walkController = new AbortController(); setMaxListeners(Infinity, this.walkController.signal); const signal = anySignal([this.walkController.signal, this.shutdownController.signal]); setMaxListeners(Infinity, signal); const start = Date.now(); let found = 0; Promise.resolve().then(async () => { this.log('start walk'); // find peers until no more consumers are interested while (this.walkers > 0) { try { const data = randomBytes(32); let s = Date.now(); for await (const peer of this.peerRouting.getClosestPeers(data, { signal })) { if (signal.aborted) { this.log('aborting walk'); } signal.throwIfAborted(); this.log('found peer %p after %dms for %d walkers', peer.id, Date.now() - s, this.walkers); found++; this.safeDispatchEvent('walk:peer', { detail: peer }); // if we only have one consumer, pause the query until they request // another random peer or they signal they are no longer interested if (this.walkers === 1 && this.needNext != null) { this.log('wait for need next'); await raceSignal(this.needNext.promise, signal); } s = Date.now(); } this.log('walk iteration for %b and %d walkers finished, found %d peers', data, this.walkers, found); } catch (err) { this.log.error('random walk errored', err); this.safeDispatchEvent('walk:error', { detail: err }); } } this.log('no walkers left, ended walk'); }) .catch(err => { this.log.error('random walk errored', err); }) .finally(() => { this.log('finished walk, found %d peers after %dms', found, Date.now() - start); this.walking = false; }); } } //# sourceMappingURL=random-walk.js.map