UNPKG

lisk-framework

Version:

Lisk blockchain application platform

326 lines 13.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Network = void 0; const lisk_codec_1 = require("@liskhq/lisk-codec"); const lisk_cryptography_1 = require("@liskhq/lisk-cryptography"); const lisk_db_1 = require("@liskhq/lisk-db"); const events_1 = require("events"); const liskP2P = require("@liskhq/lisk-p2p"); const events_2 = require("../events"); const utils_1 = require("./utils"); const schema_1 = require("./schema"); const endpoint_1 = require("./endpoint"); const { P2P, events: { EVENT_NETWORK_READY, EVENT_NEW_INBOUND_PEER, EVENT_CLOSE_INBOUND, EVENT_CLOSE_OUTBOUND, EVENT_CONNECT_OUTBOUND, EVENT_DISCOVERED_PEER, EVENT_FAILED_TO_FETCH_PEER_INFO, EVENT_FAILED_TO_PUSH_NODE_INFO, EVENT_OUTBOUND_SOCKET_ERROR, EVENT_INBOUND_SOCKET_ERROR, EVENT_UPDATED_PEER_INFO, EVENT_FAILED_PEER_INFO_UPDATE, EVENT_REQUEST_RECEIVED, EVENT_MESSAGE_RECEIVED, EVENT_BAN_PEER, }, } = liskP2P; const DB_KEY_NETWORK_NODE_SECRET = Buffer.from('network:nodeSecret', 'utf8'); const DB_KEY_NETWORK_TRIED_PEERS_LIST = Buffer.from('network:triedPeersList', 'utf8'); const DEFAULT_PEER_SAVE_INTERVAL = 10 * 60 * 1000; class Network { constructor({ options }) { this._options = options; this._endpoints = {}; this._eventHandlers = {}; this._secret = undefined; this._endpoint = new endpoint_1.Endpoint(); this.events = new events_1.EventEmitter(); } async init(args) { var _a, _b; this._logger = args.logger; this._nodeDB = args.nodeDB; this._chainID = args.chainID; let previousPeers = []; try { const previousPeersBuffer = await this._nodeDB.get(DB_KEY_NETWORK_TRIED_PEERS_LIST); previousPeers = JSON.parse(previousPeersBuffer.toString('utf8')); } catch (error) { if (!(error instanceof lisk_db_1.NotFoundError)) { this._logger.error({ err: error }, 'Error while querying nodeDB'); } } let secret; try { secret = await this._nodeDB.get(DB_KEY_NETWORK_NODE_SECRET); } catch (error) { if (!(error instanceof lisk_db_1.NotFoundError)) { this._logger.error({ err: error }, 'Error while querying nodeDB'); } } if (!secret) { secret = lisk_cryptography_1.utils.getRandomBytes(4); await this._nodeDB.set(DB_KEY_NETWORK_NODE_SECRET, secret); } this._secret = secret === null || secret === void 0 ? void 0 : secret.readUInt32BE(0); this._nodeInfoOptions = { lastBlockID: Buffer.alloc(0), blockVersion: 0, height: 0, maxHeightPrevoted: 0, legacy: [], }; const initialNodeInfo = { chainID: this._chainID, networkVersion: this._options.version, nonce: '', advertiseAddress: (_a = this._options.advertiseAddress) !== null && _a !== void 0 ? _a : true, options: { ...this._nodeInfoOptions }, }; const seedPeers = await (0, utils_1.lookupPeersIPs)(this._options.seedPeers, true); const blacklistedIPs = (_b = this._options.blacklistedIPs) !== null && _b !== void 0 ? _b : []; const fixedPeers = this._options.fixedPeers ? this._options.fixedPeers.map(peer => ({ ipAddress: peer.ip, port: peer.port, })) : []; const whitelistedPeers = this._options.whitelistedPeers ? this._options.whitelistedPeers.map(peer => ({ ipAddress: peer.ip, port: peer.port, })) : []; const p2pConfig = { port: this._options.port, nodeInfo: initialNodeInfo, hostIp: this._options.host, blacklistedIPs, fixedPeers, whitelistedPeers, seedPeers: seedPeers.map(peer => ({ ipAddress: peer.ip, port: peer.port, })), previousPeers, maxOutboundConnections: this._options.maxOutboundConnections, maxInboundConnections: this._options.maxInboundConnections, wsMaxPayload: this._options.wsMaxPayload, secret: this._secret, customNodeInfoSchema: schema_1.customNodeInfoSchema, }; this._p2p = new P2P(p2pConfig); this._endpoint.init({ p2p: this._p2p }); this._p2p.on(EVENT_NETWORK_READY, () => { this._logger.debug('Node connected to the network'); this.events.emit(events_2.EVENT_NETWORK_READY); }); this._p2p.on(EVENT_CLOSE_OUTBOUND, ({ peerInfo, code, reason }) => { this._logger.debug({ ...peerInfo, code, reason, }, 'EVENT_CLOSE_OUTBOUND: Close outbound peer connection'); }); this._p2p.on(EVENT_CLOSE_INBOUND, ({ peerInfo, code, reason }) => { this._logger.debug({ ...peerInfo, code, reason, }, 'EVENT_CLOSE_INBOUND: Close inbound peer connection'); }); this._p2p.on(EVENT_CONNECT_OUTBOUND, peerInfo => { this._logger.debug({ ...peerInfo, }, 'EVENT_CONNECT_OUTBOUND: Outbound peer connection'); }); this._p2p.on(EVENT_DISCOVERED_PEER, peerInfo => { this._logger.trace({ ...peerInfo, }, 'EVENT_DISCOVERED_PEER: Discovered peer connection'); }); this._p2p.on(EVENT_NEW_INBOUND_PEER, peerInfo => { this._logger.debug({ ...peerInfo, }, 'EVENT_NEW_INBOUND_PEER: Inbound peer connection'); }); this._p2p.on(EVENT_FAILED_TO_FETCH_PEER_INFO, (error) => { this._logger.error({ err: error }, 'EVENT_FAILED_TO_FETCH_PEER_INFO: Failed to fetch peer info'); }); this._p2p.on(EVENT_FAILED_TO_PUSH_NODE_INFO, (error) => { this._logger.trace({ err: error }, 'EVENT_FAILED_TO_PUSH_NODE_INFO: Failed to push node info'); }); this._p2p.on(EVENT_OUTBOUND_SOCKET_ERROR, (error) => { this._logger.debug({ err: error }, 'EVENT_OUTBOUND_SOCKET_ERROR: Outbound socket error'); }); this._p2p.on(EVENT_INBOUND_SOCKET_ERROR, (error) => { this._logger.debug({ err: error }, 'EVENT_INBOUND_SOCKET_ERROR: Inbound socket error'); }); this._p2p.on(EVENT_UPDATED_PEER_INFO, peerInfo => { this._logger.trace({ ...peerInfo, }, 'EVENT_UPDATED_PEER_INFO: Update peer info'); }); this._p2p.on(EVENT_FAILED_PEER_INFO_UPDATE, (error) => { this._logger.error({ err: error }, 'EVENT_FAILED_PEER_INFO_UPDATE: Failed peer update'); }); this._p2p.on(EVENT_REQUEST_RECEIVED, async (request) => { this._logger.trace({ procedure: request.procedure }, 'EVENT_REQUEST_RECEIVED: Received inbound request for procedure'); if (request.wasResponseSent) { return; } if (!Object.keys(this._endpoints).includes(request.procedure)) { const error = new Error(`Requested procedure "${request.procedure}" is not permitted.`); this._logger.warn({ err: error, procedure: request.procedure }, 'Peer request not fulfilled event: Requested procedure is not permitted. Applying a penalty to the peer'); this._p2p.applyPenalty({ peerId: request.peerId, penalty: 100 }); request.error(error); return; } try { const result = await this._endpoints[request.procedure]({ data: request.data, peerId: request.peerId, }); this._logger.trace({ procedure: request.procedure }, 'Peer request fulfilled event: Responded to peer request'); request.end(result); } catch (error) { this._logger.error({ err: error, procedure: request.procedure }, 'Peer request not fulfilled event: Could not respond to peer request'); request.error(error); } }); this._p2p.on(EVENT_MESSAGE_RECEIVED, (packet) => { if (!Object.keys(this._eventHandlers).includes(packet.event)) { const error = new Error(`Sent event "${packet.event}" is not permitted.`); this._logger.warn({ err: error, event: packet.event }, 'Peer request not fulfilled. Sent event is not permitted. Applying a penalty to the peer'); this._p2p.applyPenalty({ peerId: packet.peerId, penalty: 100 }); return; } this._logger.trace({ peerId: packet.peerId, event: packet.event, }, 'EVENT_MESSAGE_RECEIVED: Received inbound message'); try { this._eventHandlers[packet.event](packet); } catch (error) { this._logger.warn({ err: error, event: packet.event }, 'Peer request not fulfilled event: Fail to handle event. Applying a penalty to the peer'); this._p2p.applyPenalty({ peerId: packet.peerId, penalty: 100 }); } }); this._p2p.on(EVENT_BAN_PEER, (peerId) => { this._logger.error({ peerId }, 'EVENT_MESSAGE_RECEIVED: Peer has been banned temporarily'); }); } async start() { this._saveIntervalID = setInterval(async () => { const triedPeers = this._p2p.getTriedPeers(); if (triedPeers.length) { await this._nodeDB.set(DB_KEY_NETWORK_TRIED_PEERS_LIST, Buffer.from(JSON.stringify(triedPeers), 'utf8')); } }, DEFAULT_PEER_SAVE_INTERVAL); try { await this._p2p.start(); } catch (error) { this._logger.fatal({ message: error.message, stack: error.stack, }, 'Failed to initialize network'); throw error; } } async stop() { this._logger.info('Network cleanup started'); if (this._saveIntervalID) { clearInterval(this._saveIntervalID); } await this._p2p.stop(); this._logger.info('Network cleanup completed'); } get endpoint() { return this._endpoint; } registerEndpoint(endpoint, handler) { if (this._endpoints[endpoint]) { throw new Error(`Endpoint ${endpoint} has already been registered.`); } this._endpoints[endpoint] = handler; } registerHandler(event, handler) { if (this._eventHandlers[event]) { throw new Error(`Event handler for ${event} has already been registered.`); } this._eventHandlers[event] = handler; } async request(requestPacket) { return this._p2p.request({ procedure: requestPacket.procedure, data: requestPacket.data, }); } send(sendPacket) { return this._p2p.send({ event: sendPacket.event, data: sendPacket.data, }); } async requestFromPeer(requestPacket) { return this._p2p.requestFromPeer({ procedure: requestPacket.procedure, data: requestPacket.data, }, requestPacket.peerId); } sendToPeer(sendPacket) { return this._p2p.sendToPeer({ event: sendPacket.event, data: sendPacket.data, }, sendPacket.peerId); } broadcast(broadcastPacket) { return this._p2p.broadcast({ event: broadcastPacket.event, data: broadcastPacket.data, }); } getConnectedPeers() { const peers = this._p2p.getConnectedPeers(); return peers.map(peer => { const parsedPeer = { ...peer, }; if (parsedPeer.options) { parsedPeer.options = lisk_codec_1.codec.toJSON(schema_1.customNodeInfoSchema, parsedPeer.options); } return parsedPeer; }); } getNetworkStats() { return this._p2p.getNetworkStats(); } getDisconnectedPeers() { const peers = this._p2p.getDisconnectedPeers(); return peers.map(peer => { const parsedPeer = { ...peer, }; if (parsedPeer.options) { parsedPeer.options = lisk_codec_1.codec.toJSON(schema_1.customNodeInfoSchema, parsedPeer.options); } return parsedPeer; }); } applyPenaltyOnPeer(penaltyPacket) { return this._p2p.applyPenalty({ peerId: penaltyPacket.peerId, penalty: penaltyPacket.penalty, }); } applyNodeInfo(data) { var _a; this._nodeInfoOptions = { ...this._nodeInfoOptions, ...data }; const newNodeInfo = { chainID: this._chainID, networkVersion: this._options.version, advertiseAddress: (_a = this._options.advertiseAddress) !== null && _a !== void 0 ? _a : true, options: this._nodeInfoOptions, }; try { this._p2p.applyNodeInfo(newNodeInfo); } catch (error) { this._logger.error({ err: error }, 'Applying NodeInfo failed because of error'); } } } exports.Network = Network; //# sourceMappingURL=network.js.map