UNPKG

libp2p

Version:

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

329 lines • 14.7 kB
import { publicKeyFromProtobuf } from '@libp2p/crypto/keys'; import { contentRoutingSymbol, peerDiscoverySymbol, peerRoutingSymbol, InvalidParametersError } from '@libp2p/interface'; import { defaultLogger } from '@libp2p/logger'; import { PeerSet } from '@libp2p/peer-collections'; import { peerIdFromString } from '@libp2p/peer-id'; import { persistentPeerStore } from '@libp2p/peer-store'; import { isMultiaddr } from '@multiformats/multiaddr'; import { MemoryDatastore } from 'datastore-core/memory'; import { TypedEventEmitter, setMaxListeners } from 'main-event'; import { concat as uint8ArrayConcat } from 'uint8arrays/concat'; import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'; import { AddressManager } from './address-manager/index.js'; import { checkServiceDependencies, defaultComponents } from './components.js'; import { connectionGater } from './config/connection-gater.js'; import { DefaultConnectionManager } from './connection-manager/index.js'; import { ConnectionMonitor } from './connection-monitor.js'; import { CompoundContentRouting } from './content-routing.js'; import { DefaultPeerRouting } from './peer-routing.js'; import { RandomWalk } from './random-walk.js'; import { Registrar } from './registrar.js'; import { DefaultTransportManager } from './transport-manager.js'; import { Upgrader } from './upgrader.js'; import { userAgent } from './user-agent.js'; import * as pkg from './version.js'; export class Libp2p extends TypedEventEmitter { peerId; peerStore; contentRouting; peerRouting; metrics; services; logger; status; components; log; // eslint-disable-next-line complexity constructor(init) { super(); this.status = 'stopped'; // event bus - components can listen to this emitter to be notified of system events // and also cause them to be emitted const events = new TypedEventEmitter(); const originalDispatch = events.dispatchEvent.bind(events); events.dispatchEvent = (evt) => { const internalResult = originalDispatch(evt); const externalResult = this.dispatchEvent(new CustomEvent(evt.type, { detail: evt.detail })); return internalResult || externalResult; }; // This emitter gets listened to a lot setMaxListeners(Infinity, events); this.peerId = init.peerId; this.logger = init.logger ?? defaultLogger(); this.log = this.logger.forComponent('libp2p'); // @ts-expect-error {} may not be of type T this.services = {}; const nodeInfoName = init.nodeInfo?.name ?? pkg.name; const nodeInfoVersion = init.nodeInfo?.version ?? pkg.version; // @ts-expect-error defaultComponents is missing component types added later const components = this.components = defaultComponents({ peerId: init.peerId, privateKey: init.privateKey, nodeInfo: { name: nodeInfoName, version: nodeInfoVersion, userAgent: init.nodeInfo?.userAgent ?? userAgent(nodeInfoName, nodeInfoVersion) }, logger: this.logger, events, datastore: init.datastore ?? new MemoryDatastore(), connectionGater: connectionGater(init.connectionGater), dns: init.dns }); // Create Metrics if (init.metrics != null) { this.metrics = this.configureComponent('metrics', init.metrics(this.components)); } this.peerStore = this.configureComponent('peerStore', persistentPeerStore(components, { addressFilter: this.components.connectionGater.filterMultiaddrForPeer, ...init.peerStore })); components.events.addEventListener('peer:update', evt => { // if there was no peer previously in the peer store this is a new peer if (evt.detail.previous == null) { const peerInfo = { id: evt.detail.peer.id, multiaddrs: evt.detail.peer.addresses.map(a => a.multiaddr) }; components.events.safeDispatchEvent('peer:discovery', { detail: peerInfo }); } }); // Set up connection protector if configured if (init.connectionProtector != null) { this.configureComponent('connectionProtector', init.connectionProtector(components)); } // Set up the Upgrader this.components.upgrader = new Upgrader(this.components, { connectionEncrypters: (init.connectionEncrypters ?? []).map((fn, index) => this.configureComponent(`connection-encryption-${index}`, fn(this.components))), streamMuxers: (init.streamMuxers ?? []).map((fn, index) => this.configureComponent(`stream-muxers-${index}`, fn(this.components))), inboundUpgradeTimeout: init.connectionManager?.inboundUpgradeTimeout, inboundStreamProtocolNegotiationTimeout: init.connectionManager?.inboundStreamProtocolNegotiationTimeout ?? init.connectionManager?.protocolNegotiationTimeout, outboundStreamProtocolNegotiationTimeout: init.connectionManager?.outboundStreamProtocolNegotiationTimeout ?? init.connectionManager?.protocolNegotiationTimeout }); // Setup the transport manager this.configureComponent('transportManager', new DefaultTransportManager(this.components, init.transportManager)); // Create the Connection Manager this.configureComponent('connectionManager', new DefaultConnectionManager(this.components, init.connectionManager)); if (init.connectionMonitor?.enabled !== false) { // Create the Connection Monitor if not disabled this.configureComponent('connectionMonitor', new ConnectionMonitor(this.components, init.connectionMonitor)); } // Create the Registrar this.configureComponent('registrar', new Registrar(this.components)); // Addresses {listen, announce, noAnnounce} this.configureComponent('addressManager', new AddressManager(this.components, init.addresses)); // Peer routers const peerRouters = (init.peerRouters ?? []).map((fn, index) => this.configureComponent(`peer-router-${index}`, fn(this.components))); this.peerRouting = this.components.peerRouting = this.configureComponent('peerRouting', new DefaultPeerRouting(this.components, { routers: peerRouters })); // Content routers const contentRouters = (init.contentRouters ?? []).map((fn, index) => this.configureComponent(`content-router-${index}`, fn(this.components))); this.contentRouting = this.components.contentRouting = this.configureComponent('contentRouting', new CompoundContentRouting(this.components, { routers: contentRouters })); // Random walk this.configureComponent('randomWalk', new RandomWalk(this.components)); (init.peerDiscovery ?? []).forEach((fn, index) => { const service = this.configureComponent(`peer-discovery-${index}`, fn(this.components)); service.addEventListener('peer', (evt) => { this.#onDiscoveryPeer(evt); }); }); // Transport modules init.transports?.forEach((fn, index) => { this.components.transportManager.add(this.configureComponent(`transport-${index}`, fn(this.components))); }); // User defined modules if (init.services != null) { for (const name of Object.keys(init.services)) { const createService = init.services[name]; const service = createService(this.components); if (service == null) { this.log.error('service factory %s returned null or undefined instance', name); continue; } this.services[name] = service; this.configureComponent(name, service); if (service[contentRoutingSymbol] != null) { this.log('registering service %s for content routing', name); contentRouters.push(service[contentRoutingSymbol]); } if (service[peerRoutingSymbol] != null) { this.log('registering service %s for peer routing', name); peerRouters.push(service[peerRoutingSymbol]); } if (service[peerDiscoverySymbol] != null) { this.log('registering service %s for peer discovery', name); service[peerDiscoverySymbol].addEventListener?.('peer', (evt) => { this.#onDiscoveryPeer(evt); }); } } } // Ensure all services have their required dependencies checkServiceDependencies(components); } configureComponent(name, component) { if (component == null) { this.log.error('component %s was null or undefined', name); } // @ts-expect-error cannot assign props this.components[name] = component; return component; } /** * Starts the libp2p node and all its subsystems */ async start() { if (this.status !== 'stopped') { return; } this.status = 'starting'; this.log('libp2p is starting'); try { await this.components.beforeStart?.(); await this.components.start(); await this.components.afterStart?.(); this.status = 'started'; this.safeDispatchEvent('start', { detail: this }); this.log('libp2p has started'); } catch (err) { this.log.error('An error occurred starting libp2p', err); // set status to 'started' so this.stop() will stop any running components this.status = 'started'; await this.stop(); throw err; } } /** * Stop the libp2p node by closing its listeners and open connections */ async stop() { if (this.status !== 'started') { return; } this.log('libp2p is stopping'); this.status = 'stopping'; await this.components.beforeStop?.(); await this.components.stop(); await this.components.afterStop?.(); this.status = 'stopped'; this.safeDispatchEvent('stop', { detail: this }); this.log('libp2p has stopped'); } getConnections(peerId) { return this.components.connectionManager.getConnections(peerId); } getDialQueue() { return this.components.connectionManager.getDialQueue(); } getPeers() { const peerSet = new PeerSet(); for (const conn of this.components.connectionManager.getConnections()) { peerSet.add(conn.remotePeer); } return Array.from(peerSet); } async dial(peer, options = {}) { return this.components.connectionManager.openConnection(peer, { // ensure any userland dials take top priority in the queue priority: 75, ...options }); } async dialProtocol(peer, protocols, options = {}) { if (protocols == null) { throw new InvalidParametersError('no protocols were provided to open a stream'); } protocols = Array.isArray(protocols) ? protocols : [protocols]; if (protocols.length === 0) { throw new InvalidParametersError('no protocols were provided to open a stream'); } const connection = await this.dial(peer, options); return connection.newStream(protocols, options); } getMultiaddrs() { return this.components.addressManager.getAddresses(); } getProtocols() { return this.components.registrar.getProtocols(); } async hangUp(peer, options = {}) { if (isMultiaddr(peer)) { peer = peerIdFromString(peer.getPeerId() ?? ''); } await this.components.connectionManager.closeConnections(peer, options); } async getPublicKey(peer, options = {}) { this.log('getPublicKey %p', peer); if (peer.publicKey != null) { return peer.publicKey; } try { const peerInfo = await this.peerStore.get(peer, options); if (peerInfo.id.publicKey != null) { return peerInfo.id.publicKey; } } catch (err) { if (err.name !== 'NotFoundError') { throw err; } } const peerKey = uint8ArrayConcat([ uint8ArrayFromString('/pk/'), peer.toMultihash().bytes ]); // search any available content routing methods const bytes = await this.contentRouting.get(peerKey, options); // ensure the returned key is valid const publicKey = publicKeyFromProtobuf(bytes); await this.peerStore.patch(peer, { publicKey }, options); return publicKey; } async handle(protocols, handler, options) { if (!Array.isArray(protocols)) { protocols = [protocols]; } await Promise.all(protocols.map(async (protocol) => { await this.components.registrar.handle(protocol, handler, options); })); } async unhandle(protocols, options) { if (!Array.isArray(protocols)) { protocols = [protocols]; } await Promise.all(protocols.map(async (protocol) => { await this.components.registrar.unhandle(protocol, options); })); } async register(protocol, topology, options) { return this.components.registrar.register(protocol, topology, options); } unregister(id) { this.components.registrar.unregister(id); } async isDialable(multiaddr, options = {}) { return this.components.connectionManager.isDialable(multiaddr, options); } /** * Called whenever peer discovery services emit `peer` events and adds peers * to the peer store. */ #onDiscoveryPeer(evt) { const { detail: peer } = evt; if (peer.id.toString() === this.peerId.toString()) { this.log.error('peer discovery mechanism discovered self'); return; } void this.components.peerStore.merge(peer.id, { multiaddrs: peer.multiaddrs }) .catch(err => { this.log.error(err); }); } } //# sourceMappingURL=libp2p.js.map