UNPKG

@cityofzion/neo-js

Version:

Running NEO blockchain full node with Node.js and MongoDB.

192 lines 7.73 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const events_1 = require("events"); const node_log_it_1 = require("node-log-it"); const lodash_1 = require("lodash"); const MODULE_NAME = 'Mesh'; const DEFAULT_OPTIONS = { startBenchmarkOnInit: true, toFetchUserAgent: true, benchmarkIntervalMs: 2000, fetchMissingUserAgentIntervalMs: 5000, refreshUserAgentIntervalMs: 5 * 60 * 1000, minActiveNodesRequired: 2, pendingRequestsThreshold: 5, loggerOptions: {}, }; class Mesh extends events_1.EventEmitter { constructor(nodes, options = {}) { super(); this._isReady = false; this.nodes = nodes; if (this.nodes.length === 0) { throw new Error('Mesh must have 1 or more nodes.'); } this.options = lodash_1.merge({}, DEFAULT_OPTIONS, options); this.validateOptionalParameters(); this.logger = new node_log_it_1.Logger(MODULE_NAME, this.options.loggerOptions); if (this.options.startBenchmarkOnInit) { this.startBenchmark(); } this.logger.debug('constructor completes.'); } isReady() { return this._isReady; } startBenchmark() { this.logger.debug('startBenchmark triggered.'); const unknownNodes = lodash_1.filter(this.nodes, (n) => n.isActive === undefined); this.logger.debug('unknownNodes.length:', unknownNodes.length); unknownNodes.forEach((n) => { n.getBlockCount() .then(() => { this.checkMeshReady(); }) .catch((err) => { this.logger.info('node.getBlockCount() failed, but to continue. Endpoint:', n.endpoint, 'Message:', err.message); }); }); if (this.options.toFetchUserAgent) { unknownNodes.forEach((n) => { n.getVersion().catch((err) => { this.logger.info('node.getVersion() failed, but to continue. Endpoint:', n.endpoint, 'Message:', err.message); }); }); this.fetchMissingUserAgentIntervalId = setInterval(() => this.performFetchMissingUserAgent(), this.options.fetchMissingUserAgentIntervalMs); this.refreshUserAgentIntervalId = setInterval(() => this.performRefreshUserAgent(), this.options.refreshUserAgentIntervalMs); } this.benchmarkIntervalId = setInterval(() => this.performBenchmark(), this.options.benchmarkIntervalMs); } stopBenchmark() { this.logger.debug('stopBenchmark triggered.'); if (this.benchmarkIntervalId) { clearInterval(this.benchmarkIntervalId); } if (this.fetchMissingUserAgentIntervalId) { clearInterval(this.fetchMissingUserAgentIntervalId); } if (this.refreshUserAgentIntervalId) { clearInterval(this.refreshUserAgentIntervalId); } } close() { this.logger.debug('close triggered.'); this.stopBenchmark(); this.nodes.forEach((n) => { n.close(); }); } getFastestNode(activeOnly = true) { this.logger.debug('getFastestNode triggered.'); let nodePool = activeOnly ? this.listActiveNodes() : this.nodes; if (nodePool.length === 0) { return undefined; } nodePool = lodash_1.filter(nodePool, (n) => n.latency !== undefined); if (nodePool.length === 0) { return undefined; } return lodash_1.minBy(nodePool, 'latency'); } getHighestNode(activeOnly = true) { this.logger.debug('getHighestNode triggered.'); let nodePool = activeOnly ? this.listActiveNodes() : this.nodes; if (nodePool.length === 0) { return undefined; } nodePool = lodash_1.filter(nodePool, (n) => n.blockHeight !== undefined); if (nodePool.length === 0) { return undefined; } return lodash_1.maxBy(nodePool, 'blockHeight'); } getRandomNode(activeOnly = true) { this.logger.debug('getRandomNode triggered.'); const nodePool = activeOnly ? this.listActiveNodes() : this.nodes; if (nodePool.length === 0) { return undefined; } const randomIndex = lodash_1.random(0, nodePool.length - 1); return nodePool[randomIndex]; } getOptimalNode(height, activeOnly = true) { this.logger.debug('getOptimalNode triggered.'); const nodePool = activeOnly ? this.listActiveNodes() : this.nodes; if (nodePool.length === 0) { return undefined; } const qualifyHeightNodes = lodash_1.filter(this.nodes, (n) => n.blockHeight !== undefined && n.blockHeight >= height); if (qualifyHeightNodes.length === 0) { return undefined; } const qualifyPendingNodes = lodash_1.filter(qualifyHeightNodes, (n) => !n.pendingRequests || n.pendingRequests <= this.options.pendingRequestsThreshold); if (qualifyPendingNodes.length === 0) { const randomIndex = lodash_1.random(0, qualifyHeightNodes.length - 1); return qualifyHeightNodes[randomIndex]; } return lodash_1.minBy(qualifyPendingNodes, 'latency'); } validateOptionalParameters() { } performBenchmark() { this.logger.debug('performBenchmark triggered.'); const node = this.getNodeToBenchmark(); if (node) { node.getBlockCount().catch((err) => { this.logger.info('node.getBlockCount error in performBenchmark(). Endpoint:', node.endpoint, 'Message:', err.message); }); } else { this.logger.info('Unable to find a suitable node to perform benchmark.'); } } getNodeToBenchmark() { this.logger.debug('getNodeToBenchmark triggered.'); const nodePool = lodash_1.filter(this.nodes, (n) => !n.isBenchmarking); if (nodePool.length === 0) { return undefined; } const unknownNode = lodash_1.find(nodePool, (n) => n.lastPingTimestamp === undefined); if (unknownNode) { return unknownNode; } const targetNode = lodash_1.minBy(nodePool, (n) => n.lastPingTimestamp); return targetNode; } performFetchMissingUserAgent() { this.logger.debug('performBenchmark triggered.'); const nodePool = lodash_1.filter(this.nodes, (n) => n.userAgent === undefined); nodePool.forEach((n) => { n.getVersion().catch((err) => { this.logger.info('node.getVersion() failed, but to continue. Endpoint:', n.endpoint, 'Message:', err.message); }); }); } performRefreshUserAgent() { this.logger.debug('performRefreshUserAgent triggered.'); this.nodes.forEach((n) => { n.getVersion().catch((err) => { this.logger.info('node.getVersion() failed, but to continue. Endpoint:', n.endpoint, 'Message:', err.message); }); }); } checkMeshReady() { this.logger.debug('checkMeshReady triggered.'); const activeNodes = this.listActiveNodes(); if (!this.options.minActiveNodesRequired || activeNodes.length >= this.options.minActiveNodesRequired) { if (!this._isReady) { this.setReady(); this.logger.debug('mesh is considered to be now ready.'); } } } setReady() { this._isReady = true; this.emit('ready'); } listActiveNodes() { return lodash_1.filter(this.nodes, { isActive: true }); } } exports.Mesh = Mesh; //# sourceMappingURL=mesh.js.map