UNPKG

bbqpool-stratum

Version:

High performance Stratum poolserver in Node.js. Optimized to build with GCC 10 and O3 / bugfixes

832 lines (754 loc) 31.2 kB
/* * * Pool (Updated) * */ const bignum = require('bignum'); const events = require('events'); const utils = require('./utils'); const Algorithms = require('./algorithms'); const Difficulty = require('./difficulty'); const Daemon = require('./daemon'); const Manager = require('./manager'); const Network = require('./network'); const Peer = require('./peer'); //////////////////////////////////////////////////////////////////////////////// // Main Pool Function const Pool = function(poolConfig, portalConfig, authorizeFn, responseFn) { const _this = this; this.poolConfig = poolConfig; this.portalConfig = portalConfig; this.authorizeFn = authorizeFn; this.responseFn = responseFn; this.primary = {}; this.auxiliary = {}; const emitLog = (text) => _this.emit('log', 'debug', text); const emitWarningLog = (text) => _this.emit('log', 'warning', text); const emitSpecialLog = (text) => _this.emit('log', 'special', text); const emitErrorLog = (text) => { _this.emit('log', 'error', text); _this.responseFn(text); }; // Ensure Logger Only Gets Called Once /* istanbul ignore next */ const limitMessages = (callback) => { if (!process.env.forkId || process.env.forkId === '0') { callback(); } }; // Validate Pool Algorithms /* istanbul ignore next */ this.checkAlgorithm = function(algorithm) { if (!(algorithm in Algorithms)) { emitErrorLog(`The ${ algorithm } algorithm is not supported.`); throw new Error(`The ${ algorithm } algorithm is not supported.`); } }; // Check if Algorithms Supported _this.checkAlgorithm(_this.poolConfig.primary.coin.algorithms.mining); _this.checkAlgorithm(_this.poolConfig.primary.coin.algorithms.block); _this.checkAlgorithm(_this.poolConfig.primary.coin.algorithms.coinbase); // Start Pool Capabilities /* istanbul ignore next */ this.setupPool = function() { _this.setupDifficulty(); _this.setupDaemonInterface(() => { _this.setupPoolData(() => { _this.setupRecipients(); _this.setupJobManager(); _this.setupBlockchain(() => { _this.setupFirstJob(() => { _this.setupBlockPolling(); _this.setupPeer(); _this.setupStratum(() => { _this.outputPoolInfo(); _this.emit('started'); }); }); }); }); }); }; // Configure Port Difficulty /* istanbul ignore next */ this.setDifficulty = function(port) { const currentPort = port.port; const currentDifficulty = port.difficulty; if (typeof(_this.difficulty[currentPort]) != 'undefined') { _this.difficulty[currentPort].removeAllListeners(); } const difficultyInstance = new Difficulty(currentPort, currentDifficulty); _this.difficulty[currentPort] = difficultyInstance; _this.difficulty[currentPort].on('newDifficulty', (client, newDiff) => { client.enqueueNextDifficulty(newDiff); }); }; // Initialize Pool Difficulty /* istanbul ignore next */ this.setupDifficulty = function() { _this.difficulty = {}; _this.poolConfig.ports.forEach(port => { if (port.difficulty) { _this.setDifficulty(port); } }); }; // Initialize Specific Pool Daemons /* istanbul ignore next */ this.setupDaemon = function(daemons, callback) { const daemon = new Daemon(daemons, ((severity, message) => { _this.emit('log', severity , message); })); daemon.once('online', () => callback()); daemon.on('connectionFailed', (error) => { emitErrorLog(`Failed to connect daemon(s): ${ JSON.stringify(error) }`); }); daemon.on('error', (message) => emitErrorLog(message)); daemon.initDaemons(() => {}); return daemon; }; // Initialize Pool Daemon this.setupDaemonInterface = function(callback) { if (!Array.isArray(_this.poolConfig.primary.daemons) || _this.poolConfig.primary.daemons.length < 1) { emitErrorLog('No primary daemons have been configured - pool cannot start'); return; } _this.primary.daemon = _this.setupDaemon(_this.poolConfig.primary.daemons, () => {}); if (_this.poolConfig.auxiliary && _this.poolConfig.auxiliary.enabled) { if (!Array.isArray(_this.poolConfig.auxiliary.daemons) || _this.poolConfig.auxiliary.daemons.length < 1) { emitErrorLog('No auxiliary daemons have been configured - pool cannot start'); return; } _this.auxiliary.daemon = _this.setupDaemon(_this.poolConfig.auxiliary.daemons, callback); } else { callback(); } }; // Initialize Pool Data /* istanbul ignore next */ this.setupPoolData = function(callback) { const batchRPCCommand = [ ['validateaddress', [_this.poolConfig.primary.address]], ['getmininginfo', []], ['submitblock', []] ]; // Check if Coin has GetInfo Defined if (_this.poolConfig.primary.coin.getinfo) { batchRPCCommand.push(['getinfo', []]); } else { batchRPCCommand.push(['getblockchaininfo', []], ['getnetworkinfo', []]); } // Manage RPC Batches _this.primary.daemon.batchCmd(batchRPCCommand, (error, results) => { if (error || !results) { emitErrorLog('Could not start pool, error with init batch RPC call'); return; } // Check Results of Each RPC Call const rpcResults = {}; results.forEach((output, idx) => { const rpcCall = batchRPCCommand[idx][0]; rpcResults[rpcCall] = output.result || output.error; if (rpcCall !== 'submitblock' && (output.error || !output.result)) { emitErrorLog(`Could not start pool, error with init RPC call: ${ rpcCall } - ${ JSON.stringify(output.error)}`); return; } }); // Check Pool Address is Valid if (!rpcResults.validateaddress.isvalid) { emitErrorLog('Daemon reports address is not valid'); return; } // Check if Mainnet/Testnet is Active if (_this.poolConfig.primary.coin.getinfo) { _this.poolConfig.settings.testnet = (rpcResults.getinfo.testnet === true) ? true : false; } else { _this.poolConfig.settings.testnet = (rpcResults.getblockchaininfo.chain === 'test') ? true : false; } // Establish Coin Protocol Version _this.poolConfig.primary.address = rpcResults.validateaddress.address; _this.poolConfig.settings.protocolVersion = _this.poolConfig.primary.coin.getinfo ? rpcResults.getinfo.protocolversion : rpcResults.getnetworkinfo.protocolversion; let difficulty = _this.poolConfig.primary.coin.getinfo ? rpcResults.getinfo.difficulty : rpcResults.getblockchaininfo.difficulty; if (typeof(difficulty) == 'object') { difficulty = difficulty['proof-of-work']; } // Establish Coin Initial Statistics _this.poolConfig.statistics = { connections: _this.poolConfig.primary.coin.getinfo ? rpcResults.getinfo.connections : rpcResults.getnetworkinfo.connections, difficulty: difficulty * Algorithms[_this.poolConfig.primary.coin.algorithms.mining].multiplier, }; // Check if Pool is Able to Submit Blocks if (rpcResults.submitblock.message === 'Method not found') { _this.poolConfig.settings.hasSubmitMethod = false; } else if (rpcResults.submitblock.code === -1) { _this.poolConfig.settings.hasSubmitMethod = true; } else { emitErrorLog('Could not detect block submission RPC method'); return; } callback(); }); }; // Initialize Pool Recipients this.setupRecipients = function() { if (_this.poolConfig.primary.recipients.length === 0) { emitWarningLog('No recipients have been added which means that no fees will be taken'); } _this.poolConfig.settings.feePercentage = 0; _this.poolConfig.primary.recipients.forEach(recipient => { _this.poolConfig.settings.feePercentage += recipient.percentage; }); }; // Submit Primary Block to Stratum Server this.submitBlock = function(blockHex, callback) { // Check which Submit Method is Supported let rpcCommand, rpcArgs; if (_this.poolConfig.settings.hasSubmitMethod) { rpcCommand = 'submitblock'; rpcArgs = [blockHex]; } else { rpcCommand = 'getblocktemplate'; rpcArgs = [{'mode': 'submit', 'data': blockHex}]; } // Establish Submission Functionality _this.primary.daemon.cmd(rpcCommand, rpcArgs, false, (results) => { for (let i = 0; i < results.length; i += 1) { const result = results[i]; if (result.error) { emitErrorLog('RPC error with primary daemon instance ' + result.instance.index + ' when submitting block with ' + rpcCommand + ' ' + JSON.stringify(result.error)); return; } else if (result.response === 'rejected') { emitErrorLog(`Primary daemon instance ${ result.instance.index } rejected a supposedly valid block`); return; } } emitSpecialLog(`Submitted primary block successfully to ${ _this.poolConfig.primary.coin.name }'s daemon instance(s)`); callback(); }); }; // Submit Auxiliary Block to Stratum Server /* istanbul ignore next */ this.submitAuxBlock = function(headerBuffer, coinbaseBuffer, blockHash, callback) { // Build Branch Proof from Block Hash const branch = utils.uint256BufferFromHash(_this.auxiliary.rpcData.hash); let branchProof = _this.manager.auxMerkle.getHashProof(branch); if (!branchProof) { branchProof = Buffer.concat([utils.varIntBuffer(0), utils.packInt32LE(0)]); } // Build Coinbase Proof from Current Job Data const coinbaseProof = Buffer.concat([ utils.varIntBuffer(_this.manager.currentJob.merkle.steps.length), Buffer.concat(_this.manager.currentJob.merkle.steps), utils.packInt32LE(0) ]); // Build AuxPoW Block to Submit to Auxiliary Daemon const auxPow = Buffer.concat([ coinbaseBuffer, blockHash, coinbaseProof, branchProof, headerBuffer ]); // Submit AuxPow Block to Auxiliary Daemon const rpcArgs = [_this.auxiliary.rpcData.hash, auxPow.toString('hex')]; _this.auxiliary.daemon.cmd('getauxblock', rpcArgs, false, (results) => { for (let i = 0; i < results.length; i += 1) { const result = results[i]; if (result.error) { emitErrorLog('RPC error with auxiliary daemon instance ' + result.instance.index + ' when submitting block with getauxblock ' + JSON.stringify(result.error)); return; } else if (!result.response || result.response === 'rejected') { emitErrorLog(`Auxiliary daemon instance ${ result.instance.index } rejected a supposedly valid block`); return; } } emitSpecialLog(`Submitted auxiliary block successfully to ${ _this.poolConfig.auxiliary.coin.name }'s daemon instance(s)`); callback(_this.auxiliary.rpcData.hash); }); }; // Check Whether Block was Accepted by Daemon this.checkBlockAccepted = function(blockHash, daemon, callback) { daemon.cmd('getblock', [blockHash], false, (results) => { const validResults = results.filter((result) => { return result.response && (result.response.hash === blockHash); }); if (validResults.length >= 1) { if (validResults[0].response.confirmations >= 0) { callback(true, validResults[0].response.tx[0]); } else { emitErrorLog('Block was rejected by the network'); callback(false); } } else { emitErrorLog('Block was rejected by the network'); callback(false); } }); }; // Load Current Block Template this.getBlockTemplate = function(callback, force) { const callConfig = { 'capabilities': [ 'coinbasetxn', 'workid', 'coinbase/append' ] }; if (_this.poolConfig.primary.coin.segwit) { callConfig.rules = ['segwit']; } // Handle Block Templates/Subsidy _this.primary.daemon.cmd('getblocktemplate', [callConfig], true, (result) => { if (result.error) { emitErrorLog('getblocktemplate call failed for daemon instance ' + result.instance.index + ' with error ' + JSON.stringify(result.error)); callback(result.error); } else { if (_this.poolConfig.auxiliary && _this.poolConfig.auxiliary.enabled) { result.response.auxData = _this.auxiliary.rpcData; } const processedNewBlock = _this.manager.processTemplate(result.response, force); callback(null, result.response, processedNewBlock); } }); }; // Update Work for Auxiliary Chain /* istanbul ignore next */ this.getAuxTemplate = function(callback) { if (_this.poolConfig.auxiliary && _this.poolConfig.auxiliary.enabled) { _this.auxiliary.daemon.cmd('getauxblock', [], true, (result) => { if (result.error) { emitErrorLog('getauxblock call failed for daemon instance ' + result.instance.index + ' with error ' + JSON.stringify(result.error)); callback(result.error); } else { let updateTemplate = false; const hash = result.response.target || result.response._target || ''; const target = utils.uint256BufferFromHash(hash, {endian: 'little', size: 32}); if (_this.auxiliary.rpcData) { if (_this.auxiliary.rpcData.hash != result.response.hash) { updateTemplate = true; } } _this.auxiliary.rpcData = result.response; _this.auxiliary.rpcData.target = bignum.fromBuffer(target); callback(null, result.response, updateTemplate); } }); } else { callback(null, null, false); } }; // Initialize Pool Job Manager /* istanbul ignore next */ this.setupJobManager = function() { // Establish Pool Manager _this.manager = new Manager(_this.poolConfig, _this.portalConfig); _this.manager.on('newBlock', (blockTemplate) => { if (_this.stratum) { _this.stratum.broadcastMiningJobs(blockTemplate, true); if (_this.poolConfig.debug) { emitLog('Established new job for updated block template'); } } }); // Handle Share Submissions _this.manager.on('share', (shareData, auxShareData, blockValid) => { // Calculate Status of Submitted Share let shareType = 'valid'; if (shareData.error && shareData.error === 'job not found') { shareType = 'stale'; } else if (shareData.error) { shareType = 'invalid'; } // Process Share/Primary Submission if (!blockValid) { _this.emit('share', shareData, shareType, blockValid, () => {}); } else { _this.submitBlock(shareData.hex, () => { _this.checkBlockAccepted(shareData.hash, _this.primary.daemon, (accepted, tx) => { shareData.transaction = tx; _this.emit('share', shareData, shareType, accepted, () => {}); _this.getBlockTemplate((error, result, foundNewBlock) => { if (foundNewBlock) { emitSpecialLog('Block notification via RPC after primary block submission'); } }, false); }); }); } // Process Auxiliary Block Submission if (shareType === 'valid' && _this.poolConfig.auxiliary && _this.poolConfig.auxiliary.enabled) { // Calculate Auxiliary Difficulty const algorithm = _this.poolConfig.primary.coin.algorithms.mining; const shareMultiplier = Algorithms[algorithm].multiplier; const difficulty = parseFloat((Algorithms[algorithm].diff / _this.auxiliary.rpcData.target.toNumber()).toFixed(9)); auxShareData.blockDiffAuxiliary = difficulty * shareMultiplier; // Check if Share is Valid Block Candidate if (_this.auxiliary.rpcData.target.ge(auxShareData.headerDiff)) { const hexBuffer = Buffer.from(auxShareData.hex, 'hex').slice(0, 80); _this.submitAuxBlock(hexBuffer, auxShareData.coinbase, auxShareData.header, (hash) => { _this.checkBlockAccepted(hash, _this.auxiliary.daemon, (accepted, tx) => { auxShareData.transaction = tx; auxShareData.height = _this.auxiliary.rpcData.height; auxShareData.reward = _this.auxiliary.rpcData.coinbasevalue; _this.emit('share', auxShareData, shareType, accepted, () => {}); _this.getBlockTemplate((error, result, foundNewBlock) => { if (foundNewBlock) { emitSpecialLog('Block notification via RPC after auxiliary block submission'); } }, true); }); }); } } }); // Handle Updated Block Data _this.manager.on('updatedBlock', (blockTemplate) => { if (_this.stratum) { _this.stratum.broadcastMiningJobs(blockTemplate, false); } }); }; // Wait Until Blockchain is Fully Synced /* istanbul ignore next */ this.setupBlockchain = function(callback) { const callConfig = { 'capabilities': [ 'coinbasetxn', 'workid', 'coinbase/append' ] }; if (_this.poolConfig.primary.coin.segwit) { callConfig.rules = ['segwit']; } // Calculate Current Progress on Sync const generateProgress = function() { const cmd = _this.poolConfig.primary.coin.getinfo ? 'getinfo' : 'getblockchaininfo'; _this.primary.daemon.cmd(cmd, [], false, (results) => { const blockCount = Math.max.apply(null, results .flatMap(result => result.response) .flatMap(response => response.blocks)); // Compare with Peers to Get Percentage Synced _this.primary.daemon.cmd('getpeerinfo', [], true, (result) => { const peers = result.response; const totalBlocks = Math.max.apply(null, peers .flatMap(response => response.startingheight)); const percent = (blockCount / totalBlocks * 100).toFixed(2); emitWarningLog(`Downloaded ${ percent }% of blockchain from ${ peers.length } peers`); }); }); }; // Check for Blockchain to be Fully Synced const checkSynced = function(displayNotSynced) { _this.primary.daemon.cmd('getblocktemplate', [callConfig], false, (results) => { const synced = results.every((r) => { return !r.error || r.error.code !== -10; }); if (synced) { callback(); } else { if (displayNotSynced) { displayNotSynced(); } setTimeout(checkSynced, 30000); limitMessages(() => generateProgress()); } }); }; // Check and Return Message if Not Synced checkSynced(() => { limitMessages(() => { emitErrorLog('Daemon is still syncing with the network. The server will be started once synced'); }); }); }; // Initialize First Pool Job /* istanbul ignore next */ this.setupFirstJob = function(callback) { _this.getAuxTemplate(() => { _this.getBlockTemplate((error) => { if (error) { emitErrorLog('Error with getblocktemplate on creating first job, server cannot start'); return; } const portWarnings = []; const networkDiffAdjusted = _this.poolConfig.statistics.difficulty; _this.poolConfig.ports.forEach(port => { const currentPort = port.port; const portDiff = port.difficulty.initial; if (networkDiffAdjusted < portDiff) { portWarnings.push(`port ${ currentPort } w/ diff ${ portDiff}`); } }); if (portWarnings.length > 0) { limitMessages(() => { emitWarningLog(`Network diff of ${ networkDiffAdjusted } is lower than ${ portWarnings.join(' and ') }`); }); } callback(); }, false); }); }; // Initialize Pool Block Polling /* istanbul ignore next */ this.setupBlockPolling = function() { if (typeof _this.poolConfig.settings.blockRefreshInterval !== 'number' || _this.poolConfig.settings.blockRefreshInterval <= 0) { emitLog('Block template polling has been disabled'); return; } let pollingFlag = false; const pollingInterval = _this.poolConfig.settings.blockRefreshInterval; setInterval(() => { if (pollingFlag === false) { pollingFlag = true; _this.getAuxTemplate((auxililaryError, auxiliaryResult, auxiliaryUpdate) => { _this.getBlockTemplate((primaryError, primaryResult, primaryUpdate) => { pollingFlag = false; if (primaryUpdate && !auxiliaryUpdate) { limitMessages(() => { emitLog(`Primary chain (${ _this.poolConfig.primary.coin.name }) notification via RPC polling at height ${ primaryResult.height }`); }); } if (auxiliaryUpdate) { limitMessages(() => { emitLog(`Auxiliary chain (${ _this.poolConfig.auxiliary.coin.name }) notification via RPC polling at height ${ auxiliaryResult.height }`); }); } }, auxiliaryUpdate); }); } }, pollingInterval); }; // Process Block when Found /* istanbul ignore next */ this.processBlockNotify = function(blockHash) { const currentJob = _this.manager.currentJob; if ((typeof(currentJob) !== 'undefined') && (blockHash !== currentJob.rpcData.previousblockhash)) { _this.getBlockTemplate((error) => { if (error) { emitErrorLog(`Block notify error getting block template for ${ _this.poolConfig.primary.coin.name }`); } else { emitLog(`Block template for ${ _this.poolConfig.primary.coin.name } updated successfully`); } }, false); } }; // Initialize Pool Peers /* istanbul ignore next */ this.setupPeer = function() { // Establish Peer Settings _this.poolConfig.settings.verack = false; _this.poolConfig.settings.validConnectionConfig = true; // Check for P2P Configuration if (!_this.poolConfig.p2p || !_this.poolConfig.p2p.enabled) { limitMessages(() => { emitLog('p2p has been disabled in the configuration'); }); return; } if (_this.poolConfig.settings.testnet && !_this.poolConfig.primary.coin.testnet.peerMagic) { emitErrorLog('p2p cannot be enabled in testnet without peerMagic set in testnet configuration'); return; } else if (!_this.poolConfig.primary.coin.mainnet.peerMagic) { emitErrorLog('p2p cannot be enabled without peerMagic set in mainnet configuration'); return; } // Establish Peer Server _this.peer = new Peer(_this.poolConfig); _this.peer.on('blockFound', (hash) => { emitLog('Block notification via p2p', false); _this.processBlockNotify(hash); }); _this.peer.on('connectionFailed', () => { emitErrorLog('p2p connection failed - likely incorrect host or port'); }); _this.peer.on('connectionRejected', () => { emitErrorLog('p2p connection failed - likely incorrect p2p magic value'); }); _this.peer.on('error', (msg) => { emitErrorLog(`p2p had an error: ${ msg }`); }); _this.peer.on('socketError', (e) => { emitErrorLog(`p2p had a socket error: ${ JSON.stringify(e) }`); }); }; // Start Pool Stratum Server /* istanbul ignore next */ this.setupStratum = function(callback) { // Establish Stratum Server _this.stratum = new Network(_this.poolConfig, _this.portalConfig, _this.authorizeFn); _this.stratum.on('started', () => { const stratumPorts = _this.poolConfig.ports .filter(port => port.enabled) .flatMap(port => port.port); _this.poolConfig.statistics.stratumPorts = stratumPorts; _this.stratum.broadcastMiningJobs(_this.manager.currentJob, true); callback(); }); // Establish Timeout Functionality _this.stratum.on('broadcastTimeout', () => { if (_this.poolConfig.debug) { emitLog(`No new blocks for ${ _this.poolConfig.settings.jobRebroadcastTimeout } seconds - updating transactions & rebroadcasting work`); } _this.getBlockTemplate((error, rpcData, processedBlock) => { if (error || processedBlock) return; _this.manager.updateCurrentJob(rpcData); if (_this.poolConfig.debug) { emitLog('Updated existing job for current block template'); } }, false); }); // Establish New Connection Functionality _this.stratum.on('client.connected', (client) => { if (typeof(_this.difficulty[client.socket.localPort]) !== 'undefined') { _this.difficulty[client.socket.localPort].manageClient(client); } // Emit Event when Difficulty is Queued client.on('difficultyQueued', (diff) => { emitLog('Difficulty update queued for worker: ' + client.addrPrimary + ' (' + diff + ')'); }); // Emit Event when Difficulty is Updated client.on('difficultyChanged', (diff) => { emitLog('Difficulty updated successfully for worker: ' + client.addrPrimary + ' (' + diff + ')'); }); // Establish Client Subscription Functionality client.on('subscription', (params, callback) => { const extraNonce = _this.manager.extraNonceCounter.next(); switch (_this.poolConfig.primary.coin.algorithms.mining) { // Kawpow/Firopow Subscription case 'kawpow': case 'firopow': callback(null, extraNonce, extraNonce); break; // Default Subscription default: callback(null, extraNonce, _this.manager.extraNonce2Size); break; } const validPorts = _this.poolConfig.ports .filter(port => port.port === client.socket.localPort) .filter(port => typeof port.difficulty.initial !== undefined); if (validPorts.length >= 1) { client.sendDifficulty(validPorts[0].difficulty.initial); } else { client.sendDifficulty(8); } const jobParams = _this.manager.currentJob.getJobParams(client, true); client.sendMiningJob(jobParams); }); // Establish Client Submission Functionality client.on('submit', (message, callback) => { let result, submission; switch (_this.poolConfig.primary.coin.algorithms.mining) { // Kawpow/Firopow Submission case 'kawpow': case 'firopow': submission = { extraNonce1: client.extraNonce1, nonce: message.params[2].substr(2), headerHash: message.params[3].substr(2), mixHash: message.params[4].substr(2), }; result = _this.manager.processShare( message.params[1], client.previousDifficulty, client.difficulty, client.remoteAddress, client.socket.localPort, client.addrPrimary, client.addrAuxiliary, submission, ); break; // Default Submission default: submission = { extraNonce1: client.extraNonce1, extraNonce2: message.params[2], nTime: message.params[3], nonce: message.params[4], versionBit: message.params[5], versionMask: client.versionMask, asicboost: client.asicboost, }; result = _this.manager.processShare( message.params[1], client.previousDifficulty, client.difficulty, client.remoteAddress, client.socket.localPort, client.addrPrimary, client.addrAuxiliary, submission, ); break; } callback(result.error, result.result ? true : null); }); // Establish Miscellaneous Client Functionality client.on('malformedMessage', (message) => { emitWarningLog(`Malformed message from ${ client.getLabel() }: ${ JSON.stringify(message) }`); }); client.on('socketError', (e) => { emitWarningLog(`Socket error from ${ client.getLabel() }: ${ JSON.stringify(e) }`); }); client.on('socketTimeout', (reason) => { emitWarningLog(`Connection timed out for ${ client.getLabel() }: ${ reason }`); }); client.on('socketDisconnect', () => { emitWarningLog(`Socket disconnect for ${ client.getLabel() }`); }); client.on('kickedBannedIP', (remainingBanTime) => { emitLog(`Rejected incoming connection from ${ client.remoteAddress }. The client is banned for ${ remainingBanTime } seconds.`); }); client.on('forgaveBannedIP', () => { emitLog(`Forgave banned IP ${ client.remoteAddress }`); }); client.on('unknownStratumMethod', (fullMessage) => { emitLog(`Unknown stratum method from ${ client.getLabel() }: ${ fullMessage.method }`); }); client.on('socketFlooded', () => { emitWarningLog(`Detected socket flooding from ${ client.getLabel() }`); }); client.on('tcpProxyError', (data) => { emitErrorLog(`Client IP detection failed, tcpProxyProtocol is enabled yet did not receive proxy protocol message, instead got data: ${ data }`); }); client.on('triggerBan', (reason) => { emitWarningLog(`Ban triggered for ${ client.getLabel() }: ${ reason }`); _this.emit('banIP', client.remoteAddress, client.addrPrimary); }); // Indicate that Client Created Successfully _this.emit('connectionSucceeded'); }); }; // Output Derived Pool Information /* istanbul ignore next */ this.outputPoolInfo = function() { const startMessage = `Stratum pool server started for ${ _this.poolConfig.name }`; const infoLines = [startMessage, `Coins Connected:\t${ _this.poolConfig.coins }`, `Network Connected:\t${ _this.poolConfig.settings.testnet ? 'Testnet' : 'Mainnet' }`, `Current Block Height:\t${ _this.manager.currentJob.rpcData.height }`, `Current Connect Peers:\t${ _this.poolConfig.statistics.connections }`, `Current Block Diff:\t${ _this.manager.currentJob.difficulty * Algorithms[_this.poolConfig.primary.coin.algorithms.mining].multiplier }`, `Network Difficulty:\t${ _this.poolConfig.statistics.difficulty }`, `Stratum Port(s):\t${ _this.poolConfig.statistics.stratumPorts.join(', ') }`, `Pool Fee Percentage:\t${ _this.poolConfig.settings.feePercentage * 100 }%`, ]; if (typeof _this.poolConfig.settings.blockRefreshInterval === 'number' && _this.poolConfig.settings.blockRefreshInterval > 0) { infoLines.push(`Block Polling Every:\t${ _this.poolConfig.settings.blockRefreshInterval } ms`); } limitMessages(() => { emitSpecialLog(infoLines.join('\n\t\t\t\t')); }); _this.responseFn(true); }; }; module.exports = Pool; Pool.prototype.__proto__ = events.EventEmitter.prototype;