UNPKG

ipfs-bitswap

Version:

JavaScript implementation of the Bitswap data exchange protocol used by IPFS

223 lines 8.62 kB
import { CodeError } from '@libp2p/interface'; import drain from 'it-drain'; import * as lp from 'it-length-prefixed'; import map from 'it-map'; import { pipe } from 'it-pipe'; import take from 'it-take'; import { CustomProgressEvent } from 'progress-events'; import { TimeoutController } from 'timeout-abort-controller'; import * as CONSTANTS from './constants.js'; import { BitswapMessage as Message } from './message/index.js'; import { logger } from './utils/index.js'; const BITSWAP100 = '/ipfs/bitswap/1.0.0'; const BITSWAP110 = '/ipfs/bitswap/1.1.0'; const BITSWAP120 = '/ipfs/bitswap/1.2.0'; const DEFAULT_MAX_INBOUND_STREAMS = 1024; const DEFAULT_MAX_OUTBOUND_STREAMS = 1024; const DEFAULT_INCOMING_STREAM_TIMEOUT = 30000; export class Network { _log; _libp2p; _bitswap; _protocols; _stats; _running; _hashLoader; _maxInboundStreams; _maxOutboundStreams; _incomingStreamTimeout; _registrarIds; constructor(libp2p, bitswap, stats, options = {}) { this._log = logger(libp2p.peerId, 'network'); this._libp2p = libp2p; this._bitswap = bitswap; this._protocols = [BITSWAP100]; if (options.b100Only !== true) { // Latest bitswap first this._protocols.unshift(BITSWAP110); this._protocols.unshift(BITSWAP120); } this._stats = stats; this._running = false; // bind event listeners this._onPeerConnect = this._onPeerConnect.bind(this); this._onPeerDisconnect = this._onPeerDisconnect.bind(this); this._onConnection = this._onConnection.bind(this); this._hashLoader = options.hashLoader ?? { async getHasher() { throw new Error('Not implemented'); } }; this._maxInboundStreams = options.maxInboundStreams ?? DEFAULT_MAX_INBOUND_STREAMS; this._maxOutboundStreams = options.maxOutboundStreams ?? DEFAULT_MAX_OUTBOUND_STREAMS; this._incomingStreamTimeout = options.incomingStreamTimeout ?? DEFAULT_INCOMING_STREAM_TIMEOUT; } async start() { this._running = true; await this._libp2p.handle(this._protocols, this._onConnection, { maxInboundStreams: this._maxInboundStreams, maxOutboundStreams: this._maxOutboundStreams }); // register protocol with topology const topology = { onConnect: this._onPeerConnect, onDisconnect: this._onPeerDisconnect }; /** @type {string[]} */ this._registrarIds = []; for (const protocol of this._protocols) { this._registrarIds.push(await this._libp2p.register(protocol, topology)); } // All existing connections are like new ones for us this._libp2p.getConnections().forEach(conn => { this._onPeerConnect(conn.remotePeer); }); } async stop() { this._running = false; // Unhandle both, libp2p doesn't care if it's not already handled await this._libp2p.unhandle(this._protocols); // unregister protocol and handlers if (this._registrarIds != null) { for (const id of this._registrarIds) { this._libp2p.unregister(id); } this._registrarIds = []; } } /** * Handles both types of incoming bitswap messages */ _onConnection(info) { if (!this._running) { return; } const { stream, connection } = info; const controller = new TimeoutController(this._incomingStreamTimeout); Promise.resolve().then(async () => { this._log('incoming new bitswap %s connection from %p', stream.protocol, connection.remotePeer); const abortListener = () => { stream.abort(new CodeError('Incoming Bitswap stream timed out', 'ERR_TIMEOUT')); }; let signal = AbortSignal.timeout(this._incomingStreamTimeout); signal.addEventListener('abort', abortListener); await pipe(stream, (source) => lp.decode(source), async (source) => { for await (const data of source) { try { const message = await Message.deserialize(data.subarray(), this._hashLoader); await this._bitswap._receiveMessage(connection.remotePeer, message); } catch (err) { this._bitswap._receiveError(err); break; } // we have received some data so reset the timeout controller signal.removeEventListener('abort', abortListener); signal = AbortSignal.timeout(this._incomingStreamTimeout); signal.addEventListener('abort', abortListener); } }); await stream.close({ signal }); }) .catch(err => { this._log(err); stream.abort(err); }) .finally(() => { controller.clear(); }); } _onPeerConnect(peerId) { this._bitswap._onPeerConnected(peerId); } _onPeerDisconnect(peerId) { this._bitswap._onPeerDisconnected(peerId); } /** * Find providers given a `cid`. */ findProviders(cid, options = {}) { options.onProgress?.(new CustomProgressEvent('bitswap:network:find-providers', cid)); return this._libp2p.contentRouting.findProviders(cid, options); } /** * Find the providers of a given `cid` and connect to them. */ async findAndConnect(cid, options) { await drain(take(map(this.findProviders(cid, options), async (provider) => this.connectTo(provider.id, options) .catch(err => { // Prevent unhandled promise rejection this._log.error(err); })), CONSTANTS.maxProvidersPerRequest)) .catch(err => { this._log.error(err); }); } /** * Tell the network we can provide content for the passed CID */ async provide(cid, options = {}) { options.onProgress?.(new CustomProgressEvent('bitswap:network:provide', cid)); await this._libp2p.contentRouting.provide(cid, options); } /** * Connect to the given peer * Send the given msg (instance of Message) to the given peer */ async sendMessage(peer, msg, options = {}) { if (!this._running) throw new Error('network isn\'t running'); const stringId = peer.toString(); this._log('sendMessage to %s', stringId, msg); options.onProgress?.(new CustomProgressEvent('bitswap:network:send-wantlist', peer)); await this._writeMessage(peer, msg, options); this._updateSentStats(peer, msg.blocks); } /** * Connects to another peer */ async connectTo(peer, options = {}) { if (!this._running) { throw new Error('network isn\'t running'); } options.onProgress?.(new CustomProgressEvent('bitswap:network:dial', peer)); return this._libp2p.dial(peer, options); } _updateSentStats(peer, blocks) { const peerId = peer.toString(); if (this._stats != null) { for (const block of blocks.values()) { this._stats.push(peerId, 'dataSent', block.length); } this._stats.push(peerId, 'blocksSent', blocks.size); } } async _writeMessage(peerId, msg, options = {}) { const stream = await this._libp2p.dialProtocol(peerId, [BITSWAP120, BITSWAP110, BITSWAP100]); try { /** @type {Uint8Array} */ let serialized; switch (stream.protocol) { case BITSWAP100: serialized = msg.serializeToBitswap100(); break; case BITSWAP110: case BITSWAP120: serialized = msg.serializeToBitswap110(); break; default: throw new Error(`Unknown protocol: ${stream.protocol}`); } await pipe([serialized], (source) => lp.encode(source), stream); await stream.close(); } catch (err) { options.onProgress?.(new CustomProgressEvent('bitswap:network:send-wantlist:error', { peer: peerId, error: err })); this._log(err); stream.abort(err); } } } //# sourceMappingURL=network.js.map